1 SqlSession
sqlSessionFactoryBean,主要作用是通过getObject得到sqlSessionFactory,同时可以设置数据源,mybatis基本配置等。
sqlSessionFactory,用于创建sqlSession的工厂方法。
sqlSession,执行sql命令的会话。
MapperFactoryBean,创建mapper的工厂类,getObject()得到mapper接口的动态代理生成的代理类,它继承SqlSessionDaoSupport来间接操作SqlSessionTemplate。即getObject()->getMapper()
@MapperScan,会扫描目标目录的所有Mapper接口,并名字定义成mapper接口名,但是class类名是MapperFactoryBean,里面的有一个属性是mapper的权限名。如此启动项目,加载单例bean到上下文容器的时候,调用getObject会调用SqlSessionTemplate的getMapper(),得到代理mapper对象。
@Mapper,若没有使用@MapperScan【@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })】,而使用@Mapper,原理也类似,会扫描主启动类目录下的所有标注了@Mapper的mapper接口文件
1.2 生命周期
对象 | 作用域 | 说明 |
SqlSessionFactoryBuilder | 方法局部(method) | 工厂模式,创建后就不再需要了,因为就是为了创造SqlSessionFactory的工具类 |
SqlSessionFactory | 应用级别(application) | 连接池。创建就一直存在,程序运行就创建,程序关闭才释放 |
SqlSession | 请求和操作 | 类似JDBC的一个Connection对象。因此每次使用完就应该要关闭,这样才能回收到SqlSessionFactory中继续利用,而且SqlSession不是线程安全的,不能被共享。 |
Mapper | 方法 |
在编程式的开发中,SqlSession 我们会在每次请求的时候创建一个,但是Spring里面只有一个SqlSessionTemplate(默认是单例的),多个线程同时调用的时候怎么保证线程安全?
1.2 SqlSession接口的实现类
1.2.1 SqlSessionTemplate (为什么线程安全)
从这个构造方法可以看出,sqlsessionTemplate传参是必须需要一个sqlsessionfactory的,sqlsessionTemplate在执行crud操作时,都不是通过唯一的一个sqlsession来执行的,他都是通过动态代理来执行具体的操作的,所以多个线程持有同一个sqlsessionTemplate是不会产生线程安全问题的。
SqlSessionTemplate是sqlSession的实现类,是线程安全的。里面有一个动态代理的SqlSession sqlSessionProxy;代理sqlSessionProxy执行invoke方法的时候,每次invoke方法都是新生成一个SqlSession来执行,这样就保持了线程安全。
还有一个点,getMapper()方法是得到一个mapper接口的代理对象,且会传入this即本sqlSessionTemplate,最终调用sql语句的时候还是使用代理的sqlSessionProxy(实际还是invoke的新的SqlSession)。【2个动态代理】
//获取代理mapper对象,且一直传入this即本sqlSessionTemplate对象,执行sql语句的时候使用的代理sqlSessionProxy,
//它又使用invoke方法的新的sqlSession
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
SqlSessionTemplate 里面有DefaultSqlSession 的所有的方法:selectOne()、selectList()、insert()、update()、delete(),不过它都是通过一个代理对象实现的。这个代理对象在构造方法里面通过一个代理类创建:
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionTemplate.SqlSessionInterceptor());
所有的方法都会先走到内部代理类SqlSessionInterceptor 的invoke()方法:
/** 代理需要将MyBatis方法调用路由到从Spring的事务管理器获得的适当SqlSession
* Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
* unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
* {@code PersistenceExceptionTranslator}.
*/
//SqlSessionInterceptor内部代理类
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//新的sqlSession
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);
}
}
}
}
从Spring事务管理器中得到一个SqlSession,如果需要创建一个新的。
从当前事务之外得到一个SqlSession,如果没有就创造一个新的。然后,如果事务被打开,且事务管理器是SpringManagedTransactionFactory时,将得到的SqlSession同当前事务同步
1.2.2 SqlSessionManager
SqlSessionManager是sqlSession的实现类,是线程安全的。里面也有一个动态代理的SqlSession sqlSessionProxy;同时维护一个ThreadLocal localSqlSession,所以代理sqlSessionProxy执行invoke方法的时候,就是拿各自线程的ThreadLocal的SqlSession。
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionManager.SqlSessionInterceptor());
}
可以看出他的一个必要的参数也是sqlsessionFactory,SqlSessionManager既实现了SqlSessionFactory,也实现了SqlSession,具备生产SqlSession的能力,也具备SqlSession的能力,SqlSession的作用是执行具体的Sql语句
localSqlSession,这个属性其实就是一个ThreadLocal类,可以为每一个线程分配一个副本对象,来保证线程安全。
sqlsessionManager他把构造方法私有化了,想要创建一个sqlsessionManager对象,你只能调用newInstance()来创建一个SqlsessionManager对象,下面来看连接对象Connection他是怎么获取的
public Connection getConnection() {
SqlSession sqlSession = (SqlSession)this.localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot get connection. No managed session is started.");
} else {
return sqlSession.getConnection();
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var19) {
throw ExceptionUtil.unwrapThrowable(var19);
}
} else {
SqlSession autoSqlSession = SqlSessionManager.this.openSession();
Throwable var6 = null;
Object var8;
try {
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var8 = result;
} catch (Throwable var20) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var20);
}
} catch (Throwable var21) {
var6 = var21;
throw var21;
} finally {
if (autoSqlSession != null) {
if (var6 != null) {
try {
autoSqlSession.close();
} catch (Throwable var18) {
var6.addSuppressed(var18);
}
} else {
autoSqlSession.close();
}
}
}
return var8;
}
}
DefaultSqlSession(线程不安全)
DefaultSqlSession是sqlSession的实现类,普通的一次请求,不安全的。
mybatis在底层都是使用的JDBC,而JDBC这本来就是线程不安全的(连接对象Connection只有一个)
//之后走到该类的这个方法里
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
//开始创建事物
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//将事物传递给执行器Executor,这个是session执行数据库操作的核心(有三种执行器类型)
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最后sqlsession执行都是通过执行器执行的,默认执行器是SimpleExecutor,她通过连接Connection这个类创建了Statement这个JDBC要用到的对象,开始走JDBC的流程:
//查询方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 这里创建statement对象,这个方法中就用到了Connection连接对象,此时我们主要看这个方法中Connection的创建时怎么样的
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
prepareStatement(handler, ms.getStatementLog())方法解析(重点看Connection他是怎么拿的)
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 看下面的方法,此时只需要看这个方法
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
//由这个方法可以看出,具体实现是transaction.getConnection();
protected Connection getConnection(Log statementLog) throws SQLException {
// 这里最终同通过创建Executor时传入的transcation进行了连接获取
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
transaction.getConnection();
//可以看出他只会产生一个连接
@Override
public Connection getConnection() throws SQLException {
// 这里只要有连接了就不重新打开连接了(从数据源中再次获取),说明只能有一个连接在一个org.apache.ibatis.transaction.Transaction中
if (connection == null) {
openConnection();
}
return connection;
}
最终可以看出一次SqlSession的执行最终只会产生一个connection,所以我们设想一下,在两个线程通过同一个sqlsession来执行crud,那么就有可能,我先跑完的线程,把唯一的这一个连接给关闭掉,从而造成另一条线程的逻辑不被成功执行,所以通过DefaultSqlSession来执行数据库操作是线程不安全的。
1.3
- DefaultSqlSession的内部没有提供像SqlSessionManager一样通过ThreadLocal的方式来保证线程的安全性;
- SqlSessionManager是通过localSqlSession这个ThreadLocal变量,记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一个线程多次创建SqlSession对象造成的性能损耗;
- DefaultSqlSession不是线程安全的,我们在进行原生开发的时候想要达到线程安全的话,那就需要每次为一个操作都创建一个SqlSession对象,其性能可想而知