ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring中每次访问数据库都要创建SqlSession吗? [打印本页]

作者: 张裕    时间: 3 天前
标题: Spring中每次访问数据库都要创建SqlSession吗?
参考:https://www.zhihu.com/question/57568941/answer/2062846449
先来说结论:

一、SqlSession是什么

SqlSession是Mybatis工作的最顶层API会话接口,全部的数据库操作都经由它来实现。由于它是一个会话,即SqlSession对应这一次数据库会话,不是永世存活的,因此每次访问数据库时都必要创建它。
并且它不是线程安全的,如果将一个SqlSession搞成单例形式,或者静态域和实例变量的形式,都会导致SqlSession出现事务问题,这也就是为什么同一事务中的多个访问数据库哀求会共用一个SqlSession会话,而不同事务则会创建不同SqlSession的原因。
SqlSession的创建过程:
每次创建一个SqlSession会话,都会伴随创建一个专属SqlSession的毗连管理对象,如果SqlSession共享,就会出现事务问题。
接下来的内容比较干,感爱好的可以接着看!!!
二、源码分析

1)mybatis获取Mapper流程

先回首以下传统mybatis创建Mapper接口的署理对象流畅如下:
  1. //sqlSessionFactory --> sqlSession
  2. public class MybatisUtils {
  3.     static SqlSessionFactory sqlSessionFactory = null;
  4.     static {
  5.         try {
  6.             String resource = "mybatis-config.xml";
  7.             InputStream inputStream = Resources.getResourceAsStream(resource);
  8.             // 1.build方法会解析xml文件,包括我们写的mapper接口的xml文件,最终会把解析的信息封装到configuration对象中,
  9.             // 特别是我们xml文件中的sql和相关信息都会被封装成一个个的MappedStatement对象存进一个Map中,key为全限定类名+方法名,value为MappedStatement对象,
  10.             // 然后创建一个持有configuration引用的工厂对象返回,这里面就不展开分析了
  11.             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  12.         } catch (IOException e) {
  13.             e.printStackTrace();
  14.         }
  15.     }
  16.     // 2.这里会创建一个持有configuration对象引用的DefaultSqlSession对象返回,并且Executor对象也是在这一步创建的,提供了在数据库执行 SQL 命令所需的所有方法,这里面不展开分析
  17.     public static SqlSession getSqlSession(){
  18.         return sqlSessionFactory.openSession();
  19.     }
  20. }
复制代码
使用:
  1.         //1.获取SqlSession对象
  2.   SqlSession sqlSession = MybatisUtils.getSqlSession();
  3.   //2.获取代理对象,执行SQL
  4.   UserDao userDao = sqlSession.getMapper(UserDao.class);
  5.   List<User> userList = userDao.getUserList();
  6.   for (User user : userList) {
  7.       System.out.println(user);
  8.   }
  9.   //关闭sqlSession
  10.   sqlSession.close();
复制代码
  1. @Override
  2. public <T> T getMapper(Class<T> type) {
  3.                 return configuration.getMapper(type, this);
  4. }
复制代码
  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2.                 // 1.knownMappers会在解析mapper接口的xml文件时设置,key为接口的class对象,value为持有接口字节码对象引用的MapperProxyFactory对象
  3.     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  4.     if (mapperProxyFactory == null) {
  5.       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  6.     }
  7.     try {
  8.       // 2.创建代理对象逻辑
  9.       return mapperProxyFactory.newInstance(sqlSession);
  10.     } catch (Exception e) {
  11.       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  12.     }
  13.   }
复制代码
  1. public class MapperProxyFactory<T> {
  2.   private final Class<T> mapperInterface;
  3.   private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  4.   public MapperProxyFactory(Class<T> mapperInterface) {
  5.     this.mapperInterface = mapperInterface;
  6.   }
  7.   public Class<T> getMapperInterface() {
  8.     return mapperInterface;
  9.   }
  10.   public Map<Method, MapperMethod> getMethodCache() {
  11.     return methodCache;
  12.   }
  13.   @SuppressWarnings("unchecked")
  14.   protected T newInstance(MapperProxy<T> mapperProxy) {
  15.     // 3.用JDK的方式去创建一个代理了mapper接口的代理对象返回,然后可以拿这个对象来执行增删改查查方法了,具体逻辑是现在MapperProxy中
  16.     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  17.   }
  18.   public T newInstance(SqlSession sqlSession) {
  19.     // 1.创建MapperProxy对象,它持有sqlSession对象、接口字节码对象引用,并且它实现了InvocationHandler接口,这是动态代理的关键
  20.     final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  21.     // 2.创建mapper的代理对象
  22.     return newInstance(mapperProxy);
  23.   }
  24. }
复制代码
  1.   private final Map<Method, MapperMethod> methodCache;
  2.   
  3.   @Override
  4.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  5.     try {
  6.       // 1.如果是父类Object的方法就直接反射调用
  7.       if (Object.class.equals(method.getDeclaringClass())) {
  8.         return method.invoke(this, args);
  9.       } else if (method.isDefault()) {
  10.                      // 2.如果是接口的默认方法,则调用invokeDefaultMethod方法
  11.         return invokeDefaultMethod(proxy, method, args);
  12.       }
  13.     } catch (Throwable t) {
  14.       throw ExceptionUtil.unwrapThrowable(t);
  15.     }
  16.     // 3.我们自己mapper接口定义的方法,会接着调用MapperMethod#execute
  17.     final MapperMethod mapperMethod = cachedMapperMethod(method);
  18.     return mapperMethod.execute(sqlSession, args);
  19.   }
  20.   
  21.   // 4.如果mapperMethod 对象存在就不创建了,直接从缓存取
  22.   private MapperMethod cachedMapperMethod(Method method) {
  23.           // MapperMethod对象持有mapper接口字节码对象、要执行的目标方法对象、configuration对象引用
  24.     return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  25.   }
复制代码
这内里不具体展开了,大体逻辑是:
1)根据获取的MappedStatement对象的标签类型(对应xml文件的增删改查查标签:SELECT、DELETE、UPDATE、INSERT)来执行DefaultSqlSession对象对应的增删改查查方法
2)然后从configuration对象中根据全限定类名+方法名去获取一个MappedStatement对象
3)接着调用对应Executor对象的方法,并把MappedStatement对象传进去,Executor对象内里封装了JDBC的逻辑,以查询为例,大致逻辑为:


回首了mybatis执行的大致原理,都是依靠DefaultSqlSession的方法,那引入了spring为什么就不必要我们手动创建sqlSession了呢,接下来接着分析
2)Spring创建Mapper接口的署理对象流程

  1. // 省略其他代码...
  2. public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  3.   private Class<T> mapperInterface;
  4.   private boolean addToConfig = true;
  5.   public MapperFactoryBean() {
  6.     // intentionally empty
  7.   }
  8.   // 1.Mapper注解所在接口的字节码对象
  9.   public MapperFactoryBean(Class<T> mapperInterface) {
  10.     this.mapperInterface = mapperInterface;
  11.   }
  12.   @Override
  13.   protected void checkDaoConfig() {
  14.     super.checkDaoConfig();
  15.     notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  16.     Configuration configuration = getSqlSession().getConfiguration();
  17.     if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  18.       try {
  19.         configuration.addMapper(this.mapperInterface);
  20.       } catch (Exception e) {
  21.         logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
  22.         throw new IllegalArgumentException(e);
  23.       } finally {
  24.         ErrorContext.instance().reset();
  25.       }
  26.     }
  27.   }
  28.   
  29.   // 2.注册真正的Mapper对象
  30.   @Override
  31.   public T getObject() throws Exception {
  32.     return getSqlSession().getMapper(this.mapperInterface);
  33.   }
  34.   @Override
  35.   public Class<T> getObjectType() {
  36.     return this.mapperInterface;
  37.   }
  38.   @Override
  39.   public boolean isSingleton() {
  40.     return true;
  41.   }
  42.   public Class<T> getMapperInterface() {
  43.     return mapperInterface;
  44.   }
  45. }
复制代码
  1. @Override
  2. public T getObject() throws Exception {
  3.   return getSqlSession().getMapper(this.mapperInterface);
  4. }
复制代码
  1. public abstract class SqlSessionDaoSupport extends DaoSupport {
  2.   private SqlSessionTemplate sqlSessionTemplate;
  3.   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  4.     // 2.如果sqlSessionTemplate为空,则创建该对象
  5.     if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
  6.       this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
  7.     }
  8.   }
  9.   // 3.创建SqlSessionTemplate对象,并把sqlSessionFactory对象传进去(持有Mapper.xml文件解析后的数据)
  10.   @SuppressWarnings("WeakerAccess")
  11.   protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  12.     return new SqlSessionTemplate(sqlSessionFactory);
  13.   }
  14.   public final SqlSessionFactory getSqlSessionFactory() {
  15.     return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  16.   }
  17.   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
  18.     this.sqlSessionTemplate = sqlSessionTemplate;
  19.   }
  20.   
  21.   // 1.获取SqlSessionTemplate对象,它实现了SqlSession接口
  22.   public SqlSession getSqlSession() {
  23.     return this.sqlSessionTemplate;
  24.   }
  25.   public SqlSessionTemplate getSqlSessionTemplate() {
  26.     return this.sqlSessionTemplate;
  27.   }
  28.   @Override
  29.   protected void checkDaoConfig() {
  30.     notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  31.   }
  32. }
复制代码
可以看到,getSqlSession方法最终会返回一个已经创建好的SqlSessionTemplate对象,它底层实现了SqlSession接口,并且每个MapperFactoryBean对象都会持有同一个SqlSessionTemplate 对象,因为它们都继承了同一个抽象父类SqlSessionDaoSupport 的sqlSessionTemplate字段
打断点发现一级缓存中的两个MapperFactoryBean对象确实持有雷同的SqlSessionTemplate 引用,如下所示:


  1. // 省略其他代码...
  2. public class SqlSessionTemplate implements SqlSession, DisposableBean {
  3.         public <T> T getMapper(Class<T> type) {
  4.             // 2.关键部分:创建一个代理Mapper接口的对象返回
  5.             return getConfiguration().getMapper(type, this);
  6.         }
  7.        
  8.         // 1.获取创建时传进来的sqlSessionFactory对象中的Configuration对象
  9.         @Override
  10.         public Configuration getConfiguration() {
  11.           return this.sqlSessionFactory.getConfiguration();
  12.         }
  13.        
  14. }
复制代码
在创建署理对象时,关键在于这个this引用是当前的SqlSessionTemplate对象,在前面的mybatis获取Mapper流程中分析了getMapper方法的逻辑,这里不在展开分析。
总之SqlSessionTemplate对象最终会被MapperProxy对象所持有,后续调用署理对象的方法时,都会由SqlSessionTemplate对象的方法来处理,所以我们引入Spring之后,会自动创建一个SqlSessionTemplate对象,由该对象代替mybatis手动创建的DefaultSqlSession来处理我们的增删改查查方法。
小结:
为什么引入Spring就不用手动去创建SqlSession对象了?
因为在注册MapperFactoryBean时,都会调用它的getObject方法,内里会返回一个实现了SqlSession接口的SqlSessionTemplate 对象,并且由会调用它的getMapper方法来获代替理Mapper接口的对象,其中实现了InvocationHandler 接口的MapperProxy 对象会持有SqlSessionTemplate 对象引用,最终调用署理对象的方法时,都会颠末MapperProxy 的invoke方法来处理,具体是由SqlSessionTemplate 对象来处理的。
3)MapperFactoryBean#getObject调用时机


  1. protected Object getObjectForBeanInstance(
  2.                         Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
  3.                 if (BeanFactoryUtils.isFactoryDereference(name)) {
  4.                         if (beanInstance instanceof NullBean) {
  5.                                 return beanInstance;
  6.                         }
  7.                         if (!(beanInstance instanceof FactoryBean)) {
  8.                                 throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
  9.                         }
  10.                         if (mbd != null) {
  11.                                 mbd.isFactoryBean = true;
  12.                         }
  13.                         return beanInstance;
  14.                 }
  15.                
  16.                 // 1.不是工厂Bean直接返回
  17.                 if (!(beanInstance instanceof FactoryBean)) {
  18.                         return beanInstance;
  19.                 }
  20.                 Object object = null;
  21.                 if (mbd != null) {
  22.                         mbd.isFactoryBean = true;
  23.                 }
  24.                 else {
  25.                         // 2.从缓存中获取代理Bean对象
  26.                         object = getCachedObjectForFactoryBean(beanName);
  27.                 }
  28.                 // 3.缓存中获取不到,说明第一次获取
  29.                 if (object == null) {
  30.                         FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
  31.                         // Caches object obtained from FactoryBean if it is a singleton.
  32.                         if (mbd == null && containsBeanDefinition(beanName)) {
  33.                                 mbd = getMergedLocalBeanDefinition(beanName);
  34.                         }
  35.                         boolean synthetic = (mbd != null && mbd.isSynthetic());
  36.                         // 4.里面会调用MapperFactoryBean#getObject方法
  37.                         object = getObjectFromFactoryBean(factory, beanName, !synthetic);
  38.                 }
  39.                 return object;
  40.         }
  41.        
  42.        
  43.         // 从缓存中获取代理Bean
  44.         @Nullable
  45.         protected Object getCachedObjectForFactoryBean(String beanName) {
  46.                 return this.factoryBeanObjectCache.get(beanName);
  47.         }
  48.        
  49.         // 缓存代理Bean
  50.         private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
复制代码
  1. protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
  2.     // 1.判断是不是单例,一级缓存中是否存在该工厂Bean对象,很显然是有的
  3.                 if (factory.isSingleton() && containsSingleton(beanName)) {
  4.                         synchronized (getSingletonMutex()) {
  5.                                 // 2.再次从缓存中获取代理Bean
  6.                                 Object object = this.factoryBeanObjectCache.get(beanName);
  7.                                 if (object == null) {
  8.                                   // 3.缓存还是没有,这下才回去调用MapperFactoryBean#getObject方法获取代理Bean对象
  9.                                         object = doGetObjectFromFactoryBean(factory, beanName);
  10.                                        
  11.                                         Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
  12.                                         if (alreadyThere != null) {
  13.                                                 object = alreadyThere;
  14.                                         }
  15.                                         else {
  16.                                                 if (shouldPostProcess) {
  17.                                                         if (isSingletonCurrentlyInCreation(beanName)) {
  18.                                                                 // Temporarily return non-post-processed object, not storing it yet..
  19.                                                                 return object;
  20.                                                         }
  21.                                                         beforeSingletonCreation(beanName);
  22.                                                         try {
  23.                                                           // 4.这个方法最终会遍历所有的BeanPostProcessor,尝试执行postProcessAfterInitialization方法来对该代理Bean对象做后置增强,这里不在展开分析
  24.                                                                 object = postProcessObjectFromFactoryBean(object, beanName);
  25.                                                         }
  26.                                                         catch (Throwable ex) {
  27.                                                                 throw new BeanCreationException(beanName,
  28.                                                                                 "Post-processing of FactoryBean's singleton object failed", ex);
  29.                                                         }
  30.                                                         finally {
  31.                                                                 afterSingletonCreation(beanName);
  32.                                                         }
  33.                                                 }
  34.                                                 if (containsSingleton(beanName)) {
  35.                                                         // 5.将增强后的代理Bean对象放入到缓存中,这样当别的类注入这个Mapper对象时,就不需要再走一遍后置增强的逻辑了。。直接从这个缓存获取即可
  36.                                                         this.factoryBeanObjectCache.put(beanName, object);
  37.                                                 }
  38.                                         }
  39.                                 }
  40.                                 return object;
  41.                         }
  42.                 }
  43.                 else {
  44.                         Object object = doGetObjectFromFactoryBean(factory, beanName);
  45.                         if (shouldPostProcess) {
  46.                                 try {
  47.                                         object = postProcessObjectFromFactoryBean(object, beanName);
  48.                                 }
  49.                                 catch (Throwable ex) {
  50.                                         throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
  51.                                 }
  52.                         }
  53.                         return object;
  54.                 }
  55.         }
复制代码
可以看到这内里就会通过getObject方法来获代替理了我们Mapper接口的对象,并且它持有一个MybatisMapperProxy 引用(mybatis-plus框架的对象,也实现了InvocationHandler 接口),MybatisMapperProxy 对象内里又会持有SqlSessionTemplate对象的引用,假设没有引入mybatis-plus框架,最终署理对象持有的是MapperProxy 引用

这里的SqlSessionTemplate对象和前面图片的SqlSessionTemplate对象不同,是因为我重启了项目。。
小结:
4)SqlSessionTemplate创建流程

可以看到SqlSessionTemplate确实是实现了SqlSession接口
  1. public class SqlSessionTemplate implements SqlSession, DisposableBean {
  2.         // 持有Configuration对象引用
  3.         private final SqlSessionFactory sqlSessionFactory;
  4.   private final ExecutorType executorType;
  5.         // 代理SqlSession接口的对象,也是SqlSessionTemplate的核心
  6.   private final SqlSession sqlSessionProxy;
  7.   private final PersistenceExceptionTranslator exceptionTranslator;
  8.         //省略其他代码...
  9. }
复制代码
颠末前面的分析,我们知道当调用署理Mapper接口的对象方法时,SqlSessionTemplate最终会代替DefaultSqlSession 来完成Mapper接口的增删改查查操作,所以我们先来看下SqlSessionTemplate 的创建流程:
  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  2.     this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  3.   }
  4.   
  5.   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
  6.     this(sqlSessionFactory, executorType,
  7.         new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  8.   }
复制代码
  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
  2.       PersistenceExceptionTranslator exceptionTranslator) {
  3.     notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  4.     notNull(executorType, "Property 'executorType' is required");
  5.     this.sqlSessionFactory = sqlSessionFactory;
  6.     this.executorType = executorType;
  7.     this.exceptionTranslator = exceptionTranslator;
  8.     // 关键:是不是很熟悉这种代码,这里面创建了一个代理了SqlSession接口的对象,并且最终该代理对象的逻辑会被SqlSessionInterceptor拦截到,因为它实现了InvocationHandler接口
  9.     this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
  10.         new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  11.   }
复制代码
可以看到,它创建了一个署理SqlSession接口的对象,最终署理对象的方法都会被SqlSessionInterceptor拦截到
  1.   @Override
  2.   public <E> List<E> selectList(String statement, Object parameter) {
  3.     return this.sqlSessionProxy.selectList(statement, parameter);
  4.   }
  5.   @Override
  6.   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  7.     return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
  8.   }
  9.   @Override
  10.   public int insert(String statement) {
  11.     return this.sqlSessionProxy.insert(statement);
  12.   }
  13.   @Override
  14.   public int update(String statement, Object parameter) {
  15.     return this.sqlSessionProxy.update(statement, parameter);
  16.   }
  17.   @Override
  18.   public int delete(String statement, Object parameter) {
  19.     return this.sqlSessionProxy.delete(statement, parameter);
  20.   }
复制代码
可以看到,当署理Mapper接口的对象执行增删改查查方法时,会被MapperProxy对象拦截到,然后由SqlSessionTemplate对象来处理,最终都会交由自己内部的sqlSessionProxy对象处理,而由于sqlSessionProxy也是个署理对象,它又会被SqlSessionInterceptor拦截来处理,所以接下来看下SqlSessionInterceptor做了什么处理,也是本篇文章问题的答案所在
5)SqlSessionInterceptor拦截逻辑

先来看下SqlSessionInterceptor的源码如下,它是SqlSessionTemplate的内部类:
  1. private class SqlSessionInterceptor implements InvocationHandler {
  2.   @Override
  3.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  4.     // 1.获取SqlSession对象,见后面分析
  5.     SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
  6.         SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
  7.     try {
  8.       // 2.执行DefaultSqlSession的方法
  9.       Object result = method.invoke(sqlSession, args);
  10.       // 3.判断是否开启了事务,见后面分析
  11.       if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  12.         // force commit even on non-dirty sessions because some databases require
  13.         // a commit/rollback before calling close()
  14.         // 4.没有开启,则提交事务
  15.         sqlSession.commit(true);
  16.       }
  17.       return result;
  18.     } catch (Throwable t) {
  19.       Throwable unwrapped = unwrapThrowable(t);
  20.       if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  21.         // 5.关闭会话,见后面分析
  22.         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  23.         // 6.置为null,finally块就不会重复执行closeSqlSession方法了
  24.         sqlSession = null;
  25.         Throwable translated = SqlSessionTemplate.this.exceptionTranslator
  26.             .translateExceptionIfPossible((PersistenceException) unwrapped);
  27.         if (translated != null) {
  28.           unwrapped = translated;
  29.         }
  30.       }
  31.       throw unwrapped;
  32.     } finally {
  33.       if (sqlSession != null) {
  34.         // 7.关闭会话
  35.         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  36.       }
  37.     }
  38.   }
  39. }
复制代码
当我们执行Mapper接口的增删改查查方法时,最终都会执行到SqlSessionInterceptor的invoke方法,接下来分析下invoke方法的逻辑。
①获取SqlSession对象流程
  1. SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
  2.           SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
复制代码
  1. public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
  2.       PersistenceExceptionTranslator exceptionTranslator) {
  3.     notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  4.     notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  5.                 // 1.从TransactionSynchronizationManager(以下称当前线程事务管理器)获取当前线程threadLocal是否有SqlSessionHolder
  6.     SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  7.     // 2.如果获取到了,则从SqlSessionHolder中拿到SqlSession对象返回
  8.     SqlSession session = sessionHolder(executorType, holder);
  9.     if (session != null) {
  10.       return session;
  11.     }
  12.     LOGGER.debug(() -> "Creating a new SqlSession");
  13.     // 3.由SqlSessionFactory创建一个DefaultSqlSession对象,和使用mybaits手动创建DefaultSqlSession的方法一样
  14.     session = sessionFactory.openSession(executorType);
  15.                
  16.                 // 4.将SqlSession对象封装到SqlSessionHolder对象中,并保存到当前线程事务管理器中
  17.     registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
  18.     return session;
  19.   }
复制代码
接下来依次分析getSqlSession中调用的方法
先来看看当前线程事务管理器的结构:
  1. public abstract class TransactionSynchronizationManager {
  2.   // 存储当前线程事务资源,比如Connection、session等
  3.   private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
  4.   // 存储当前线程事务同步回调器
  5.   // 当有事务,该字段会被初始化,即激活当前线程事务管理器
  6.   private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
  7.   
  8.   // 省略其他代码...
  9. }
复制代码
TransactionSynchronizationManager#getResource方法如下:
  1. public static Object getResource(Object key) {
  2.                 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
  3.                 // 根据SqlSessionFactory对象,从resources中获取SqlSessionHolder对象
  4.                 Object value = doGetResource(actualKey);
  5.                 if (value != null && logger.isTraceEnabled()) {
  6.                         logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
  7.                                         Thread.currentThread().getName() + "]");
  8.                 }
  9.                 return value;
  10.         }
复制代码
它会接着调用doGetResource方法:
  1. @Nullable
  2.         private static Object doGetResource(Object actualKey) {
  3.           // 1.从resources中获取当前线程的事务资源
  4.                 Map<Object, Object> map = resources.get();
  5.                 if (map == null) {
  6.                         return null;
  7.                 }
  8.                 // 2.如果事务资源存在,则根据将SqlSessionFactory对象作为Key,去获取一个SqlSessionHolder对象
  9.                 Object value = map.get(actualKey);
  10.                 // Transparently remove ResourceHolder that was marked as void...
  11.                 if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
  12.                         map.remove(actualKey);
  13.                         // Remove entire ThreadLocal if empty...
  14.                         if (map.isEmpty()) {
  15.                                 resources.remove();
  16.                         }
  17.                         value = null;
  18.                 }
  19.                 // 3.返回SqlSessionHolder对象
  20.                 return value;
  21.         }
复制代码
  1. private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
  2.     SqlSession session = null;
  3.     if (holder != null && holder.isSynchronizedWithTransaction()) {
  4.       if (holder.getExecutorType() != executorType) {
  5.         throw new TransientDataAccessResourceException(
  6.             "Cannot change the ExecutorType when there is an existing transaction");
  7.       }
  8.       holder.requested();
  9.       LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
  10.       // 从SqlSessionHolder中获取到SqlSession对象
  11.       session = holder.getSqlSession();
  12.     }
  13.     return session;
  14.   }
复制代码
  1. session = sessionFactory.openSession(executorType);
复制代码
  1. private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
  2.       PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  3.     SqlSessionHolder holder;
  4.     // 1.判断当前是否有事务
  5.     if (TransactionSynchronizationManager.isSynchronizationActive()) {
  6.       // 2.判断当前环境配置的事务管理工厂是否是SpringManagedTransactionFactory(默认)
  7.       Environment environment = sessionFactory.getConfiguration().getEnvironment();
  8.       if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  9.         LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
  10.                                
  11.                                 // 3.创建一个SqlSessionHolder对象
  12.         holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  13.         // 4.绑定当前SqlSessionHolder到线程ThreadLocal中,即ThreadLocal<Map<Object, Object>> resources中
  14.         TransactionSynchronizationManager.bindResource(sessionFactory, holder);
  15.         // 5.注册SqlSession同步回调器到线程的本地变量synchronizations中
  16.         TransactionSynchronizationManager
  17.             .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
  18.         holder.setSynchronizedWithTransaction(true);
  19.         // 会话引用次数+1
  20.         holder.requested();
  21.       } else {
  22.         if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
  23.           LOGGER.debug(() -> "SqlSession [" + session
  24.               + "] was not registered for synchronization because DataSource is not transactional");
  25.         } else {
  26.           throw new TransientDataAccessResourceException(
  27.               "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
  28.         }
  29.       }
  30.     } else {
  31.       LOGGER.debug(() -> "SqlSession [" + session
  32.           + "] was not registered for synchronization because synchronization is not active");
  33.     }
  34.   }
复制代码
注册SqlSession到当前线程事务管理器的条件首先是当前环境中有事务,否则不注册,判定是否有事务的条件是synchronizations的ThreadLocal是否为空:
  1. public static boolean isSynchronizationActive() {
  2.                 return (synchronizations.get() != null);
  3. }
复制代码
每当我们开启一个事务(声明式、编程式),会调用initSynchronization()方法进行初始化synchronizations,以激活当前线程事务管理器:
  1. private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  2.                         new NamedThreadLocal<>("Transaction synchronizations");
  3.                        
  4. public static void initSynchronization() throws IllegalStateException {
  5.                 if (isSynchronizationActive()) {
  6.                         throw new IllegalStateException("Cannot activate transaction synchronization - already active");
  7.                 }
  8.                 logger.trace("Initializing transaction synchronization");
  9.                 synchronizations.set(new LinkedHashSet<>());
  10.         }
复制代码
后续事务管理器AbstractPlatformTransactionManager 可以从synchronizations获取到SqlSessionHolder对象中的SqlSession来对事务管理,比如关闭Sqlsession。
②事务提交时机
当获取到SqlSession对象之后,接下来会执行以下方法:
  1. Object result = method.invoke(sqlSession, args);
  2.                                 // 判断有没有开启事务
  3.         if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  4.           // 提交当前事务
  5.           sqlSession.commit(true);
  6.         }
复制代码
检察SqlSessionUtils#isSqlSessionTransactional方法如下:
  1. public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
  2.     notNull(session, NO_SQL_SESSION_SPECIFIED);
  3.     notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  4.                 // 从线程的本地变量中获取SqlSessionHolder
  5.     SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  6.                 // 如果SqlSessionHolder不为null说明开启了事务,返回true
  7.     return (holder != null) && (holder.getSqlSession() == session);
  8.   }
复制代码
从前面的分析中,我们知道只有当开启事务时,才会将SqlSessionHolder 对象生存到线程的当地变量ThreadLocal<Map<Object, Object>> resources 中,所以如果没有开启事务的话,是不会生存的。
为什么要判定是否开启事务以控制当前事务提交?
例如:在一个方法上标注了@Transaction注解阐明开启了事务,内里执行的方法都为增删改查的逻辑:
  1. @Transactional(rollbackFor = Exception.class)
  2. public void insertData(Item item, ItemStock itemStock) {
  3.     itemStockMapper.save(itemStock);
  4.     int i = 1 / 0;
  5.     itemMapper.save(item);
  6. }
复制代码
如果没有这个判定逻辑,当该方法执行完itemStockMapper.save(itemStock),便会提交事务了,背面纵然报错了也不会回滚了。正是因为有了这个判定,才不会出现这种情况,将标注@Transaction注解方法内的全部增删改查操作都看作一个团体事务,只有第一个增删改查方法执行时才会创建SqlSession对象,后续的每个增删改查方法执行时都能从线程的当地变量中获取到同一个SqlSession对象来使用,而只有当全部增删改查操作执行完成,才会提交事务。
那标注了@Transaction注解的方法是怎么提交的事务?
刚才看到了,只要没有开启事务并且没有报错,Spring会自动帮我们把事务提交了,这也就是为什么我们平常写代码不必要手动提交事务的原因。
而标注了@Transaction注解的提交事务时机又有所不同,这里不展开代码分析了,分析下大致逻辑:
③closeSqlSession方法分析
无论是正常提交还是异常回滚,都会执行这个关闭会话的方法:
  1. public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  2.     notNull(session, NO_SQL_SESSION_SPECIFIED);
  3.     notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  4.     // 1.从线程本地变量中获取SqlSessionHolder
  5.     SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  6.     if ((holder != null) && (holder.getSqlSession() == session)) {
  7.       LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
  8.       // 2.能获取到,说明开启了事务,则不能关闭会话,减少会话引用次数
  9.       holder.released();
  10.     } else {
  11.       LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
  12.       // 3.如果没有开启事务,则直接关闭会话
  13.       session.close();
  14.     }
  15.   }
复制代码
6)开启事务后,关闭会话的时机分析

在前面的分析中,当方法标注了@Transaction注解代表开了事务,则每次执行内里的子方法时都会从当地变量中获取到SqlSession对象,并且会话引用次数加一。在closeSqlSession方法逻辑中,只是将会话引用次数减一,并没有执行关闭会话的逻辑,那标注了@Transaction注解的方法什么时候才会关闭会话呢?
TransactionInterceptor#invoke→TransactionAspectSupport#invokeWithinTransaction→TransactionAspectSupport#commitTransactionAfterReturning→AbstractPlatformTransactionManager#commit→AbstractPlatformTransactionManager#processCommit→AbstractPlatformTransactionManager#triggerBeforeCompletion → SqlSessionSynchronization#beforeCompletion
  1. @Override
  2. public void beforeCompletion() {
  3.   // Issue #18 Close SqlSession and deregister it now
  4.   // because afterCompletion may be called from a different thread
  5.   // 1.判断会话引用次数是否大于0
  6.   if (!this.holder.isOpen()) {
  7.                 // 2.小于等于0,说明@Transaction注解标注的方法里面的所有增删改查方法都执行完成了,可以进行会话关闭了
  8.     LOGGER
  9.         .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
  10.     // 3.从线程的本地变量中移除SqlSessionHolder
  11.     TransactionSynchronizationManager.unbindResource(sessionFactory);
  12.     this.holderActive = false;
  13.     LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
  14.     // 4.从SqlSessionHolder中获取SqlSession对象,执行会话关闭方法
  15.     this.holder.getSqlSession().close();
  16.   }
  17. }
  18. // ResourceHolderSupport类的方法,方便查看
  19. public boolean isOpen() {
  20.                 return (this.referenceCount > 0);
  21.         }
复制代码
因此,当我们开启事务之后,同一个事务的方法执行时,由于它们同属于一个SqlSession 会话,都会将会话引用次数加一,每个方法执行完成会将会话引用次数减一,当整个方法都执行完成之后,会话引用次数递减为0,最终Spring会判定会话引用次数是否大于0,如果大于0则不关闭会话,小于便是0才会关闭。
来个例子阐明下:
  1. @Transactional(rollbackFor = Exception.class)
  2. public void insertData(Item item, ItemStock itemStock) {
  3.     itemStockMapper.save(itemStock);
  4.     itemMapper.save(item);
  5. }
复制代码
三、总结

本文大致讲解了Mybatis手动创建SqlSession的流程,引入Spring之后为什么就不必要手动去创建SqlSession,以及Spring创建SqlSession的时机原理。
当我们引入Spring之后的处理:

实在底层都是基于动态署理和AOP切面拦截的思想,通过这些机制和线程当地变量,让不同事务创建不同的SqlSession对象,让同一个事务共享同一个SqlSession对象,包管了线程安全。
末了再来一个例子:哪些方法会回滚?
在 insertData方法中内里调用了saveItem方法和saveItemStock方法,并且通过一个新线程调用了 saveItemStock,在 saveItemStock中抛出了异常,这些方法都开启了事务。
  1. @Transactional
  2. public void insertData(Item item, ItemStock itemStock) {
  3.     itemStockService.saveItemStock(itemStock);
  4.                 new Thread(() -> {
  5.                 try {
  6.                     itemService.saveItem(item);
  7.                 } catch (Exception e) {
  8.                     throw new RuntimeException();
  9.                 }
  10.             }).start();
  11. }
  12. @Transactional
  13. public void saveItemStock(ItemStock itemStock) {
  14.         save(itemStock);
  15.     throw new RuntimeException("111");
  16. }
  17. @Transactional
  18. public void saveItem(Item item) {
  19.     save(item);
  20. }
复制代码
结果:saveItem不回滚、 saveItemStock 回滚:
所以开启事务后,在多线程环境下事务管理器并不会跨线程流传事务,事务的状态是存储在线程的当地ThreadLocal 中, 方便后续管理当前线程的事务上下文信息。这也意味着每个线程都有一个独立的事务上下文,事务信息在不同线程之间不会共享。
这篇文章断断续续写了好几天,再加上自己的表达能力有限,所以写起来有点乱,包涵包涵,如果有错误的地方接待指正!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4