Hibernate 一级缓存源码级

你是否遇到过这类情况:

case 1:

  1. 查询返回10条数据
  2. 其他程序修改数据
  3. 再次查询返回的数据并没有被修改

case 2:

  1. 查询返回5条数据
  2. 修改已有5条数据的部分字段,并新增5条数据。
  3. 再次查询,读取到10条数据,已被查询过的5条数据没有被更新,新查询到的5条数据的值是最新的。

没错,这些情况都是由于缓存导致的。如果你还完全不知道什么是一级缓存,不建议阅读本文。


太长不看版:

一. 一级缓存是Session级别的。
二. 判断Entity是否存在于缓存中是根据Identifier区分。
三. 当从DB query回结果,会根据结果中的数据生成Identifier与缓存中的Identifier比较。如果不存在则存入缓存,如果存在则无操作。
四. session.setCacheable(true),同一条重复的SQL完全从缓存中读取数据跳过DB。
五. session.setCacheable(false),执行(3.)中的逻辑。
六. 因此只能跳过读取DB,不能跳过读取缓存。
七. 缓存是可以被清理的。如:session.clear()session.evict()
八. 清理cache后再读取可获取最新数据。



这是第一次查询方法调用栈:
几乎所有的查询操作都会调用到list()方法,最后的addEntry()是将结果加到缓存中。


addEntry(Object, Status, Object[], Object, Serializable, Object, LockMode, boolean, EntityPersister, boolean, boolean):507, StatefulPersistenceContext {org.hibernate.engine}, StatefulPersistenceContext.java
postHydrate(EntityPersister, Serializable, Object[], Object, Object, LockMode, boolean, SessionImplementor):80, TwoPhaseLoad {org.hibernate.engine}, TwoPhaseLoad.java
loadFromResultSet(ResultSet, int, Object, String, EntityKey, String, LockMode, Loadable, SessionImplementor):1562, Loader {org.hibernate.loader}, Loader.java
instanceNotYetLoaded(ResultSet, int, Loadable, String, EntityKey, LockMode, EntityKey, Object, List, SessionImplementor):1455, Loader {org.hibernate.loader}, Loader.java
getRow(ResultSet, Loadable[], EntityKey[], Object, EntityKey, LockMode[], List, SessionImplementor):1355, Loader {org.hibernate.loader}, Loader.java
getRowFromResultSet(ResultSet, SessionImplementor, QueryParameters, LockMode[], EntityKey, List, EntityKey[], boolean):611, Loader {org.hibernate.loader}, Loader.java
doQuery(SessionImplementor, QueryParameters, boolean):829, Loader {org.hibernate.loader}, Loader.java
doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean):274, Loader {org.hibernate.loader}, Loader.java
doList(SessionImplementor, QueryParameters):2533, Loader {org.hibernate.loader}, Loader.java
listIgnoreQueryCache(SessionImplementor, QueryParameters):2276, Loader {org.hibernate.loader}, Loader.java
list(SessionImplementor, QueryParameters, Set, Type[]):2271, Loader {org.hibernate.loader}, Loader.java
list(SessionImplementor, QueryParameters):452, QueryLoader {org.hibernate.loader.hql}, QueryLoader.java
list(SessionImplementor, QueryParameters):363, QueryTranslatorImpl {org.hibernate.hql.ast}, QueryTranslatorImpl.java
performList(QueryParameters, SessionImplementor):196, HQLQueryPlan {org.hibernate.engine.query}, HQLQueryPlan.java
list(String, QueryParameters):1268, SessionImpl {org.hibernate.impl}, SessionImpl.java
list():102, QueryImpl {org.hibernate.impl}, QueryImpl.java

已经在代码注释中使用数字需要如(1. 2. 3. …)进行调用顺序的标识。

一、 一级缓存是Session级别的。

我们说的Session其实是一个接口org.hibernate.Session,它提供了一些与数据库交互的方法。
在这里插入图片描述
实际用到的是它的实现类SessionImpl,这里会根据生成的执行计划(HQLQueryPlan)调用对应的翻译器(QueryTranslatorImpl),而翻译器器是继承自(org.hibernate.loader.Loader),后续大量的操作都是在Loader完成的。
在这里插入图片描述

Loader的入口:
几乎所有的查询操作,最终都会通过list()的接口完成。
这里对配置的是否使用缓存选择不同的代码分支。

	// 1. 这是Loader最先被调用的方法,这里对是否读取缓存进行了判断
    protected List list(SessionImplementor session, QueryParameters queryParameters, Set querySpaces, Type[] resultTypes) throws HibernateException {
    // this.factory.getSettings().isQueryCacheEnabled() 是通过hibernate.cache.use_query_cache,这是二级缓存后续介绍
    // queryParameters.isCacheable()是通过session.setCacheable()确定
        boolean cacheable = this.factory.getSettings().isQueryCacheEnabled() && queryParameters.isCacheable();
        //这样通过cacheable,true:使用查询缓存,false:忽略缓存
        return cacheable ? this.listUsingQueryCache(session, queryParameters, querySpaces, resultTypes) : this.listIgnoreQueryCache(session, queryParameters);
    }

先看不使用缓存的情况listIgnoreQueryCache(),通过doList()完成工作

    private List listIgnoreQueryCache(SessionImplementor session, QueryParameters queryParameters) {
    // 2. 实际的工作是doList()完成的
        return this.getResultList(this.doList(session, queryParameters), queryParameters.getResultTransformer());
    }

接下来通过一些列参数的准备工作后(参考上面的调用栈),会调用到doQuery(),这里是真正的读取数据库,用到我们熟悉的JDBC的接口。
下面是doQuery()方法里的内容:

	PreparedStatement st = this.prepareQueryStatement(queryParameters, false, session);
	ResultSet rs = this.getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session);

这里是经典的JDBC操作,返回的rs就是查询DB返回的结果集了。
下面就是遍历结果集对每一条数据进行处理

	// rs.next()遍历结果
	for(count = 0; count < maxRows && rs.next(); ++count) {
       if (log.isTraceEnabled()) {
            log.debug("result set row: " + count);
        }
		// 3. 对rs结果进行处理
        Object result = this.getRowFromResultSet(rs, session, queryParameters, lockModesArray, optionalObjectKey, hydratedObjects, keys, returnProxies);
        // 处理后的结果加到返回集
        results.add(result);
        if (createSubselects) {
            subselectResultKeys.add(keys);
            keys = new EntityKey[entitySpan];
        }
    }
    // 13. 非常重要!!这里包含了很多工作
    // 真正初始化对象的地方,并且包含interceptor的onLoad回调.
    // 通过深拷贝将从缓存复制对象到返回结果集,执行之前results中的对象只是空壳,执行后正真deepCopy。
    // 将对象管理状态修改为Status.MANAGED
    this.initializeEntitiesAndCollections(hydratedObjects, rs, session, queryParameters.isReadOnly(session));
    return results;

二、判断Entity是否存在于缓存中是根据Identifier区分。

三、 当从DB query回结果,会根据结果中的数据生成Identifier与缓存中的Identifier比较。如果不存在则存入缓存,如果存在则无操作。

extractKeysFromResultSet()是用于通过Identifier自动抽取key的方法,会将结果填充到keys这个集合。

    private Object getRowFromResultSet(ResultSet resultSet, SessionImplementor session, QueryParameters queryParameters, LockMode[] lockModesArray, EntityKey optionalObjectKey, List hydratedObjects, EntityKey[] keys, boolean returnProxies) throws SQLException, HibernateException {
        Loadable[] persisters = this.getEntityPersisters();
        int entitySpan = persisters.length;
        // 4. 从结果集抽取keys,这个命名非常好,见名知意。这个方法的作用就是从DB返回的数据中提取对象的identifier放到keys这个集合里面,这个key的作用就是用于与缓存中的对象比较。相同的数据key是相同的。
        this.extractKeysFromResultSet(persisters, queryParameters, resultSet, session, keys, lockModesArray, hydratedObjects);
        this.registerNonExists(keys, persisters, session);
        // 5. getRow()就用于是提取数据并返回,下一个代码块我们看里面实现
        Object[] row = this.getRow(resultSet, persisters, keys, queryParameters.getOptionalObject(), optionalObjectKey, lockModesArray, hydratedObjects, session);
        this.readCollectionElements(row, resultSet, session);
        if (returnProxies) {
            for(int i = 0; i < entitySpan; ++i) {
                Object entity = row[i];
                Object proxy = session.getPersistenceContext().proxyFor(persisters[i], keys[i], entity);
                if (entity != proxy) {
                    ((HibernateProxy)proxy).getHibernateLazyInitializer().setImplementation(entity);
                    row[i] = proxy;
                }
            }
        }

        this.applyPostLoadLocks(row, lockModesArray, session);
        return this.getResultColumnOrRow(row, queryParameters.getResultTransformer(), resultSet, session);
    }
    private Object[] getRow(ResultSet rs, Loadable[] persisters, EntityKey[] keys, Object optionalObject, EntityKey optionalObjectKey, LockMode[] lockModes, List hydratedObjects, SessionImplementor session) throws HibernateException, SQLException {
        int cols = persisters.length;
        EntityAliases[] descriptors = this.getEntityAliases();

        Object[] rowResults = new Object[cols];

        for(int i = 0; i < cols; ++i) {
            Object object = null;
            EntityKey key = keys[i];
            if (keys[i] != null) {
            	// 6. hibernate提供了很多回调配置以interceptor的接口,这里就已经去查缓存,并检查interceptor.
            	// 7. 注意:这里通过session调用,故访问一级缓存
                object = session.getEntityUsingInterceptor(key);
                // 缓存中不存在对象会返回null
                if (object != null) {
                	// 14. 实例已存在 >> 处理已缓存的情况
                    this.instanceAlreadyLoaded(rs, i, persisters[i], key, object, lockModes[i], session);
                } else {
	                // 8. 实例未被加载过,这里跟进此方法
                    object = this.instanceNotYetLoaded(rs, i, persisters[i], descriptors[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, optionalObject, hydratedObjects, session);
                }
            }
            rowResults[i] = object;
        }
        return rowResults;
    }

六、 因此只能跳过读取DB,不能跳过读取缓存。

这里总是会去缓存去一次数据

    public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException {
        this.errorIfClosed();
        // persistenceContext是缓存上对象了,通过前面提到的key,从缓存中获得对象。
        Object result = this.persistenceContext.getEntity(key);
        if (result == null) {
	        // 调用配置interceptor的getEntity()方法。没有配置interceptor会有一个默认的EmptyInterceptor,方法实现返回null。也就是说,如果缓存里没有对象,给你个回调机会可以自己构造一个对象作为结果返回。
            Object newObject = this.interceptor.getEntity(key.getEntityName(), key.getIdentifier());
            if (newObject != null) {
                this.lock(newObject, LockMode.NONE);
            }
            // 缓存中没有get到,那么这里会返回null,请返回上一个代码块继续。
            return newObject;
        } else {
            return result;
        }
    }

如果缓存中没有对应的Identifier,那么:

    private Object instanceNotYetLoaded(ResultSet rs, int i, Loadable persister, String rowIdAlias, EntityKey key, LockMode lockMode, EntityKey optionalObjectKey, Object optionalObject, List hydratedObjects, SessionImplementor session) throws HibernateException, SQLException {
	    //得到POJO全类名
        String instanceClass = this.getInstanceClass(rs, i, persister, key.getIdentifier(), session);
        Object object;
        if (optionalObjectKey != null && key.equals(optionalObjectKey)) {
            object = optionalObject;
        } else {
        	// 可以理解为反射一个实例,将identifier传进去,对象的其他属性没有被赋值。
            object = session.instantiate(instanceClass, key.getIdentifier());
        }

        LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode;
        // 9. 最终的加载数据的方法
        this.loadFromResultSet(rs, i, object, instanceClass, key, rowIdAlias, acquiredLockMode, persister, session);
        hydratedObjects.add(object);
        return object;
    }
    private void loadFromResultSet(ResultSet rs, int i, Object object, String instanceEntityName, EntityKey key, String rowIdAlias, LockMode lockMode, Loadable rootPersister, SessionImplementor session) throws SQLException, HibernateException {
        Serializable id = key.getIdentifier();
        Loadable persister = (Loadable)this.getFactory().getEntityPersister(instanceEntityName);

        boolean eagerPropertyFetch = this.isEagerPropertyFetchEnabled(i);
        // 代码非常清晰,2阶段加载。
        // 10. 添加未初始化的实体。object是上面提到的一个仅带有identifier的空对象,把它加到缓存。
        TwoPhaseLoad.addUninitializedEntity(key, object, persister, lockMode, !eagerPropertyFetch, session);
        String[][] cols = persister == rootPersister ? this.getEntityAliases()[i].getSuffixedPropertyAliases() : this.getEntityAliases()[i].getSuffixedPropertyAliases(persister);
        // 11. 这里处理数据,提取真实的数据到values
        Object[] values = persister.hydrate(rs, id, object, rootPersister, cols, eagerPropertyFetch, session);
        Object rowId = persister.hasRowId() ? rs.getObject(rowIdAlias) : null;
        AssociationType[] ownerAssociationTypes = this.getOwnerAssociationTypes();
        if (ownerAssociationTypes != null && ownerAssociationTypes[i] != null) {
            String ukName = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName();
            if (ukName != null) {
                int index = ((UniqueKeyLoadable)persister).getPropertyIndex(ukName);
                Type type = persister.getPropertyTypes()[index];
                EntityUniqueKey euk = new EntityUniqueKey(rootPersister.getEntityName(), ukName, type.semiResolve(values[index], session, object), type, session.getEntityMode(), session.getFactory());
                session.getPersistenceContext().addEntity(euk, object);
            }
        }
		// 12. 2阶段加载,合成。下一个代码块中展示将对象存入缓存
        TwoPhaseLoad.postHydrate(persister, id, values, rowId, object, lockMode, !eagerPropertyFetch, session);
    }

真正添加到缓存的地方

    public static void postHydrate(EntityPersister persister, Serializable id, Object[] values, Object rowId, Object object, LockMode lockMode, boolean lazyPropertiesAreUnfetched, SessionImplementor session) throws HibernateException {
        Object version = Versioning.getVersion(values, persister);
		// 这里将对象存入缓存
        session.getPersistenceContext().addEntry(object, Status.LOADING, values, rowId, id, version, lockMode, true, persister, false, lazyPropertiesAreUnfetched);
        if (log.isTraceEnabled() && version != null) {
            String versionStr = persister.isVersioned() ? persister.getVersionType().toLoggableString(version, session.getFactory()) : "null";
            log.trace("Version: " + versionStr);
        }
    }
// 处理结束开始返回,向上寻找第13步操作后,数据就处理就结束了。

对于缓存中不存在的情况走到这里就差不多了,还有另外一种情况就是

四、 session.setCacheable(true),同一条重复的SQL完全从缓存中读取数据跳过DB。

如果设置不用缓存,2种方式:不论怎么,都会用到一级缓存

  1. 通过代码对query或则session设置setCacheable(false);
  2. 通过hibernate的xml配置<property name="hibernate.cache.use_query_cache">true</property>

那么会执行第14步,instanceAlreadyLoaded()
可以通过设置LockMode指定进入版本检查(default is LockMode.NONE),一旦版本不同会抛出异常 Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)!!!

    private void instanceAlreadyLoaded(ResultSet rs, int i, Loadable persister, EntityKey key, Object object, LockMode lockMode, SessionImplementor session) throws HibernateException, SQLException {
        if (!persister.isInstance(object, session.getEntityMode())) {
            throw new WrongClassException("loaded object was of wrong class " + object.getClass(), key.getIdentifier(), persister.getEntityName());
        } else {
        	// 15. 默认的LockMode是None,也就不会进入if再处理任何数据。
            if (LockMode.NONE != lockMode && this.upgradeLocks()) {
                boolean isVersionCheckNeeded = persister.isVersioned() && session.getPersistenceContext().getEntry(object).getLockMode().lessThan(lockMode);
                if (isVersionCheckNeeded) {
	                // 这里的checkVersion,会检查版本标识(通过POJO注解或XML中定义的字段),一旦发现版本不一致会抛出异常。
	                // Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
                    this.checkVersion(i, persister, key.getIdentifier(), object, rs, session);
                    session.getPersistenceContext().getEntry(object).setLockMode(lockMode);
                }
            }

        }
    }

处理结束返回再看getRow()

    private Object[] getRow(ResultSet rs, Loadable[] persisters, EntityKey[] keys, Object optionalObject, EntityKey optionalObjectKey, LockMode[] lockModes, List hydratedObjects, SessionImplementor session) throws HibernateException, SQLException {
        int cols = persisters.length;
        EntityAliases[] descriptors = this.getEntityAliases();

        Object[] rowResults = new Object[cols];

        for(int i = 0; i < cols; ++i) {
            Object object = null;
            EntityKey key = keys[i];
            if (keys[i] != null) {
            	// hibernate提供了很多回调配置以interceptor的接口,这里就已经去查缓存,并检查interceptor.
            	// 7. 注意:这里通过session调用,故访问一级缓存
                object = session.getEntityUsingInterceptor(key);
                // 缓存中不存在对象会返回null
                if (object != null) {
                	// 14. 实例已存在 >> 处理已缓存的情况
                    this.instanceAlreadyLoaded(rs, i, persisters[i], key, object, lockModes[i], session);
                } else {
	                // 8. 实例未被加载过,这里跟进此方法
                    object = this.instanceNotYetLoaded(rs, i, persisters[i], descriptors[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, optionalObject, hydratedObjects, session);
                }
            }
            // 16. 由于此时进入的instanceAlreadyLoaded,没有任何处理,故object就是来自于缓存中的对象,因此从DB中得到最新的数据没有被返回,而是返回了缓存中的对象。
            rowResults[i] = object;
        }
        return rowResults;
    }

由于此时进入的instanceAlreadyLoaded(),没有对返回结果有任何处理,故object就是来自于缓存中的对象,因此从DB中得到最新的数据没有被返回,而是返回了缓存中的对象。此时复现了开篇提到的异常case,即:查询数据库却没有返回最新数据,而是返回缓存中的数据。

    private void instanceAlreadyLoaded(ResultSet rs, int i, Loadable persister, EntityKey key, Object object, LockMode lockMode, SessionImplementor session) throws HibernateException, SQLException {
       if (!persister.isInstance(object, session.getEntityMode())) {
           throw new WrongClassException("loaded object was of wrong class " + object.getClass(), key.getIdentifier(), persister.getEntityName());
       } else {
           if (LockMode.NONE != lockMode && this.upgradeLocks()) {
               boolean isVersionCheckNeeded = persister.isVersioned() && session.getPersistenceContext().getEntry(object).getLockMode().lessThan(lockMode);
               if (isVersionCheckNeeded) {
               	// checkVersion中会比较版本,如不一致说明出现脏数据,会抛出异常
                   this.checkVersion(i, persister, key.getIdentifier(), object, rs, session);
                   session.getPersistenceContext().getEntry(object).setLockMode(lockMode);
               }
           }
       }
   }

七、 缓存是可以被清理的。如:session.clear()session.evict()

对于缓存管理,hibernate其提供了2个删除缓存的接口。

  1. clear() 清空缓存
  2. evict(Object o)清理单个缓存。注意虽然参数类型是Object,但只能传入单个对象。也就是说,如果你的查询返回的是一个集合,那么你需要遍历集合中的每个元素传给evcit()进行清理。
    完成清理之后再次进行数据读取就不会出现开篇提到的问题了。

这里有很多内容没有提及,比如事务隔离级别,二级缓存,缓存托管机制,LockMode作用等等。有机会再更新吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值