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. |
Monday, October 4, 2021
Grails 4 - how to attach domain entity manually to session and marked as readonly mode in hibernate session
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment