Showing posts with label grails4. Show all posts
Showing posts with label grails4. Show all posts

Thursday, October 21, 2021

Grails 4: How to add Java JAR files to Grails project | How to add an external library or JAR file that is not a grails plugin to the Grails project

Putting the jar in the lib folder should do the trick.

The default lib folder is gone as of Grails 3.0. grails.github.io/grails-doc/3.0.x/guide/single.html#upgrading --> "Dependency resolution should be used to resolve JAR files"
If Grails by default don't take the local .jar libraries located in <GRAILS-APP-DIR>/lib (seems that with Grails >= 3.X the /lib folder default configuration is removed) the easy way to enforce it is modifying build.gradle to add a local directory dependency for it.
For almost all cases is of course better to use the maven repos, but in some cases it's possible to have some other libraries which aren't in a public repo. To do so we have to add these libraries in some lib folder then modify the <GRAILS-APP-DIR>/build.gradle and add something like:
dependencies {
    ...
	// lib folder or any other name one can use 
    compile fileTree(dir: './lib', include: ['*.jar'])
    ...
}
If you want you can use another directory (not /lib) since you're specifying it. Of course use the correct scope (for example for jars which probably already are in your application container class path the scope will be runtime instead of compile)
You can download and drop the jar file into the grails-app/lib directory directly. This should be carefully maintained by someone time to time. Other developers working on the same project might not be aware of its presence. Plus you cannot easily upgrade versions in a transparent manner.

Tuesday, October 12, 2021

Grails 4: How to load datasource configuration from external file in grails 4 In Grails, how do I put my DB username and password in an external property file

I'm trying to take certain database configurations from variables and put them into an external properties file.

I am writing a grails 4.0.11 application. My datasource written in application.groovy file.

I want to load datasource configuration like username,password,DB from an external file. Is there any way to do it in grails 4+ versions.

Here is my datasource configuration in application.groovy using static database name and other properties like username, password etc:-
hibernate {
    cache {
        queries = false
        use_second_level_cache = true
        use_query_cache = true
    }
}

dataSource {
    pooled = true
    jmxExport = true
    dialect = "org.hibernate.dialect.MySQL5InnoDBDialect"
    driverClassName = "org.mariadb.jdbc.Driver"
    username = 'root'
    password = ''
    dbCreate = "update"
    url = "jdbc:mysql://localhost/db2?useUnicode=yes" +
            "&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true" +
            "&useLegacyDatetimeCode=false&serverTimezone=UTC"
    properties = {
        jmxEnabled = true
        initialSize = 5
        maxActive = 50
        minIdle = 5
        maxIdle = 25
        maxWait = 10000
        maxAge = 10 * 60000
        timeBetweenEvictionRunsMillis = 5000
        minEvictableIdleTimeMillis = 60000
        validationQuery = "SELECT 1"
        validationQueryTimeout = 3
        validationInterval = 15000
        testOnBorrow = true
        testWhileIdle = true
        testOnReturn = false
        ignoreExceptionOnPreLoad = true
        jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
        defaultTransactionIsolation = Connection.TRANSACTION_READ_COMMITTED // safe default
        abandonWhenPercentageFull = 100 // settings are active only when pool is full
        removeAbandonedTimeout = 120
        removeAbandoned = true
        logAbandoned = false // causes stacktrace recording overhead, use only for debugging
    }
}
Yes, what we can do that is to put database configurations to a file named db_name.properties under [src/main/webapp] directory with following contents:

db_name=some_data_base_name
db_user=root_user
db_password=some_password

Keeping these information will not load automatically. We have to do something magic to load these information into system.

We can define database configuration for grails 4 in 3 different ways -

1. grails-app/conf/config/application.yml
2. grails-app/conf/application
3. grails-app/conf/application.groovy


So from above list we can easily set our target file to load grails 4 application datasource information because we can write code inside groovy files.

First of all remove any datasource related block from above 2 files and add configuration to grails-app/conf/application.groovy file as early statement with some modification.

Now we will load database information from some properties file. We sill use Properties to load database information from file.

Check below code snippet:
import grails.util.BuildSettings

import java.sql.Connection

grails {
    gorm {
        failOnError = true
        'default' {
            mapping = {
                cache true
                version false
                autoTimestamp false
                id generator:'assigned'
                '*'(cascadeValidate: 'none')
            }
        }
    }
}
Properties ppt = new Properties()
File file = new File(BuildSettings.BASE_DIR.absolutePath + "/src/main/webapp/db.properties")
println("Setting up db name-${file.absolutePath}, exists=${file.exists() ? 1 : 0}")
if (file.exists()) {
    file.getCanonicalFile().withInputStream { InputStream stream ->
        ppt.load(stream)
    }
}
println(ppt)

hibernate {
    cache {
        queries = false
        use_second_level_cache = true
        use_query_cache = true
    }
}

dataSource {
    pooled = true
    jmxExport = true
    dialect = "org.hibernate.dialect.MySQL5InnoDBDialect"
    driverClassName = "org.mariadb.jdbc.Driver"
    username = 'root'
    password = ''
    dbCreate = "update"
    url = "jdbc:mysql://localhost/${ppt.get("db.name", "none_db_selected")}?useUnicode=yes" +
            "&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true" +
            "&useLegacyDatetimeCode=false&serverTimezone=UTC"
    properties = {
        jmxEnabled = true
        initialSize = 5
        maxActive = 50
        minIdle = 5
        maxIdle = 25
        maxWait = 10000
        maxAge = 10 * 60000
        timeBetweenEvictionRunsMillis = 5000
        minEvictableIdleTimeMillis = 60000
        validationQuery = "SELECT 1"
        validationQueryTimeout = 3
        validationInterval = 15000
        testOnBorrow = true
        testWhileIdle = true
        testOnReturn = false
        ignoreExceptionOnPreLoad = true
        jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
        defaultTransactionIsolation = Connection.TRANSACTION_READ_COMMITTED // safe default
        abandonWhenPercentageFull = 100 // settings are active only when pool is full
        removeAbandonedTimeout = 120
        removeAbandoned = true
        logAbandoned = false // causes stacktrace recording overhead, use only for debugging
    }
}
In above example I only set database name, you can set anything from that configuration file as I did for database name.

Grails saves datetime as UTC time, but reads it as local server time

Grails saves datetime as UTC time, but reads it as local server time???
The timestamp is read back as local time instead. So if my timezone is +2 UTC and the current local time is 12:30:00, what will be saved to the database is 10:30:00, but when I read it back, it becomes 10:30:00. Does anybody know how to fix this problem so that Grails will read the timestamp back as UTC and convert it accordingly?
I have the following line in my Grails application (BootStrap.groovy) to set the default timezone to UTC:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
And above line solved my problem. Now I can set time anything to db and returned back the same date to my domain field

Monday, October 4, 2021

Grails 4 - how to attach domain entity manually to session and marked as readonly mode in hibernate session

Basically when an entity loaded from database via hibernate transaction manager then automatically attached to hibernate session.

If we use Domain.read() then domain entity attached with hibernate session but marked as readonly mode.

In such case domain don't get updated on any change, because they marked as readonly mode.

But what will happen if you want to attach entity in hibernate session manually and mark them as readonly mode.
To understand check below example:

Account account = new Account()
account.id = 10 
// we already know that there is an entity with id 10
// and we don't want to fetch data from database

Order order = new Order()
order.account = account
order.save()
Above example will execute but when the statement [order.account = account] executed, a fetch operation will take place like:

select account.id,account..... from account where account.id=10 because account entity is not attached with current hibernate session.

To overcome this problem we will manually attach account entity to hibernate session and mark them as readonly state using:
import org.hibernate.internal.SessionImpl

Account.withSession { SessionImpl session ->
	account.attach()
	session.setReadOnly(account, true)
	// readonly entity will not persist in database
}
Now when saving order, account entity already attached with hibernate session and no additional query will be executed.

This mechanism will help you when you know that an entity already exising and you don't want to fetch from db.
Here is another fantastic solution I hope you will like this idea.

As of latest hibernate version, all domain entities must be attached to session (previous hibernate version don't recommend this)

So check below case:

Account account = new Account()
account.id = 10 
// we already know that there is an entity with id 10
// and we don't want to fetch data from database

Order order = new Order()
order.account = account


When assign account to order, system expect account must be attached with session as account and order both are domain entities.

And because of account is not attached with current session, an select query will took place here to fetch data from database.
To avoid this we can do some simple trick so that without reading one entity from database we can continue our operation.

At first we need to add a Map field to each domain class to hold some value like:

class Account implements MyDomainInterface {
    Object TEMP = [:]
    Object ATTACHED_OBJECT_ENTITIES = [:]
    
    Long id

    void setAsCarbonCopy() {
        this.TEMP.existing = true
    }

    Boolean isCarbonCopy(Object o = null) {
        return this.TEMP.containsKey("existing")
    }
}


You need to do same for order domain class.

Then we need to extend metaclass like:

import grails.core.GrailsApplication
import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack

GrailsApplication grailsApplication

grailsApplication.domainClasses.each { domainClass ->
    domainClass.metaClass.setProperty = { String propertyName, propertyValue ->
        MetaProperty metaProperty = domainClass.metaClass.getMetaProperty(propertyName);
        if (metaProperty) {
            if (propertyValue instanceof MyDomainInterface && propertyValue.isCarbonCopy()) {
                //we will set an proxy instance rather then real entity
                //proxy entity by default attached with session
                //but no addition query run for data collection until call explicitly
                Object no = propertyValue.class.proxy(propertyValue.id)
                delegate.ATTACHED_OBJECT_ENTITIES[propertyName] = propertyValue
                metaProperty.setProperty(delegate, no)
            }
            else {
                metaProperty.setProperty(delegate, propertyValue)
            }
        }
        else {
            throw new MissingPropertyExceptionNoStack(propertyName, domainClass.metaClass.theClass)
        }
    }
    domainClass.metaClass.getProperty = { String propertyName ->
        if (propertyName != "ATTACHED_OBJECT_ENTITIES" && delegate.ATTACHED_OBJECT_ENTITIES.containsKey(propertyName)) {
            //we will return original domain entity 
            //rather than proxy entity
            return delegate.ATTACHED_OBJECT_ENTITIES[propertyName]
        }
        MetaProperty metaProperty = domainClass.metaClass.getMetaProperty(propertyName)
        return metaProperty.getProperty(delegate)
    }
}
Then do the logic as follows:

Account account = new Account()
account.id = 10 
account.setAsCarbonCopy()

Order order = new Order()
order.account = account
// now no additional query will run


You can get order.account which will return above pojo entity as we modified our meta method.

So any change on account and/or order.account will not automatically persist in our database as account is nothing but a pojo entity.

And is not attach with session, we attached Account.proxy() with hibernate session, not pojo entity.

Wednesday, September 29, 2021

GRAILS 4 - how to disable deepvalidate in grails globally | Add ability to control cascading validation independently

How can we disable deepvalidate on global level in grails 4? as in our case on saving one domain object its trying to save all internal domain objects leading to different errors like unique constraint and all.

If GORM entity references some other entities, then during its constraints evaluation (validation) the constraints of the referenced entity could be evaluated also, if needed. There is a special parameter cascadeValidate in the entity mappings section, which manage the way of this cascaded validation happens.

You can do this in three ways
1. Define cascadeValidate as mapping per domain where needed:

class Author {
    Publisher publisher

    static mapping = {
        publisher(cascadeValidate: "none")
    }
}

class Publisher {
    String name

    static constraints = {
        name blank: false
    }
}
The following table presents all options, which can be used:

none: Will not do any cascade validation at all for the association.

default: The DEFAULT option. GORM performs cascade validation in some cases.

dirty: Only cascade validation if the referenced object is dirty via the DirtyCheckable trait. If the object doesn’t implement DirtyCheckable, this will fall back to default.

owned: Only cascade validation if the entity owns the referenced object.
2. It is possible to set the global option for the cascadeValidate:

Globally disable cascadeValidate in Grails 3 or 4 using:

grails {
    gorm {
        failOnError = true
        'default' {
            mapping = {
                cache true
                version false
                autoTimestamp false
                id generator:'assigned'
                '*'(cascadeValidate: 'none') // this one is the option to disable deep validate
            }
        }
    }
}
3. Alternatevely you can disable when call save() or merge() using:

new Account().save(validate: true, deepValidate: false)

Grails gorm reference link:

Reference https://gorm.grails.org/latest/hibernate/manual/#_cascade_constraints_validation