一、MyBatis-Spring
MyBatis-Spring会将Mybatis无缝的接入到Spring容器中,也就是可以正常的使用Spring来管理Mybatis。先看一个整合demo。
@Configuration
public class Configure {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(new MyDataSource());
return factoryBean.getObject();
}
@Bean
public MapperFactoryBean mapperFactoryBean() throws Exception {
MapperFactoryBean bean=new MapperFactoryBean();
bean.setMapperInterface(UserMapper.class);
bean.setSqlSessionFactory(sqlSessionFactory());
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext a=new AnnotationConfigApplicationContext(Configure.class);
UserMapper u= (UserMapper) a.getBean(UserMapper.class);
;
System.out.println(u.getUser("1"));
}
}
以上是以注解的方式去整合Spring和Mybatis,非常的简单,其中有两个重要的信息就是SqlSessionFactoryBean 和MapperFactoryBean ,前者是获取到SqlSessionFactory 对象,后者返回接口的代理对象,所以先按照这个思路进行分析。
二、SqlSessionFactoryBean
他实现了FactoryBean< SqlSessionFactory>, InitializingBean, ApplicationListener< ApplicationEvent> 接口,并且都会实现对应的方法,首先afterPropertiesSet方法会被调用,这个方法通过调用buildSqlSessionFactory()方法来实例化SqlSessionFactory 对象。
buildSqlSessionFactory()比较多,大致逻辑如下:
- 定义好XMLConfigBuilder xmlConfigBuilder和Configuration targetConfiguration局部变量
- 判断是否已经有了Configuration, 如果存在,赋值给targetConfiguration
- 如果还没有Configuration,继续判断是否设置了configLocation,也就是mybatis的配置文件,存在就实例化XMLConfigBuilder 来获取Configuration,否则走4
- 直接new一个Configuration对象
- 设置基本上属性,以及环境对象
- 如果xmlConfigBuilder不为空,调用其parse()方法
- 是否配置了mapperLocations属性,也就是mapper映射文件,配置了就通过XMLMapperBuilder解析映射文件,逻辑和mybatis一样
- 通过sqlSessionFactoryBuilder对象返回SqlSessionFactory,这和mybatis中的逻辑是一样的
所以SqlSessionFactoryBean会通过afterPropertiesSet实例化SqlSessionFactory对象,然后通过getObject() 方法返回,需要注意的是我们要传入对应的数据源,此时我们可以通过spring容器来获取SqlSessionFactoryBean实例,然后通过这个实例得到对应的SqlSessionFactory对象。
三、MapperFactoryBean
第二部分,我们可以通过spring来得到SqlSessionFactory,但是SqlSession以及Mapper接口都还没有通过spring容器来管理,这部分工作就是MapperFactoryBean所作的工作。
先看MapperFactoryBean的继承关系:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>{}
继承自SqlSessionDaoSupport ,实现了FactoryBean接口。第一部分的demo中我们需要设置对应的mapper接口和SqlSessionFactory对象。从结构来看一个mapper接口对应一个MapperFactoryBean对象。我们先分析setSqlSessionFactory方法,实现在父类中。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if(this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
//创建一个sqlSessionTemplate实例,先不用管这个对象
this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
}
}
我们继续查看SqlSessionDaoSupport 的父类DaoSupport,他实现了InitializingBean接口,所以会先执行afterPropertiesSet方法。
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//检查配置类
this.checkDaoConfig();
try {
//默认是空方法
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
checkDaoConfig方法的实现在子类MapperFactoryBean中。
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if(this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
//调用配置类对象的addMapper方法
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
此时Spring容器中保存的是MapperFactoryBean对象,他是一个FactoryBean实现,如果要获取本身对象只能是需要加上“&”,所以本例中要拿到UserMapper对象,会从MapperFactoryBean的getObject方法中获取。
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()方法返回的是SqlSessionTemplate实例,是SqlSession的一个实现,其getMapper方法和DefaultSqlSession中的getMapper方法是一样的调用逻辑。MapperFactoryBean每次只能添加一个接口,这不利于我们的开发,所以我们需要批量设置的时候可以使用MapperScannerConfigurer只需配置好SqlSessionFactory和一个basePackage即可。
四、SqlSessionTemplate
这部分分析一下MyBatis-Spring中SqlSession的实现,SqlSessionTemplate的创建是在MapperFactoryBean实例调用其setSqlSessionFactory方法时创建。
new SqlSessionTemplate(sqlSessionFactory);
其中有个重要属性sqlSessionProxy,SqlSession中的增删改成方法的调用都是通过sqlSessionProxy来实现的,他是一个动态代理对象,主要代理逻辑在SqlSessionInterceptor的invoke方法。从源码中可以知道,他的提交、回滚、关闭方法都是不可用的,直接抛异常(不能进行手动操作),所以我们主要来看一下SqlSessionInterceptor的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//重新获取一个SqlSession实例,默认得到的是DefaultSqlSession实例,因为每次执行语句都是用新的SqlSession对象,所以解决了mybatis中线程不安全的问题
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
//执行方法
Object result = method.invoke(sqlSession, args);
//如果当前执行了事务,则提交事务
if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if(translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
//关闭SqlSession
if(sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
从invoke方法可知,SqlSessionProxy又重新实例化了一个SqlSession对象来执行具体的操作。
五、事务工厂
我们知道,mybatis中配置数据源的时候,会选配置一个事务工厂,JDBC和MANAGER,MyBatis-Spring中也创建了一个事务工厂SpringManagedTransactionFactory->new SpringManagedTransaction(dataSource)。所以mybatis的事务可以交给Spring中的事务管理。
到此,整个Mybatis的源码分析过程就已经告一段落,若有什么需要改进的、有什么不对的地方、请留言!谢谢!