博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SqlSessionTemplate探究
阅读量:6816 次
发布时间:2019-06-26

本文共 5100 字,大约阅读时间需要 17 分钟。

hot3.png

 问题就是:无论是多个dao使用一个SqlSessionTemplate,还是一个dao使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个sqlSession,当多个web线程调用同一个dao时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession,如何保证线程安全,关键就在于代理:

(1)首先,通过如下代码创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法

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

(2)所以关键之处转移到invoke方法中,代码如下,该类的注释是代理将Mybatis的方法调用导向从Spring的事务管理器获取的合适的SqlSession,说明虽然都是调用同样一个SqlSession接口,但是实际执行sql的sqlSession会有所不同。

 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //调用SqlSessionUtils的getSqlSession方法从Spring的事务管理器获取合适的SqlSession     final SqlSession sqlSession = getSqlSession(            SqlSessionTemplate.this.sqlSessionFactory,            SqlSessionTemplate.this.executorType,            SqlSessionTemplate.this.exceptionTranslator);        try {    //通过sqlSession对象调用该方法          Object result = method.invoke(sqlSession, args);          //判断sqlSession是否被Spring事务管理,也就是sqlSession被放在Spring事务管理的本地线程缓存中。如果不是,则需要自己提交。如果是,则Spring通过代理机制,进行提交和回滚          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) {    //如果出现异常,则利用异常转换器将Mybatis的异常转为Spring的DataAccessException          Throwable unwrapped = unwrapThrowable(t);          if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException)     {            Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);            if (translated != null) {              unwrapped = translated;            }          }          throw unwrapped;        } finally {    //方法调用完毕后,关闭sqlSession连接          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);        }      } 

其中,核心有两部分:

(3)如何从Spring的事务管理器中获得合适的sqlSession,从而保证线程安全,很明显所有dao的多个线程不是使用同一个sqlSession,不然其中一个closeSqlSession,其他怎么用。

该方法的注释:从Spring事务管理器中得到一个SqlSession,如果需要创建一个新的。首先努力从当前事务之外得到一个SqlSession,如果没有就创造一个新的。然后,如果Spring TX被激活,也就是事务被打开,且事务管理器是SpringManagedTransactionFactory时,将得到的SqlSession同当前事务同步,下面是该函数的核心代码

 
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {          //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式      SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);   //如果holder不为空,且和当前事务同步      if (holder != null && holder.isSynchronizedWithTransaction()) {        //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用        if (holder.getExecutorType() != executorType) {          throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");        }        //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加        holder.requested();     //返回sqlSession        return holder.getSqlSession();      }   //如果找不到,则根据执行类型构造一个新的sqlSession      SqlSession session = sessionFactory.openSession(executorType);   //判断同步是否激活,只要SpringTX被激活,就是true      if (isSynchronizationActive()) {     //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务        Environment environment = sessionFactory.getConfiguration().getEnvironment();        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {    //如果是,则将sqlSession加载进事务管理的本地线程缓存中          holder = new SqlSessionHolder(session, executorType, exceptionTranslator);    //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中          bindResource(sessionFactory, holder);    //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations          registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));          //设置当前holder和当前事务同步    holder.setSynchronizedWithTransaction(true);    //增加引用数          holder.requested();        } else {          if (getResource(environment.getDataSource()) == null) {          } else {            throw new TransientDataAccessResourceException(                "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");          }        }      } else {      }      return session;    } 

上述代码中可以看出,只有在一个线程的一个事务中,由同一个sqlSessionFactory创建的执行类型相同的sqlSession才会被复用,其他情况下都是创建新的sqlSession。试想一下,即使只有一个SqlSessionTemplate供所有dao使用,所有地方使用的都是同以个sqlSessionFactory,但是由于是不同线程,所以得到的不是同一个sqlSession,因此不会出现线程安全问题。好处是同一个线程同一个事务中sqlSession会被复用,不会每执行一个sql请求,都创建一个SqlSession,这样很浪费资源,因为SqlSession相当于一次数据库连接。

(4)如何关闭sqlSession连接,主要代码如下

 
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {   //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder       SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);      if ((holder != null) && (holder.getSqlSession() == session)) {     //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用        holder.released();      } else {     //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close        session.close();      }    } 

 

转载于:https://my.oschina.net/u/1169079/blog/215915

你可能感兴趣的文章
Python里的append和extend
查看>>
cut命令
查看>>
JavaScript强化教程-cookie对象
查看>>
MEMCACHE常用的命令
查看>>
docker 基础
查看>>
Angular基础(七) HTTP & Routing
查看>>
使用Freeline提高你的工作效率
查看>>
FTP服务器
查看>>
爬百度新闻
查看>>
上网行为管理设备网关部署方式
查看>>
TCP协议与UDP协议的区别
查看>>
MySQL 忘记root密码解决办法
查看>>
路由器的4种配置模式
查看>>
时空大数据可视化之湖泊可视化简介(Lake Level Viewer)
查看>>
The reference to entity "characterEncoding" must end with the ';' delimiter
查看>>
意大利石油和天然气服务公司Saipem称遭到了来自印度的网络***
查看>>
zabbix 自定义端口监控 。
查看>>
软件定时器算法
查看>>
day1 01
查看>>
[MACOS] Mac上的抓包工具Charles
查看>>