Showing posts with label Hibernate Session. Show all posts
Showing posts with label Hibernate Session. Show all posts

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, March 15, 2017

How can I determine whether an entity already exists in the Hibernate session cache

Hibernate use first & second level cache to store database data fetch from database and actually its Hibernate Session. So when your read data from database it stores in Hibernate Session. There is a separate Hibernate Session for each request. Sometimes we need to identify if any object exists in session or not. Below is a simple code snippet to determine if any object exists in current Hibernate Session or not. The most important thing is that you must use proxy to reference the object to check.


package com.pkm

import grails.util.Holders
import org.hibernate.SessionFactory
import org.hibernate.engine.spi.EntityKey
import org.hibernate.stat.SessionStatistics

/**
 * Created by pritom on 15/03/2017.
 * Session.html#getStatistics
 * SessionStatistics.html#getEntityKeys
 */
class HibernateSessionUtil {
    private static SessionFactory sessionFactory

    public static void main(String[] args) {
        def domainInstance = "DomainClass".proxy(100L)
        checkIfObjectExistsInSession(domainInstance)
    }

    static Boolean checkIfObjectExistsInSession(def domainInstance) {
        SessionStatistics sessionStatistics = sessionFactory.currentSession.getStatistics()
        println("Total ${sessionStatistics.getEntityKeys().asList().size()} Object Exists in Session")
        Boolean exists = false
        sessionStatistics.getEntityKeys().asList().find { EntityKey entityKey ->
            println("EntityName=${entityKey.entityName},EntityId=${entityKey.identifier.toString()}")
            if (domainInstance.class.canonicalName.equals(entityKey.entityName) &&
                    domainInstance.id.toString().equals(entityKey.identifier.toString())) {
                exists = true
            }
        }
        return exists
    }

    static {
        sessionFactory = sessionFactory ?: (sessionFactory = getBean(SessionFactory))
    }

    static <T> T getBean(Class<T> requiredType) {
        try {
            return Holders.applicationContext.getBean(requiredType)
        }
        catch (Exception e) {
            return null
        }
    }
}

Wednesday, August 3, 2016

Hibernate evict an persistent object from hibernate session

package com.pkm.evict_entity

import grails.util.Holders
import org.hibernate.SessionFactory

/**
 * Created by pritom on 3/08/2016.
 */
class EvictEntityInstance {
    public static void _evict(def domainInstance) {
        getBean(SessionFactory).currentSession.evict(domainInstance)
    }

    public static <T> T getBean(Class<T> requiredType) {
        try {
            return Holders.applicationContext.getBean(requiredType)
        }
        catch (Exception e) {
            return null
        }
    }
}