SqlSessionFactory的open方法获取的是 DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是 线程不安全的。所以直接使用会存在数据安全问题,如果是两个线程同时方案, 一个线程插入数据, 一个线程修改数据, 因为是同一个连接, 就造成内存和数据库不一致现象, 所以线程不安全.
image.png
解决方案:
SqlSessionTemplate 在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这 个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是 单例的) 本地真正执行对象还是DefaultSqlSession
private class SqlSessionInterceptor implements InvocationHandler {
@Override
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);
}
}
}
}
- 重点: 从当前上下文获取sqlSession , 如果获取到, 就返回, 同一个线程里面的sqlSession 可以是同一个
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
如果获取不到, 就创建一个新的
执行完成后就及时关闭session, 保证每一个连接都是全新的sqlsession对象.
因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理 类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建 一个DefaultSqlSession实例,再调用被代理对象的相应方法。 MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成 到Spring要保证线程安全,就用SqlSessionManager