1. Spring集成Mybatis代码示例
Spring在集成Mybatis时,使用SqlSessionFactoryBean
来完成Configuration
的解析,代码如下:
@EnableTransactionManagement
@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig { // =====> spring.xml
//用SqlSessionFactoryBean来代替Mybatis中解析Configuration的过程
@Bean
public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//设置数据源
factoryBean.setDataSource(dataSource());
// 设置 MyBatis 配置文件路径
factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
// 设置 SQL 映射文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
factoryBean.setTypeAliases(User.class);
return factoryBean;
}
//数据源
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_example");
return dataSource;
}
2. Spring 如何解析Mybatis配置文件
Spring 是根据bean对象SqlSessionFactoryBean
来解析xml文件到Configuration
类中的,首先来看一下SqlSessionFactoryBean
这个类:
//SqlSessionFactoryBean 实现了FactoryBean 和 InitializingBean
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
//内部封装了Configuration 类
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
//类型处理器
private TypeHandler<?>[] typeHandlers;
private String typeHandlersPackage;
//类型别名
private Class<?>[] typeAliases;
private String typeAliasesPackage;
可以看到SqlSessionFactoryBean
内部封装了Configuration
类,
实现了FactoryBean
,会创建getObject
中返回的单例sqlSessionFactory
对象,这个对象就是我们Mybatis中最开始的DefaultSqlSessionFactory
对象!
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//如果sqlSessionFactory 为空,则先创建sqlSessionFactory !
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
还实现了InitializingBean,InitializingBean
中的afterPropertiesSet
方法会在类初始化完成后调用,而上边的getObject() 方法在获取对象时会调用这个方法生成sqlSessionFactory
@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");
//创建sqlSessionFactory 的方法
this.sqlSessionFactory = buildSqlSessionFactory();
}
核心方法是buildSqlSessionFactory
,在这个方法中,大部分都是为Configuration
对象赋值!解析过程与Mybatis解析过程类似!最后通过建造者模式返回sqlSessionFactory
的实现
DefaultSqlSessionFactory
!
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//局部变量Configuration
Configuration configuration;
.......
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
//设置类型别名
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
//添加插件
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
//设置类型处理器
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
//缓存
if (this.cache != null) {
configuration.addCache(this.cache);
}
//事务管理器
//SpringManagedTransactionFactory是新定义的事务管理器,它使用Spring事务中的dataSource ,从而达到跟事务集成
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
//环境
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
....... 省略
//建造者模式 返回 DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
注意:
- Spring集成mybatis,在处理事务时,事务工厂会使用一个新的
new SpringManagedTransactionFactory
- 而不是MyBatis之前的
ManagedTransactionFactory
, 这个SpringManagedTransactionFactory
会使用Spring事务同步管理器TransactionSynchronizationManager
中的dataSource
, 从而达到跟事务集成
3. Spring是怎么管理Mapper接口的动态代理的
Spring与Mybatis
整合的最主要目的就是:把Mapper
接口的代理对象放入Spring容器,在使用时能够像使用一个普通的bean一样去使用这个代理对象,比如能被@Autowired
自动注入!
Sping的解决方案
在Mapper接口上加@Component
注解时,Spring会在扫描@CompopnentScan
指定的路径时,过滤掉接口、抽象类,不为他们生成BeanDefinition
,这就导致了Mapper
接口无法实例化!
//判断是否是顶级类、非接口类、非抽象类等等,整合Mybatis时,需要重写这个方法,让Mapper接口也加入集合!
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
//如果不是顶级类、接口类、抽象类,则加入到集合中!!!
candidates.add(sbd);
}
else {
//如果是顶级类、接口类、抽象类,则不予处理!!!
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
======== isCandidateComponent 判断是否是顶级类、非接口类、非抽象类等等=======
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
/**
* 判断是否是顶级类、非接口类、非抽象类等等
*/
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
然而,Spring在整合Mybatis时,为了让Mapper接口注册成BeanDefinition,重写了isCandidateComponent 方法!!,他让这个方法忽略了接口,让接口也可以被扫描到。
但是接口也可以被扫描到也不能改变什么啊,因为Mapper接口只是一个接口,还没有为他生成代理类呢!!Spring为了解决这个问题,使用了FactoryBean的getObject方法进行了偷天换日, 在Mapper
接口的BeanDefinition
中设置其beanClass
为Factorybean.class
,这样每个Mapper
接口在生成实例的时候生成的是Factorybean
的getObject
方法中返回的代理类了!
再通过自定义类实现bean
工厂后置处理器BeanDefinitionRegistryPostProcessor
,重写postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法,修改成带有Factorybean.class
的BeanDefinition
,这样就会按照我们自定义的BeanDefinition
去生成对象! 这就完美解决了问题
伪代码如下:
//扫描到的 Mappers
Set mappers = new HashSet();
//遍历 Mappers
for (Object mapper : mappers) {
//新建一个bean定义
RootBeanDefinition beanDefinition = new RootBeanDefinition();
//偷天换日,设置MyfactoryBean的getObject方法返回的对象为mapper的Class
beanDefinition.setBeanClass(MyfactoryBean.class);
//每一次循环都为当前mapper创建动态代理
beanDefinition.getPropertyValues().add("mapperInterFace",mapper.class);
//最后注册成bean定义
registry.registerBeanDefinition("userMapper",beanDefinition);
}
4. Spring整合Mybatis源码大致流程
- spring会重写
isCandidateComponent
方法,来扫描到所有的mapper
接口,并将所有mapper
的 bean定义中的class
类型指向MapperFactoryBean
; - Spring在创建
UserServiceImpl
实例的时候,发现其内部有@Autowired
了UserMapper
接口,那么就会去spring
容器获取UserMapper
实例,没有则进行创建 - 创建
UserMapper
实例的时候,根据bean定义创建的实例 实际上是MapperFactoryBean
实例,然后再利用MapperFactoryBean
的getObject
方法获取mapper的代理实例(调用MapperFactoryBean的getObject方法,mybatis会利用jdk的动态代理创建mapper代理对象
MapperFactoryBean 类如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
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 {
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();
}
}
}
// getObject获取对应Mapper的代理类
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
public boolean isAddToConfig() {
return this.addToConfig;
}
}
评论区