一、spring 与mybaits集成
1、这里主要讲通过配置方式来集成,主要在以下几个方面体现:
- Spring 将会加载必要的 MyBatis 工厂类和 session 类;
- 提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean
- 方便集成 spring 事务;
- 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常)中;
1、加入配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.enjoylearning.mybatis.entity" />
//配置 SqlSessionFactoryBean 扫描 XML 映射文件的路径, 可以使用 Ant 风格的路径进行配置。
<property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
</bean>
!-- DAO接口所在包名,Spring会自动查找其下的类 -->
//通过 MapperScannerConfigurer 类自动扫描所有的 Mapper 接口,使用时可以直接注入接口。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
</bean>
<!-- (事务管理)transaction manager -->
//配置事务,让 Mybatis 集成 spring 的事务
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
<qualifier value="transactionManager" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
二、MyBatis-Spring 集成原理分析
(1)、SqlSessionFactoryBean 源码分析
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
private Resource configLocation;
private Configuration configuration;
}
SqlSessionFactoryBean 实现了 InitializingBean 接口, 那么容器在初始化完成 SqlSessionFactoryBean 之后必然会调用 afterPropertiesSet()方法,如下图所示,其中调用的 buildSqlSessionFactory()方法实际是对 MyBatis 初始化加载配置阶段的封装;
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
SqlSessionFactoryBean 实现了 FactoryBean接口, 实出了getObject方法,将SqlSessionFactory 注入到IOC:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
这样就将sqlSessionFactory对象注入到了IOC容器,IOC 容器中的其他类型能拿到 SqlSession 实例了,就可以进行相关的 SQL 执行任务了;
(2)MapperScannerConfigurer源码分析:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
......
}
这个类继承了BeanDefinitionRegistryPostProcessor接口,这个类是IOC容器后置处理器,在注入完bean定义后,会执行实现这个接口的类方法postProcessBeanDefinitionRegistry,这个方法扫描完这些 mapper 接口之后,主要是将 Mapper 接口一个个的转换成 MapperFactoryBean 之后注入IOC容器。
查看postProcessBeanDefinitionRegistry方法:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
查看 ClassPathMapperScanner .scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));方法:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
processBeanDefinitions方法将Mapper 接口一个个的转换成 MapperFactoryBean 之后注入容器
(3)MapperFactoryBean ,getObject最终注入不同代理对象
可以看到MapperFactoryBean实现了FactoryBean接口,所以最终注入的是getObject方法返回的对象
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}