引言
在demo: springboot+mybatis的MybatisConfig中有这样两个bean配置(事务此时不看):
// 数据源配置
@Bean
public DataSource dataSource() {
// mybatis自带的一个简易数据库连接池,只是为了debug代码,这个就不关心了
PooledDataSource pooledDataSource = new PooledDataSource();
pooledDataSource.setDriver(environment.getProperty("mysql.driver"));
pooledDataSource.setUsername(environment.getProperty("mysql.username"));
pooledDataSource.setPassword(environment.getProperty("mysql.passwd"));
pooledDataSource.setUrl(environment.getProperty("mysql.url"));
return pooledDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:/mapper/*.xml"));
return sqlSessionFactoryBean;
}
dataSource数据源配置的bean,是通过依赖注入传递给SqlSessionFactoryBean这个bean了。
问题点在于SqlSessionFactoryBean在整个容器初始化过程中起了什么作用。
@MappserScan
mybatis-spring:@MapperScan注解中解释使用这个注解,完成两件事:
1. 扫描指定接口
2. 注册这些接口的bean定义到spring容器(实际是FactoryBean定义)
然后spring容器在对Dao层接口注入的时候,实际注入的是一个Mybatis创建的代理对象:MapperProxy,
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
}
看构造方法和属性字段,mapperInterface是被代理的接口,methodCache是接口的方法,另外一个关键属性是sqlSession。
SqlSession是MyBatis 的一个主要 Java 接口。通过它的实现可以执行SQL命令、执行事务等。所以它是执行SQL的关键。现在需要确认的是SqlSession与SqlSessionFactoryBean的关系。
SqlSessionFactoryBean
见名知义,SqlSessionFactoryBean是创建SqlSessionFactory的:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {}
它也实现了FactoryBean,并且实现了InitializingBean接口的afterPropertiesSet()方法。这样在spring容器实例化这个bean的时候,便会回调afterPropertiesSet()方法:
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
来创建SqlSessionFactory实例,然后看下它对FactoryBean的接口方法实现:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* {@inheritDoc}
*/
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
spring容器中某个bean的字段需要依赖注入SqlSessionFactory实例的时候,会调用getObject方法,返回SqlSessionFactory实例。
那么哪个bean的属性需要注入SqlSessionFactory?答案是:MapperFactoryBean。
上文说扫描到的每个接口注册的工厂bean定义的就是MapperFactoryBean这个bean定义。
它的用处就是注入service的dao层接口属性(如在ServiceImpl的属性UserDao)的代理对象的时候,会返回这个MapperProxy代理对象。mybatis-spring:@MapperScan注解文中有提到解释。
问题来了,这里注入的是SqlSessionFactory对象,MapperProxy需要的是SqlSession实现类。答案看下面:
MapperFactoryBean继承自SqlSessionDaoSupport:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}
在父类SqlSessionDaoSupport里有方法:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
而SqlSessionTemplate是SqlSession的实现:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
}
而它的sqlSessionProxy实例是从它的构造方法里构造的:
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());
}
所以MapperProxy的SqlSession实例,实际是SqlSessionTemplate实例,下层再委托给sqlSessionProxy代理对象进行实际调用。
因此,注入ServiceImpl的UserDao属性字段时,发生以下过程:
1. spring容器注入UserDao属性的MapperProxy实例时,依赖MapperProxy实例
2. 此时实例MapperFactoryBean来创建MapperProxy实例
3. 创建MapperFactoryBean需要依赖注入SqlSessionFacotry实例
4. 使用SqlSessionFactoryBean来创建SqlSessionFactory实例
最终完成整个过程,注入userDao属性的值 MapperProxy实例。