mybatis-spring包主要作用:
- 将SqlSession 等对象交给Spring管理,由SpringIoC容器将SqlSession 对象注入到其他Spring Bean中
- 将自动扫描mapper注入到SpringIoC容器
- 将MyBatis的事务交给Spring 来管理
如何管理SqlSession、mapper
在没有spring之前如何使用mybatis
@Test
public void testSelectByExample() {
InputStream in = DopAppRecordMapperTest.class.getClassLoader().getResourceAsStream("mybatisTestConfiguration/DopAppRecordMapperTestConfiguration.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
DopAppRecordMapper mapper = sqlSessionFactory.getConfiguration().getMapper(DopAppRecordMapper.class, sqlSession);
List<DopAppRecord> dopAppRecord = mapper.selectByExample(new DopAppRecordExample());
}
所以自然而然的spring要管理SqlSession,spring就要解决初始化,才能管理SqlSessionFactory,才能管理sqlSession,进而才能管理mapper
1. SqlSessionFactoryBean – 管理SqlSessionFactory
SqlSessionFactoryBean这个类实现了三个接口:InitializingBean、FactoryBean、ApplicationListener
InitializingBean接口:实现了这个接口,那么当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean 的时候所需要的逻辑。
FactoryBean接口:实现了该接口的类,在调用getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例。
ApplicationListener接口:实现了该接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理
在spring调用InitializingBean、FactoryBean接口的时候,SqlSessionFactoryBean最后都会执行一个私有方法:buildSqlSessionFactory()
SqlSessionFactoryBean有下面这些字段,和Configuration都差不多,buildSqlSessionFactory()方法作用就是从原有的Configuration或者xml配置文件里构建Configuration,
最后会走new DefaultSqlSessionFactory(config)来创建SqlSessionFactory

2. SqlSessionTemplate – 管理sqlSession
SqlSessionTemplate 是MyBatis-Spring 的核心,代替MyBatis 中的DefaultSqlSession 的功能,所以可以通过SqlSessionTemplate 对象完成指定的数据库操作。
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;
//创建动态代理对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先尝试从Spring事务管理器中获取SqlSession对象,如果获取成功则直接返回,
//否则通过SqlSessionFactory 新建SqlSession 对象井将其交由Spring事务管理器管理后返回
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
Object result = method.invoke(sqlSession, args);
// 事务不由Spring 进行管理,sqlSession提交事务
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;
}
}
SqlSessionUtils.getSqlSession()方法: sessionFactory.openSession() 在这里调用
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
//从Spring 事务管理器中获取SqlSessionHolder ,其中封装了SqlSession 对象
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//获取SqlSessionHolder 中封装的SqlSession 对象
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//获取SqlSessionHolder 中封装的SqlSession 对象
session = sessionFactory.openSession(executorType);
//将SqlSession 对象与Spring 事务管理器绑定
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
SqlSessionTemplate 是线程安全(因为没涉及到共享变量,SqlSession实际是在ThreadLocal里)的,可以在DAO(Data Access Object,数据访问对象)之间共享使用 即所有的Dao执行方法都会走SqlSessionTemplate,然后走SqlSessionUtils.getSqlSession()拿到DefaultSqlSession,如果有事务的话会共用同一个DefaultSqlSession, 否则每次都会创建、关闭,所以一级缓存在没有事务的时候是不生效的
3. SqlSessionDaoSupport、MapperFactoryBean – 管理mapper
通过sqlSession 字段维护了一个SqlSessionTemplate,现在dao层就可以下面这么写,注入到springIoc容器中
@Repository
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getUser (String userId) {
return (User) getSqlSession().selectOne("com.xqw.mapper.UserMapper.getUser", userId) ;
}
}
但是每个类的要这么写很麻烦,要手动注入,MapperProxy也没用到,引出MapperFactoryBean
MapperFactoryBean实现FactoryBean,spring通过FactoryBean.getObject()可以将Mapper 接口注入到Service 层的Bean中
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
protected void checkDaoConfig() {
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} ...
}
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
spring要调用FactoryBean.getObject(),就必须写xml配置才会调,才能注入到springIoc容器中:
<!-配置id 为userMapper 的Bean ->
<bean id = "userMapper" class = "org.mybatis.spring.mapper.MapperFactoryBean">
<! -配置Mapper 接口->
<property name = "mapperinterface" value = "com.xqw.mapper.UserMapper"/>
<! -配置SqlSessionFactory ,用于创建底层的SqlSessionTemplate ->
<property name = "sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
4. MapperScannerConfigurer – 自动扫描mapper
自动扫描包下的mapper MapperScannerConfigurer 实现了BeanDefinitionRegistryPostProcessor 接口,该接口中的 postProcessBeanDefinitionRegistry()方法会在系统初始化的过程中被调用
最后会走ClassPathMapperScanner.processBeanDefinitions()方法,会对doScan()方法中扫描到的
BeanDefinition集合进行修改,主要是将其中记录的接口类型改造为MapperFactoryBean 类型,
井填充MapperFactoryBean所需的相关信息,这样,后续即可通过MapperFactoryBean完成相应功能了。
如何管理事务?
SpringManagedTransaction
在buildSqlSessionFactory()中有以下代码,如果配置文件中没有指定transactionFactory,那么会用默认的SpringManagedTransactionFactory
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
//SpringManagedTransactionFactory没啥特殊的,实现了org.apache.ibatis.transaction.TransactionFactory,工厂直接调用SpringManagedTransaction的构造方法
public class SpringManagedTransactionFactory implements TransactionFactory {
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
}
SpringManagedTransaction的实现:
SpringManagedTransaction的connection 字段维护的JDBC 连接来自Spring 事务管理器,当应用不再使用该连接时,会将其返还给Spring 事务管理器
public class SpringManagedTransaction implements Transaction {
private Connection connection; // 当前事务管理中维护的数据库连接对象
private final DataSource dataSource; // 与当前数据库连接对象关联的数据派对象
private boolean isConnectionTransactional ; // 标识该数据库连接对象是否由Spring 的事务管理器管理
private boolean autoCommit ; // 事务是否自动提交
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
@Override
private void openConnection() throws SQLException {
//从Spring 事务管理器中获取数据库连接对象
//首先尝试从事务上下文中获取数据库连接,如果获取成功则返回该连接,否则从数据源获取数据库连接并返回
// 底层是通过基于TransactionSynchronizationManager.getResource()静态方法实现的,在
// applicationContext.xml 中配置的事务管理器DataSourceTransactionManager 中,也是通
// 过该静态方法获取事务对象,并完成开启/关闭事务功能的
this.connection = DataSourceUtils.getConnection(this.dataSource);
// 记录事务是否自动提交,当使用Spring 来管理事务时,并不会由SpringManagedTransaction 的
// commit()和rollback()两个方法来管理事务
this.autoCommit = this.connection.getAutoCommit();
// 记录当前连接是否由Spring 事务管理器管理
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
}
//DataSourceUtils.getConnection
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
//从spring事务上下文中获取数据库连接,如果获取成功则返回该连接
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// 否则从数据源获取数据库连接
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
//注册到事务管理器中
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
spring-mybatis调用栈
该例子时使用了事务、分页插件调用栈
相比于
多了 事务、SqlSessionTemplate、分页插件、以及最后在StatementHandler自定义同步拦截插件 四个代理
spring-mybatis SqlSessiom生命周期
一次有事务的会话
先由单例SqlSessionTemplate代理,从ThreadLocal里看是否有SqlSession,没有创建一个DefaultSqlSession

由org.mybatis.spring.transaction.SpringManagedTransactionFactory创建出:
org.apache.ibatis.transaction.Transaction -> org.mybatis.spring.transaction.SpringManagedTransaction
再用事务创建出Simple和Cache组合的Executor,都会创建CacheExecutor这个代理,缓存是否有效是通过某个namespace是否需要缓存判断的
org.apache.ibatis.executor.Executor
Executor:在执行的时候会调用SpringManagedTransaction.getConnection,从ThreadLocal获取
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = this.transaction.getConnection();
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
没事务的会话
每次都会走OpenSession(),也会创建SpringManagedTransaction,但是是一次性的