http://longlongchang.blog.51cto.com/4725201/1171019
问题就是:无论是多个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 {
-
- final 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)) {
-
-
- sqlSession.commit(true);
- }
-
- return result;
- } catch (Throwable t) {
-
- 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 {
-
- 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) {
-
- SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
-
- if (holder != null && holder.isSynchronizedWithTransaction()) {
-
- if (holder.getExecutorType() != executorType) {
- throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
- }
-
- holder.requested();
-
- return holder.getSqlSession();
- }
-
- SqlSession session = sessionFactory.openSession(executorType);
-
- if (isSynchronizationActive()) {
-
- Environment environment = sessionFactory.getConfiguration().getEnvironment();
- if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
-
- holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
-
- bindResource(sessionFactory, holder);
-
- registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
-
- 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) {
-
- SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
- if ((holder != null) && (holder.getSqlSession() == session)) {
-
- holder.released();
- } else {
-
- session.close();
- }
- }