asp网站发邮件,快递空包网站建设,seo网站排名优化,淮北市建筑一、SqlSession是什么二、源码分析1#xff09;mybatis获取Mapper流程2#xff09;Spring创建Mapper接口的代理对象流程3#xff09;MapperFactoryBean#getObject调用时机4#xff09;SqlSessionTemplate创建流程5#xff09;SqlSessionInterceptor拦截逻辑6#xff09;开… 一、SqlSession是什么二、源码分析1mybatis获取Mapper流程2Spring创建Mapper接口的代理对象流程3MapperFactoryBean#getObject调用时机4SqlSessionTemplate创建流程5SqlSessionInterceptor拦截逻辑6开启事务后关闭会话的时机分析 三、总结 参考https://www.zhihu.com/question/57568941/answer/2062846449
先来说结论
如果方法上标注了Transactional 注解则该方法里面多次访问数据库用的是同一个SqlSession多线程调用除外如果方法上没有标注该注解则每访问一次数据库都会创建新的SqlSession
一、SqlSession是什么
SqlSession是Mybatis工作的最顶层API会话接口所有的数据库操作都经由它来实现。由于它是一个会话即SqlSession对应这一次数据库会话不是永久存活的因此每次访问数据库时都需要创建它。
并且它不是线程安全的如果将一个SqlSession搞成单例形式或者静态域和实例变量的形式都会导致SqlSession出现事务问题这也就是为什么同一事务中的多个访问数据库请求会共用一个SqlSession会话而不同事务则会创建不同SqlSession的原因。
SqlSession的创建过程
从Configuration配置类中拿到Environment数据源从数据源中获取TransactionFactory和DataSource并创建一个Transaction连接管理对象创建Executor对象SqlSession只是所有操作的门面真正要干活的是Executor它封装了底层JDBC所有的操作细节创建SqlSession会话。
每次创建一个SqlSession会话都会伴随创建一个专属SqlSession的连接管理对象如果SqlSession共享就会出现事务问题。
二、源码分析
1mybatis获取Mapper流程
先回顾以下传统mybatis创建Mapper接口的代理对象流畅如下
如果没有引入spring的依赖以前做法是通过sqlSession手动去获取Mapper对象第一步是先创建SqlSession工厂对象由它来创建SqlSession对象
//sqlSessionFactory -- sqlSession
public class MybatisUtils {static SqlSessionFactory sqlSessionFactory null;static {try {String resource mybatis-config.xml;InputStream inputStream Resources.getResourceAsStream(resource);// 1.build方法会解析xml文件包括我们写的mapper接口的xml文件最终会把解析的信息封装到configuration对象中// 特别是我们xml文件中的sql和相关信息都会被封装成一个个的MappedStatement对象存进一个Map中key为全限定类名方法名value为MappedStatement对象// 然后创建一个持有configuration引用的工厂对象返回这里面就不展开分析了sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);} catch (IOException e) {e.printStackTrace();}}// 2.这里会创建一个持有configuration对象引用的DefaultSqlSession对象返回并且Executor对象也是在这一步创建的提供了在数据库执行 SQL 命令所需的所有方法这里面不展开分析public static SqlSession getSqlSession(){return sqlSessionFactory.openSession();}
}使用 //1.获取SqlSession对象SqlSession sqlSession MybatisUtils.getSqlSession();//2.获取代理对象执行SQLUserDao userDao sqlSession.getMapper(UserDao.class);ListUser userList userDao.getUserList();for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();
查看DefaultSqlSession的getMapper方法如下
Override
public T T getMapper(ClassT type) {return configuration.getMapper(type, this);
}接着会调用到MapperRegistry#getMapper方法如下
public T T getMapper(ClassT type, SqlSession sqlSession) {// 1.knownMappers会在解析mapper接口的xml文件时设置key为接口的class对象value为持有接口字节码对象引用的MapperProxyFactory对象final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type);if (mapperProxyFactory null) {throw new BindingException(Type type is not known to the MapperRegistry.);}try {// 2.创建代理对象逻辑return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException(Error getting mapper instance. Cause: e, e);}}接着调用MapperRegistry#newInstance 方法MapperProxyFactory源码如下
public class MapperProxyFactoryT {private final ClassT mapperInterface;private final MapMethod, MapperMethod methodCache new ConcurrentHashMap();public MapperProxyFactory(ClassT mapperInterface) {this.mapperInterface mapperInterface;}public ClassT getMapperInterface() {return mapperInterface;}public MapMethod, MapperMethod getMethodCache() {return methodCache;}SuppressWarnings(unchecked)protected T newInstance(MapperProxyT mapperProxy) {// 3.用JDK的方式去创建一个代理了mapper接口的代理对象返回然后可以拿这个对象来执行增删改查查方法了具体逻辑是现在MapperProxy中return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {// 1.创建MapperProxy对象它持有sqlSession对象、接口字节码对象引用并且它实现了InvocationHandler接口这是动态代理的关键final MapperProxyT mapperProxy new MapperProxy(sqlSession, mapperInterface, methodCache);// 2.创建mapper的代理对象return newInstance(mapperProxy);}
}因为MapperProxy实现了InvocationHandler接口所以代理对象调用方法时会先经过MapperProxy#invoke方法 private final MapMethod, MapperMethod methodCache;Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 1.如果是父类Object的方法就直接反射调用if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {// 2.如果是接口的默认方法则调用invokeDefaultMethod方法return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);} // 3.我们自己mapper接口定义的方法会接着调用MapperMethod#executefinal MapperMethod mapperMethod cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}// 4.如果mapperMethod 对象存在就不创建了直接从缓存取private MapperMethod cachedMapperMethod(Method method) {// MapperMethod对象持有mapper接口字节码对象、要执行的目标方法对象、configuration对象引用return methodCache.computeIfAbsent(method, k - new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}MapperMethod#execute 方法如下 public MapperMethod(Class? mapperInterface, Method method, Configuration config) {// 根据全限定类名方法名去获取MappedStatement对象然后获取MappedStatement对象的标签类型其中它的字段type为标签类型name为全限定类名方法名this.command new SqlCommand(config, mapperInterface, method);this.method new MethodSignature(config, mapperInterface, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result null;} else if (method.returnsMany()) {result executeForMany(sqlSession, args);} else if (method.returnsMap()) {result executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result executeForCursor(sqlSession, args);} else {Object param method.convertArgsToSqlCommandParam(args);result sqlSession.selectOne(command.getName(), param);if (method.returnsOptional() (result null || !method.getReturnType().equals(result.getClass()))) {result Optional.ofNullable(result);}}break;case FLUSH:result sqlSession.flushStatements();break;default:throw new BindingException(Unknown execution method for: command.getName());}if (result null method.getReturnType().isPrimitive() !method.returnsVoid()) {throw new BindingException(Mapper method command.getName() attempted to return null from a method with a primitive return type ( method.getReturnType() ).);}return result;}这里面不具体展开了大体逻辑是
1根据获取的MappedStatement对象的标签类型对应xml文件的增删改查查标签SELECT、DELETE、UPDATE、INSERT来执行DefaultSqlSession对象对应的增删改查查方法
2然后从configuration对象中根据全限定类名方法名去获取一个MappedStatement对象
3接着调用对应Executor对象的方法并把MappedStatement对象传进去Executor对象里面封装了JDBC的逻辑以查询为例大致逻辑为
首先会创建PreparedStatementHandler对象接着会创建ParameterHandler 对象和ResultSetHandler对象这些对象在创建时都会用InterceptorChain 拦截器链的pluginAll方法去判断是否需要增强这三个对象如果要增强则会用动态代理来创建这些对象的代理对象这也是mybatis插件原理的实现PreparedStatementHandler 对象相当于JDBC的预编译语句对象它会处理sgl语句预编译设置参数等相关工作在设置预编译参数时sql语句的占位符替换PreparedStatementHandler 对象会调用ParameterHandler 的setParameters方法来实现参数设置里面会调用TypeHandler 对象方法来完成Java类型到数据库类型的转换在处理结果集时PreparedStatementHandler 对象会调用ResultSetHandler的handleResultSets方法来实现结果集映射里面会调用TypeHandler 对象方法来完数据库类型到Java类型的转换 回顾了mybatis执行的大致原理都是依靠DefaultSqlSession的方法那引入了spring为什么就不需要我们手动创建sqlSession了呢接下来接着分析
2Spring创建Mapper接口的代理对象流程
当我们在接口标注一个Mapper注解并且MapperScan注解的包路径能扫描到该接口时则会对该接口生成一个工厂Bean对象MapperFactoryBean 放入一级缓存中
// 省略其他代码...
public class MapperFactoryBeanT extends SqlSessionDaoSupport implements FactoryBeanT {private ClassT mapperInterface;private boolean addToConfig true;public MapperFactoryBean() {// intentionally empty}// 1.Mapper注解所在接口的字节码对象public MapperFactoryBean(ClassT mapperInterface) {this.mapperInterface mapperInterface;}Overrideprotected void checkDaoConfig() {super.checkDaoConfig();notNull(this.mapperInterface, Property mapperInterface is required);Configuration configuration getSqlSession().getConfiguration();if (this.addToConfig !configuration.hasMapper(this.mapperInterface)) {try {configuration.addMapper(this.mapperInterface);} catch (Exception e) {logger.error(Error while adding the mapper this.mapperInterface to configuration., e);throw new IllegalArgumentException(e);} finally {ErrorContext.instance().reset();}}}// 2.注册真正的Mapper对象Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}Overridepublic ClassT getObjectType() {return this.mapperInterface;}Overridepublic boolean isSingleton() {return true;}public ClassT getMapperInterface() {return mapperInterface;}
}
当Spring填充某个Bean的字段时如果根据字段名称能从一级缓存获取到了Bean实例并且该Bean实现了FactoryBean接口则会调用该Bean的getObject方法来获取真正的Bean来注入到对应字段中
Override
public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);
}里面会调用父类的getSqlSession方法
public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSessionTemplate sqlSessionTemplate;public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {// 2.如果sqlSessionTemplate为空则创建该对象if (this.sqlSessionTemplate null || sqlSessionFactory ! this.sqlSessionTemplate.getSqlSessionFactory()) {this.sqlSessionTemplate createSqlSessionTemplate(sqlSessionFactory);}}// 3.创建SqlSessionTemplate对象并把sqlSessionFactory对象传进去持有Mapper.xml文件解析后的数据SuppressWarnings(WeakerAccess)protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}public final SqlSessionFactory getSqlSessionFactory() {return (this.sqlSessionTemplate ! null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);}public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {this.sqlSessionTemplate sqlSessionTemplate;}// 1.获取SqlSessionTemplate对象它实现了SqlSession接口public SqlSession getSqlSession() {return this.sqlSessionTemplate;}public SqlSessionTemplate getSqlSessionTemplate() {return this.sqlSessionTemplate;}Overrideprotected void checkDaoConfig() {notNull(this.sqlSessionTemplate, Property sqlSessionFactory or sqlSessionTemplate are required);}}可以看到getSqlSession方法最终会返回一个已经创建好的SqlSessionTemplate对象它底层实现了SqlSession接口并且每个MapperFactoryBean对象都会持有同一个SqlSessionTemplate 对象因为它们都继承了同一个抽象父类SqlSessionDaoSupport 的sqlSessionTemplate字段
打断点发现一级缓存中的两个MapperFactoryBean对象确实持有相同的SqlSessionTemplate 引用如下所示 当返回SqlSessionTemplate对象之后就会调用它的getMapper方法来获取Mapper接口的代理对象
// 省略其他代码...
public class SqlSessionTemplate implements SqlSession, DisposableBean {public T T getMapper(ClassT type) {// 2.关键部分创建一个代理Mapper接口的对象返回return getConfiguration().getMapper(type, this);}// 1.获取创建时传进来的sqlSessionFactory对象中的Configuration对象Overridepublic Configuration getConfiguration() {return this.sqlSessionFactory.getConfiguration();}}在创建代理对象时关键在于这个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 对象来处理的。
3MapperFactoryBean#getObject调用时机
当我注入一个Mapper接口对象时它会调用doGetBean方法根据bean的名称从一级缓存中获取到对应的MapperFactoryBean对象 来看看getObjectForBeanInstance 方法逻辑
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, Nullable RootBeanDefinition mbd) {if (BeanFactoryUtils.isFactoryDereference(name)) {if (beanInstance instanceof NullBean) {return beanInstance;}if (!(beanInstance instanceof FactoryBean)) {throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());}if (mbd ! null) {mbd.isFactoryBean true;}return beanInstance;}// 1.不是工厂Bean直接返回if (!(beanInstance instanceof FactoryBean)) {return beanInstance;}Object object null;if (mbd ! null) {mbd.isFactoryBean true;}else {// 2.从缓存中获取代理Bean对象object getCachedObjectForFactoryBean(beanName);}// 3.缓存中获取不到说明第一次获取if (object null) {FactoryBean? factory (FactoryBean?) beanInstance;// Caches object obtained from FactoryBean if it is a singleton.if (mbd null containsBeanDefinition(beanName)) {mbd getMergedLocalBeanDefinition(beanName);}boolean synthetic (mbd ! null mbd.isSynthetic());// 4.里面会调用MapperFactoryBean#getObject方法object getObjectFromFactoryBean(factory, beanName, !synthetic);}return object;}// 从缓存中获取代理BeanNullableprotected Object getCachedObjectForFactoryBean(String beanName) {return this.factoryBeanObjectCache.get(beanName);}// 缓存代理Beanprivate final MapString, Object factoryBeanObjectCache new ConcurrentHashMap(16);此时传进来的beanInstance是MapperFactoryBean实例显然是工厂Bean对象所以接下来会执行getObjectFromFactoryBean方法
protected Object getObjectFromFactoryBean(FactoryBean? factory, String beanName, boolean shouldPostProcess) {// 1.判断是不是单例一级缓存中是否存在该工厂Bean对象很显然是有的if (factory.isSingleton() containsSingleton(beanName)) {synchronized (getSingletonMutex()) {// 2.再次从缓存中获取代理BeanObject object this.factoryBeanObjectCache.get(beanName);if (object null) {// 3.缓存还是没有这下才回去调用MapperFactoryBean#getObject方法获取代理Bean对象object doGetObjectFromFactoryBean(factory, beanName);Object alreadyThere this.factoryBeanObjectCache.get(beanName);if (alreadyThere ! null) {object alreadyThere;}else {if (shouldPostProcess) {if (isSingletonCurrentlyInCreation(beanName)) {// Temporarily return non-post-processed object, not storing it yet..return object;}beforeSingletonCreation(beanName);try {// 4.这个方法最终会遍历所有的BeanPostProcessor尝试执行postProcessAfterInitialization方法来对该代理Bean对象做后置增强这里不在展开分析object postProcessObjectFromFactoryBean(object, beanName);}catch (Throwable ex) {throw new BeanCreationException(beanName,Post-processing of FactoryBeans singleton object failed, ex);}finally {afterSingletonCreation(beanName);}}if (containsSingleton(beanName)) {// 5.将增强后的代理Bean对象放入到缓存中这样当别的类注入这个Mapper对象时就不需要再走一遍后置增强的逻辑了。。直接从这个缓存获取即可this.factoryBeanObjectCache.put(beanName, object);}}}return object;}}else {Object object doGetObjectFromFactoryBean(factory, beanName);if (shouldPostProcess) {try {object postProcessObjectFromFactoryBean(object, beanName);}catch (Throwable ex) {throw new BeanCreationException(beanName, Post-processing of FactoryBeans object failed, ex);}}return object;}}doGetObjectFromFactoryBean方法
可以看到这里面就会通过getObject方法来获取代理了我们Mapper接口的对象并且它持有一个MybatisMapperProxy 引用mybatis-plus框架的对象也实现了InvocationHandler 接口MybatisMapperProxy 对象里面又会持有SqlSessionTemplate对象的引用假设没有引入mybatis-plus框架最终代理对象持有的是MapperProxy 引用 这里的SqlSessionTemplate对象和前面图片的SqlSessionTemplate对象不同是因为我重启了项目。。
小结
当标注Mapper注解的接口被扫描到时会生成一个该接口对应的MapperFactoryBean对象然后将接口名称第一个字母小写作为keyMapperFactoryBean对象作为value放入到一级缓存中注意此时还没有创建Mapper接口的代理对象当我们给一些类的字段注入Mapper接口的对象时此时会走getBean流程根据接口名称从一级缓存获取到了MapperFactoryBean对象接着Spring会判断该Bean是不是FactoryBean类型如果该Bean不是FactoryBean类型直接返回如果该Bean是FactoryBean类型此时会尝试从factoryBeanObjectCache这个缓存中根据接口名称获取Mapper接口的代理对象如果获取到直接返回该代理Bean对象如果获取不到说明是第一次注入该Mapper接口的对象则会去调用MapperFactoryBean的getObject方法来创建一个代理Mapper接口的对象返回此时拿到的代理Bean对象还不能返回会拿到所有的后置处理器尝试对该代理Bean对象做增强将增强后的代理Bean对象放入到factoryBeanObjectCache缓存中并将该对象返回
4SqlSessionTemplate创建流程
可以看到SqlSessionTemplate确实是实现了SqlSession接口
public class SqlSessionTemplate implements SqlSession, DisposableBean {// 持有Configuration对象引用private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;// 代理SqlSession接口的对象也是SqlSessionTemplate的核心private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;//省略其他代码...
}经过前面的分析我们知道当调用代理Mapper接口的对象方法时SqlSessionTemplate最终会代替DefaultSqlSession 来完成Mapper接口的增删改查查操作所以我们先来看下SqlSessionTemplate 的创建流程
调用有参构造方法将sqlSessionFactory传进来
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this(sqlSessionFactory, executorType,new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));}最终会调用下面的构造方法
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;// 关键是不是很熟悉这种代码这里面创建了一个代理了SqlSession接口的对象并且最终该代理对象的逻辑会被SqlSessionInterceptor拦截到因为它实现了InvocationHandler接口this.sqlSessionProxy (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class }, new SqlSessionInterceptor());}可以看到它创建了一个代理SqlSession接口的对象最终代理对象的方法都会被SqlSessionInterceptor拦截到
我们看下SqlSessionTemplate的一些其他方法
Overridepublic E ListE selectList(String statement, Object parameter) {return this.sqlSessionProxy.selectList(statement, parameter);}Overridepublic E ListE selectList(String statement, Object parameter, RowBounds rowBounds) {return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);}Overridepublic int insert(String statement) {return this.sqlSessionProxy.insert(statement);}Overridepublic int update(String statement, Object parameter) {return this.sqlSessionProxy.update(statement, parameter);}Overridepublic int delete(String statement, Object parameter) {return this.sqlSessionProxy.delete(statement, parameter);}可以看到当代理Mapper接口的对象执行增删改查查方法时会被MapperProxy对象拦截到然后由SqlSessionTemplate对象来处理最终都会交由自己内部的sqlSessionProxy对象处理而由于sqlSessionProxy也是个代理对象它又会被SqlSessionInterceptor拦截来处理所以接下来看下SqlSessionInterceptor做了什么处理也是本篇文章问题的答案所在
5SqlSessionInterceptor拦截逻辑
先来看下SqlSessionInterceptor的源码如下它是SqlSessionTemplate的内部类
private class SqlSessionInterceptor implements InvocationHandler {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.获取SqlSession对象见后面分析SqlSession sqlSession getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);try {// 2.执行DefaultSqlSession的方法Object result method.invoke(sqlSession, args);// 3.判断是否开启了事务见后面分析if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()// 4.没有开启则提交事务sqlSession.commit(true);}return result;} catch (Throwable t) {Throwable unwrapped unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator ! null unwrapped instanceof PersistenceException) {// 5.关闭会话见后面分析closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);// 6.置为nullfinally块就不会重复执行closeSqlSession方法了sqlSession null;Throwable translated SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated ! null) {unwrapped translated;}}throw unwrapped;} finally {if (sqlSession ! null) {// 7.关闭会话closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
}当我们执行Mapper接口的增删改查查方法时最终都会执行到SqlSessionInterceptor的invoke方法接下来分析下invoke方法的逻辑。
①获取SqlSession对象流程
首先会调用如下方法获取一个SqlSession对象
SqlSession sqlSession getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);SqlSessionUtils#getSqlSession 方法如下
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);// 1.从TransactionSynchronizationManager以下称当前线程事务管理器获取当前线程threadLocal是否有SqlSessionHolderSqlSessionHolder holder (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);// 2.如果获取到了则从SqlSessionHolder中拿到SqlSession对象返回SqlSession session sessionHolder(executorType, holder);if (session ! null) {return session;}LOGGER.debug(() - Creating a new SqlSession);// 3.由SqlSessionFactory创建一个DefaultSqlSession对象和使用mybaits手动创建DefaultSqlSession的方法一样session sessionFactory.openSession(executorType);// 4.将SqlSession对象封装到SqlSessionHolder对象中并保存到当前线程事务管理器中registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}接下来依次分析getSqlSession中调用的方法
TransactionSynchronizationManager#getResource方法流程
先来看看当前线程事务管理器的结构
public abstract class TransactionSynchronizationManager {// 存储当前线程事务资源比如Connection、session等private static final ThreadLocalMapObject, Object resources new NamedThreadLocal(Transactional resources);// 存储当前线程事务同步回调器// 当有事务该字段会被初始化即激活当前线程事务管理器private static final ThreadLocalSetTransactionSynchronization synchronizations new NamedThreadLocal(Transaction synchronizations);// 省略其他代码...
}TransactionSynchronizationManager#getResource方法如下
public static Object getResource(Object key) {Object actualKey TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);// 根据SqlSessionFactory对象从resources中获取SqlSessionHolder对象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;}它会接着调用doGetResource方法
Nullableprivate static Object doGetResource(Object actualKey) {// 1.从resources中获取当前线程的事务资源MapObject, Object map resources.get();if (map null) {return null;}// 2.如果事务资源存在则根据将SqlSessionFactory对象作为Key去获取一个SqlSessionHolder对象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;}// 3.返回SqlSessionHolder对象return value;}当拿到SqlSessionHolder对象后会执行sessionHolder方法来获取SqlSession对象
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {SqlSession session null;if (holder ! null holder.isSynchronizedWithTransaction()) {if (holder.getExecutorType() ! executorType) {throw new TransientDataAccessResourceException(Cannot change the ExecutorType when there is an existing transaction);}holder.requested();LOGGER.debug(() - Fetched SqlSession [ holder.getSqlSession() ] from current transaction);// 从SqlSessionHolder中获取到SqlSession对象session holder.getSqlSession();}return session;}如果能从SqlSessionHolder中获取到SqlSession 对象则直接返回否则会执行下面的方法去创建一个DefaultSqlSession对象 session sessionFactory.openSession(executorType);当创建了SqlSession对象之后会接着执行registerSessionHolder方法
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {SqlSessionHolder holder;// 1.判断当前是否有事务if (TransactionSynchronizationManager.isSynchronizationActive()) {// 2.判断当前环境配置的事务管理工厂是否是SpringManagedTransactionFactory默认Environment environment sessionFactory.getConfiguration().getEnvironment();if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {LOGGER.debug(() - Registering transaction synchronization for SqlSession [ session ]);// 3.创建一个SqlSessionHolder对象holder new SqlSessionHolder(session, executorType, exceptionTranslator);// 4.绑定当前SqlSessionHolder到线程ThreadLocal中即ThreadLocalMapObject, Object resources中TransactionSynchronizationManager.bindResource(sessionFactory, holder);// 5.注册SqlSession同步回调器到线程的本地变量synchronizations中TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));holder.setSynchronizedWithTransaction(true);// 会话引用次数1holder.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);}}注册SqlSession到当前线程事务管理器的条件首先是当前环境中有事务否则不注册判断是否有事务的条件是synchronizations的ThreadLocal是否为空
public static boolean isSynchronizationActive() {return (synchronizations.get() ! null);
}每当我们开启一个事务声明式、编程式会调用initSynchronization()方法进行初始化synchronizations以激活当前线程事务管理器 private static final ThreadLocalSetTransactionSynchronization synchronizations new NamedThreadLocal(Transaction synchronizations);public static void initSynchronization() throws IllegalStateException {if (isSynchronizationActive()) {throw new IllegalStateException(Cannot activate transaction synchronization - already active);}logger.trace(Initializing transaction synchronization);synchronizations.set(new LinkedHashSet());}后续事务管理器AbstractPlatformTransactionManager 可以从synchronizations获取到SqlSessionHolder对象中的SqlSession来对事务管理比如关闭Sqlsession。
②事务提交时机
当获取到SqlSession对象之后接下来会执行以下方法
Object result method.invoke(sqlSession, args);// 判断有没有开启事务if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// 提交当前事务sqlSession.commit(true);}查看SqlSessionUtils#isSqlSessionTransactional方法如下
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {notNull(session, NO_SQL_SESSION_SPECIFIED);notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);// 从线程的本地变量中获取SqlSessionHolderSqlSessionHolder holder (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);// 如果SqlSessionHolder不为null说明开启了事务返回truereturn (holder ! null) (holder.getSqlSession() session);}从前面的分析中我们知道只有当开启事务时才会将SqlSessionHolder 对象保存到线程的本地变量ThreadLocalMapObject, Object resources 中所以如果没有开启事务的话是不会保存的。
为什么要判断是否开启事务以控制当前事务提交
例如在一个方法上标注了Transaction注解说明开启了事务里面执行的方法都为增删改查的逻辑
Transactional(rollbackFor Exception.class)
public void insertData(Item item, ItemStock itemStock) {itemStockMapper.save(itemStock);int i 1 / 0;itemMapper.save(item);
}如果没有这个判断逻辑当该方法执行完itemStockMapper.save(itemStock)便会提交事务了后面即使报错了也不会回滚了。正是因为有了这个判断才不会出现这种情况将标注Transaction注解方法内的所有增删改查操作都看作一个整体事务只有第一个增删改查方法执行时才会创建SqlSession对象后续的每个增删改查方法执行时都能从线程的本地变量中获取到同一个SqlSession对象来使用而只有当全部增删改查操作执行完成才会提交事务。
那标注了Transaction注解的方法是怎么提交的事务
刚才看到了只要没有开启事务并且没有报错Spring会自动帮我们把事务提交了这也就是为什么我们平常写代码不需要手动提交事务的原因。
而标注了Transaction注解的提交事务时机又有所不同这里不展开代码分析了分析下大致逻辑
当一个public方法被标注Transaction 注解之后后续简称目标方法Spring会基于AOP给这个方法所在的类创建一个代理对象并且会给这个代理对象创建出一个方法拦截器TransactionInterceptor假设这个代理对象是JDK代理的那当我们执行这个代理对象的方法时最终会执行到JdkDynamicAopProxy 的invoke方法接着里面会根据方法对象和方法对象的hashcode去MapMethodCacheKey, ListObject methodCache 这个缓存中尝试获取拦截器链如果没有获取到说明是第一次执行方法则会从ProxyFactory 此对象在创建代理对象时会被保存在JdkDynamicAopProxy 中获取增强器链接着遍历增强链如果不是方法拦截器则适配成方法拦截器此时就获取到了TransactionInterceptor 这个拦截器对象拿到拦截器链之后就会按照顺序执行拦截器链中的拦截器方法以及目标方法其中TransactionInterceptor 会比目标方法先执行它会在目标方法执行之前开启事务如果目标方法执行过程中报错它会控制事务回滚当目标方法执行完成之后它才会控制事务提交。不过事务的处理是交由PlatformTransactionManager 这个事务管理器来处理的。
③closeSqlSession方法分析
无论是正常提交还是异常回滚都会执行这个关闭会话的方法
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {notNull(session, NO_SQL_SESSION_SPECIFIED);notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);// 1.从线程本地变量中获取SqlSessionHolder SqlSessionHolder holder (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);if ((holder ! null) (holder.getSqlSession() session)) {LOGGER.debug(() - Releasing transactional SqlSession [ session ]);// 2.能获取到说明开启了事务则不能关闭会话减少会话引用次数holder.released();} else {LOGGER.debug(() - Closing non transactional SqlSession [ session ]);// 3.如果没有开启事务则直接关闭会话session.close();}}6开启事务后关闭会话的时机分析
在前面的分析中当方法标注了Transaction注解代表开了事务则每次执行里面的子方法时都会从本地变量中获取到SqlSession对象并且会话引用次数加一。在closeSqlSession方法逻辑中只是将会话引用次数减一并没有执行关闭会话的逻辑那标注了Transaction注解的方法什么时候才会关闭会话呢
当方法标注Transaction注解之后Spring会给当前类生成一个代理对象并且事务处理的拦截器为TransactionInterceptor 所以当我们执行事务注解标注的方法时假设没有异常的情况下最终调用链路如下
TransactionInterceptor#invoke→TransactionAspectSupport#invokeWithinTransaction→TransactionAspectSupport#commitTransactionAfterReturning→AbstractPlatformTransactionManager#commit→AbstractPlatformTransactionManager#processCommit→AbstractPlatformTransactionManager#triggerBeforeCompletion → SqlSessionSynchronization#beforeCompletion
首先会从前面分析获取SqlSession对象时的synchronizations本地变量中获取到SqlSessionSynchronization 对象里面保存了SqlSessionHolder对象在整体事务提交之前会执行SqlSessionSynchronization 对象的 beforeCompletion 方法
Override
public void beforeCompletion() {// Issue #18 Close SqlSession and deregister it now// because afterCompletion may be called from a different thread// 1.判断会话引用次数是否大于0if (!this.holder.isOpen()) {// 2.小于等于0说明Transaction注解标注的方法里面的所有增删改查方法都执行完成了可以进行会话关闭了LOGGER.debug(() - Transaction synchronization deregistering SqlSession [ this.holder.getSqlSession() ]);// 3.从线程的本地变量中移除SqlSessionHolderTransactionSynchronizationManager.unbindResource(sessionFactory);this.holderActive false;LOGGER.debug(() - Transaction synchronization closing SqlSession [ this.holder.getSqlSession() ]);// 4.从SqlSessionHolder中获取SqlSession对象执行会话关闭方法this.holder.getSqlSession().close();}
}// ResourceHolderSupport类的方法方便查看
public boolean isOpen() {return (this.referenceCount 0);}因此当我们开启事务之后同一个事务的方法执行时由于它们同属于一个SqlSession 会话都会将会话引用次数加一每个方法执行完成会将会话引用次数减一当整个方法都执行完成之后会话引用次数递减为0最终Spring会判断会话引用次数是否大于0如果大于0则不关闭会话小于等于0才会关闭。
来个例子说明下
Transactional(rollbackFor Exception.class)
public void insertData(Item item, ItemStock itemStock) {itemStockMapper.save(itemStock);itemMapper.save(item);
}当执行insertData方法时其实调用的是代理对象的方法最终会被TransactionInterceptor拦截到在目标对象的insertData执行前会由AbstractPlatformTransactionManager 开启事务 当执行到itemStockMapper.save(itemStock)时此时执行的也是代理了ItemStockMapper接口的对象方法最终会执行到SqlSessionInterceptor 的invoke方法 前面也分析过了第一次执行invoke方法此时线程的本地变量没有SqlSessionHolder所以会去创建SqlSession对象并把它放入SqlSessionHolder对象中接着会把SqlSessionHolder放入本地变量中并对会话引用计数加一当itemStockMapper.save(itemStock)执行完成之后此时会把会话引用计数减一并没有提交事务 当执行到itemMapper.save(item)时此时执行的也是代理了ItemMapper接口的对象方法最终会执行到SqlSessionInterceptor 的invoke方法 这是第二次调用invoke方法所以可以在本地变量中获取到SqlSession对象并对会话引用计数加一当itemMapper.save(item)执行完成之后此时会话引用计数减一变为0了此时还没有提交事务 当整个insertData方法都执行完成之后代表整个事务都完成了此时会由AbstractPlatformTransactionManager 来提交事务并且在提交的时候会判断会话引用计数是否大于0如果小于等于0则关闭会话
三、总结
本文大致讲解了Mybatis手动创建SqlSession的流程引入Spring之后为什么就不需要手动去创建SqlSession以及Spring创建SqlSession的时机原理。
当我们引入Spring之后的处理
自动扫描标注Mapper的接口生成一个代理对象其中代理对象的增删改查操作最终会由SqlSessionTemplate 来执行SqlSessionTemplate 会生成一个代理SqlSession接口的对象由该代理对象帮我们管理SqlSession的创建当方法上标注了Transactional 注解则该方法里面多次访问数据库用的是同一个SqlSession否则每次调用方法都会去创建一个SqlSessionTransactionInterceptor 会拦截标注Transaction注解的方法通过事务管理器PlatformTransactionManager 来对当前事务进行管理包括正常提交、异常回滚、关闭会话等操作在开启事务后 SqlSessionHolder 对象起着很大重用它保存着首次创建的SqlSession对象并且它会存储在线程的两个本地变量中 TransactionSynchronizationManager.resources 主要作用是给事务方法里面的多个子方法使用保证了它们能从这个本地变量中获取同一个SqlSession对象多线程情况除外TransactionSynchronizationManager.synchronizations 主要作用是由事务管理器AbstractPlatformTransactionManager从本地变量中获取SqlSession对象来对全局事务进行管理。并且当它获取的值不为空时说明开启了事务才会将SqlSessionHolder 对象保存在TransactionSynchronizationManager.resources 中
其实底层都是基于动态代理和AOP切面拦截的思想通过这些机制和线程本地变量让不同事务创建不同的SqlSession对象让同一个事务共享同一个SqlSession对象保证了线程安全。
最后再来一个例子哪些方法会回滚
在 insertData方法中里面调用了saveItem方法和saveItemStock方法并且通过一个新线程调用了 saveItemStock在 saveItemStock中抛出了异常这些方法都开启了事务。
Transactional
public void insertData(Item item, ItemStock itemStock) {itemStockService.saveItemStock(itemStock);new Thread(() - {try {itemService.saveItem(item);} catch (Exception e) {throw new RuntimeException();}}).start();
}Transactional
public void saveItemStock(ItemStock itemStock) {save(itemStock);throw new RuntimeException(111);
}Transactional
public void saveItem(Item item) {save(item);
}结果saveItem不回滚、 saveItemStock 回滚
这里相当于两个线程调用不同的事务方法而每个线程不会共享自己的SqlSessionsaveItem无法回滚是因为没有捕获到新线程中抛出的异常saveItemStock方法可以回滚是因为事务管理器只对当前线程中的事务有效
所以开启事务后在多线程环境下事务管理器并不会跨线程传播事务事务的状态是存储在线程的本地ThreadLocal 中 方便后续管理当前线程的事务上下文信息。这也意味着每个线程都有一个独立的事务上下文事务信息在不同线程之间不会共享。
这篇文章断断续续写了好几天再加上自己的表达能力有限所以写起来有点乱见谅见谅如果有错误的地方欢迎指正