Hibernate的Session脏检查问题

Hibernate的Session是怎么判断一个对象是新建还是一个dirty,其原理是怎样的

When session.load(obj), a copy of the loaded object (a snapshot) is made (see SessionImpl.initializeEntity() ), when session.flush(), all objects in session are compared to their saved snapshots to see if their property values have changed (dirty), if yes then that object's dirty properties will be updated in SQL (see SessionImpl.flushEntity()).

你可以研究下 HibernateSessionFactory

HibernateSessionFactory 中 有一段这样的 代码

/**
* Returns the ThreadLocal Session instance. Lazy initialize
* the SessionFactory if needed.
*
* @return Session
* @throws HibernateException
*/
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();

    if (session == null || !session.isOpen()) {
        if (sessionFactory == null) {
            rebuildSessionFactory();
        }
        session = (sessionFactory != null) ? sessionFactory.openSession()
                : null;
        threadLocal.set(session);
    }

    return session;
}

我用的是 myeclipse 自动生成的,稍微修改了下,全部如下:

import java.util.Properties;
import java.util.ResourceBundle;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;

import com.konolabs.lites.hibernate.HibernateConstant;

/**

  • Configures and provides access to Hibernate sessions, tied to the
  • current thread of execution. Follows the Thread Local Session
  • pattern, see {@link http://hibernate.org/42.html }.
    */
    public class HibernateSessionFactory {

    /**

    • Location of hibernate.cfg.xml file.
    • Location should be on the classpath as Hibernate uses
    • #resourceAsStream style lookup for its configuration file.
    • The default classpath location of the hibernate config file is
    • in the default package. Use #setConfigFile() to update
    • the location of the configuration file for the current session.
      */ private static String CONFIG_FILE_LOCATION = HibernateConstant.CONFIG_FILE_PATH; private static final ThreadLocal threadLocal = new ThreadLocal(); private static Configuration configuration = new Configuration(); private static org.hibernate.SessionFactory sessionFactory; private static String configFile = CONFIG_FILE_LOCATION;

    /** properties. /
    private static Properties properties;
    /
    * webApp. */
    private static String webApp = "webapp";

    /** commonProxool. */
    private static String commonProxool = "common.proxool";

    /** hibernateProxoolKey. */
    private static String hibernateProxoolKey = "hibernate.proxool.xml";

    static {
    try {
    // webapp properties read
    ResourceBundle bundleCommon = ResourceBundle.getBundle(webApp);
    // get proxool_*.xml path
    String proxoolPath = bundleCommon.getString(commonProxool);

        Properties extraProperties = new Properties();
        extraProperties.setProperty(hibernateProxoolKey, proxoolPath);
        properties = extraProperties;
        configuration.addProperties(extraProperties);
        configuration.configure(configFile);
        sessionFactory = configuration.buildSessionFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    }

    /**

    • HibernateSessionFactory. */ private HibernateSessionFactory() { }

    /**

    • Returns the ThreadLocal Session instance. Lazy initialize
    • the SessionFactory if needed. *
    • @return Session
    • @throws HibernateException
      */
      public static Session getSession() throws HibernateException {
      Session session = (Session) threadLocal.get();

      if (session == null || !session.isOpen()) {
      if (sessionFactory == null) {
      rebuildSessionFactory();
      }
      session = (sessionFactory != null) ? sessionFactory.openSession()
      : null;
      threadLocal.set(session);
      }

      return session;
      }

    /**

    • Rebuild hibernate session factory. * */ public static void rebuildSessionFactory() { try { configuration.addProperties(properties); configuration.configure(configFile); sessionFactory = configuration.buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); } }

    /**

    • Close the single hibernate session instance. *
    • @throws HibernateException
      */
      public static void closeSession() throws HibernateException {
      Session session = (Session) threadLocal.get();
      threadLocal.set(null);

      if (session != null) {
      session.close();
      }
      }

    /**

    • return session factory.
    • @return SessionFactory */ public static org.hibernate.SessionFactory getSessionFactory() { return sessionFactory; }

    /**

    • return session factory.
    • @param inConfigFile String
    • session factory will be rebuilded in the next call */ public static void setConfigFile(String inConfigFile) { HibernateSessionFactory.configFile = inConfigFile; sessionFactory = null; }

    /**

    • return hibernate configuration.
    • @return Configuration */ public static Configuration getConfiguration() { return configuration; } }

Hibernate does/can use bytecode generation (CGLIB) so that it knows a field is dirty as soon as you call the setter (or even assign to the field afaict).

关于 CGLIB,参考: http://www.iteye.com/wiki/topic/263841

This immediately marks that field/object as dirty, but doesn't reduce the number of objects that need to be dirty-checked during flush. All it does is impact the implementation of org.hibernate.engine.EntityEntry.requiresDirtyCheck(). It still does a field-by-field comparison to check for dirtiness.

I say the above based on a recent trawl through the source code (3.2.6GA), with whatever credibility that adds. Points of interest are:

SessionImpl.flush() triggers an onFlush() event.
SessionImpl.list() calls autoFlushIfRequired() which triggers an onAutoFlush() event. (on the tables-of-interest). That is, queries can invoke a flush. Interestingly, no flush occurs if there is no transaction.
Both those events eventually end up in AbstractFlushingEventListener.flushEverythingToExecutions(), which ends up (amongst other interesting locations) at flushEntities().
That loops over every entity in the session (source.getPersistenceContext().getEntityEntries()) calling DefaultFlushEntityEventListener.onFlushEntity().
You eventually end up at dirtyCheck(). That method does make some optimizations wrt to CGLIB dirty flags, but we've still ended up looping over every entity.