spring-mybatis,一级缓存分析。

597 阅读5分钟

先分析一下spring-mybatis整合之后,mapper的注入过程:

mapper注入过程
图片processOn地址

基于上图的分析,spring-mybatis 环境已完成初始化,现在开始分析 mapper 的具体执行情况。
代码执行到mapper的具体操作时

public User getUser(Integer id) {
        userMapper.getUser(1);
        personMapper.getUser(1);
        return null;
    }

方法的调用链如下: MapperProxy.invoke() ->
MapperMethod.execute() ->
SqlSessionTemplate.selectOne()

// Session接口的selectOne方法
// org.apache.ibatis.session.SqlSession#selectOne(java.lang.String)
@Override
public <T> T selectOne(String statement, Object parameter) {
  return this.sqlSessionProxy.selectOne(statement, parameter);
}

显然具体的执行逻辑交给了 sqlSessionTemplate 的 sqlSessionProxy 去完成。

// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
// 这个内部类就是sqlSessionProxy的invocationHandler
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 下面关注下这个getSqlSession()方法
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

看一下这个SqlSessionUtils的getSqlSession方法

// org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    
    // session由sqlSessionHolder持有,下面关注下怎么拿到的holder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }
// org.springframework.transaction.support.TransactionSynchronizationManager#getResource
@Nullable
	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}
// org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
@Nullable
	private static Object doGetResource(Object actualKey) {
		Map<Object, Object> map = resources.get();
		if (map == null) {
			return null;
		}
		Object value = map.get(actualKey);
		// Transparently remove ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			map.remove(actualKey);
			// Remove entire ThreadLocal if empty...
			if (map.isEmpty()) {
				resources.remove();
			}
			value = null;
		}
		return value;
	}

关注一下这个resources

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

第一次进来,这个resources是没有sqlSessionHolder的。 这时候就要openSession并registerSessionHolder了。

    session = sessionFactory.openSession(executorType);
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

看一下Utils的registerSessionHolder()方法。

// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    // 这个方法的返回值意味着是否开启了事务
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

  }
/**
	 * Return if transaction synchronization is active for the current thread.
	 * Can be called before register to avoid unnecessary instance creation.
	 * @see #registerSynchronization
	 */
	public static boolean isSynchronizationActive() {
		return (synchronizations.get() != null);
	}

如果开启了事务,就会去注册SessionHolder

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
// org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
// Bind the given resource for the given key to the current thread.
public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		// 这里将<DefaultSqlSessionFactory,SqlSessionHolder>添加了进了resource里面
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}
//org.springframework.transaction.support.TransactionSynchronizationManager#registerSynchronization
// Register a new transaction synchronization for the current thread.
public static void registerSynchronization(TransactionSynchronization synchronization)
			throws IllegalStateException {

		Assert.notNull(synchronization, "TransactionSynchronization must not be null");
		// static final ThreadLocal<Set<TransactionSynchronization>> synchronizations
		Set<TransactionSynchronization> synchs = synchronizations.get();
		if (synchs == null) {
			throw new IllegalStateException("Transaction synchronization is not active");
		}
		synchs.add(synchronization);
	}
// org.springframework.transaction.support.ResourceHolderSupport#released
// Decrease the reference count by one because the holder has been released
public void requested() {
		this.referenceCount++;
	}

如果没有开启事务,就不去registerSqlSessionHolder。

最后将sqlSession返回。回到invoke方法:

try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      }

这里判断一下是否开启了事务并且是同一个事务(猜的)。

// org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional
// Returns if the {@code SqlSession} passed as an argument is being managed by Spring
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    return (holder != null) && (holder.getSqlSession() == session);
  }

不是就直接commmit,否则就不commit。然后返回查询到的result结果集。 返回之前还要执行finally语句块:

finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }

spring与mybatis整合之后,sqlSession的开闭都是由spring负责了。 看下closeSqlSession:

// org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
      LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
      holder.released();
    } else {
      LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
      session.close();
    }
  }

如果当前sessionFactory对应的sessionHolder注册过了,则调用holder.release();否则直接调用session.close()进行关闭。

public void released() {
		this.referenceCount--;
	}

看到这里的close思路就很清晰了。mybatis的一级缓存是基于sqlSession的,与spirng整合之后,sqlSession的开闭都交给了spring控制。这里sqlSession的开闭方式受到了@Transactional注解的影响。

当添加了@Transactional注解之后:

  1. 执行到SqlSessionUtils的getSqlSession方法的时候,会先尝试从TransactionSynchronizationManager的resources里拿到此时的sqlSessionFactory对应的sqlSesisonHolder。
  2. 拿不到则创建新的SqlSession和holder注册进这个resources。 返回sqlSession之后(返回到SqlSessiongTemplate内部类的invoke逻辑),会执行被代理的select方法(session接口被代理的不同方法)。
  3. 拿到查询结果之后,因为添加了@Transactional注解而不会执行sqlSession的commit方法(此方法会将mybatis一级缓存内容放进二级缓存并清空一级缓存),而且在返回之前关闭sqlSession时也不会去直接调用sqlSession的close()方法,而是去调用holder的releaseed()方法去将holder的被引用数减一(this.referenceCount--)。

当没有添加@Transactional注解时:

  1. 执行到SqlSessionUtils的getSqlSession方法的时候,也会先尝试从TransactionSynchronizationManager的resources里拿到此时的sqlSessionFactory对应的sqlSesisonHolder(不过这时是肯定拿不到的),直接创建新的SqlSession并返回。
  2. 然后就是回到SqlSessionTemplate去执行被代理的select方法(session接口被代理的不同方法),并且直接执行sqlSession的commit()方法和close方法。

综上,在没有添加@Transactional注解时,sqlSession每次都是创建的新的而且用完就commit()并且close(),所以此时的一级缓存是失效的;当添加了@Transactional注解之后,创建的sqlSession都会包装成sqlSessionHolder注册到一个static ThreadLocal的resources里面,并且不去commit和close,事务内拿到的都是同一个sqlSession,所以此时的以及缓存时生效的。