简述spring的事务管理机制

重要的概念:

理解mybatis和数据库连接池中间件如hikariCP的关系

简单来说,java的JDBC为与数据库交互操作提供了一套标准接口,常见的datasource,connection,preparedstatement等。hikariCP这类的框架负责实现这些接口。

但是单纯使用JDBC操作数据库不方便,效率也不高。所以mybatis是将JDBC操作给封装了。两者以datasource作为桥梁。mybatis底层拿到hikariCP的datasource执行JDBC标准的数据库操作,在mybatis源码里datasource作为成员变量存储在

org.apache.ibatis.mapping.Environment

这个类的对象里。

sqlSession

源码

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}

实现 

概述

sqlSession是mybatis的一个sql会话,对外提供SQL操作的接口,但是真正执行的是其成员变量executor,后面会说(当使用mybatis等框架时,通过是调用mapper接口内定义的方法,但实际是最终还是会调用sqlSession来执行sql)。由于spring是加载bean是单例的,所以每执行一个sql会话,默认情况下都会创建一个sqlSession,以此来保证了sqlSession的线程安全。但是当启动一个事务的时候,或者说多个SQL在同一个事务内执行的时候,sqlSession会被复用。下面是具体一条SQL是如何被执行的。

以常用实现sqlSessionTemplate为例:

  @Override
  public <T> T selectOne(String statement) {
    return this.sqlSessionProxy.selectOne(statement);
  }

可以看到使用代理sqlSessionProxy执行。下面是代理的初始化:

private final SqlSession sqlSessionProxy;

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

通过动态代理,去执行sqlSession接口里的方法,这里涉及了动态代理的知识,就不多说了,总之执行时会调用SqlSessionInterceptor的involve方法。下面是源码

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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);
        }
      }
    }

这里实际上是个拦截器,与spring的AOP原理类似,在调用对应的method(method.invoke)前后,加上其他的逻辑。大概就是先获取另外一个sqlSession的实现,去执行method,后面看这个SQL是不是开启了一个事务,如果不是,就直接commit,如果是,提交操作就由这个事务完成,最后再把sqlSession关掉。

第一步就是通过getSqlSession获取sqlSession的另一个实现,去真正执行method。进去看源码

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    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;
  }

逻辑是尝试获取sqlSessionHolder,再由sqlSessionHolder获取sqlSession,获取到了直接返回,这里就是之前说的在同一事务下,复用sqlSession的原理。sqlSessionHolder获取不到则通过openSession,新建一个sqlSession,再通过registerSessionHolder把刚刚新建的sqlSession放进一个sqlSessionHolder里,这样下一个sql执行的时候,就能直接拿到sqlSession了,但是注意这个方法其实不一定会生效,只有之前所说的同一个事务下,才会,后面会解释。下面一一看源码:

getResource

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

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;
	}



	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;
	}

可以看到,核心逻辑就是从当前线程的threadLocal(resource变量)里面以当前的sessionFactory为key,获取sqlSessionHolder ,获取不到就返回null。返回null,就代表了当前sql就是独立的一个事务,不和其他sql共享会话,所以可以理解为一个事务对应一个SqlSession

再看后面的sessionHolder

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值