Mybatis集成Spring原理

Mybatis-Spring集成原理

  1. 通过定义BD来最终集成到spring中
  2. 由于mapper文件时接口 所以没法直接通过class名来定义BD,需要通过动态代理生成代理类 所以可以考虑用FactoryBean来自定义Bean的具体实现
  3. 为了保证FactoryBean的通用性,不需要为每一个Mapper编写一个factoryBean 所以可以通过泛型来解决
  4. 考虑到应该动态添加BD到spring中 所以需要用到Import技术来动态添加DB
  5. 最后需要用到scan技术来扫描指定包下的所有接口,生成代理对象,定义为BD,最终通过ImportBeanDefinitionRegistrar 注册到Spring中
  • 引入依赖

    org.mybatis mybatis-spring 1.2.2
  • Spring启动
    ApplicationContext ctx = new ClassPathXmlApplicationContext(“bean.xml”);

  • Spring配置文件

  • 创建MapperScannerConfigurer

这个扫描器继承了spring的ClassPathBeanDefinitionScanner

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

判断包扫描路径下的Interface

sbd.getPropertyValues().add(“mapperInterface”, definition.getBeanClassName());
sbd.setBeanClass(MapperFactoryBean.class);
sbd.getPropertyValues().add(“sqlSessionFactory”, this.sqlSessionFactory);

将bd注入到beanfactory

再看看SqlSessionFactoryBean

两次赋值:
setDataSource
setMapperLocations

public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, “Property ‘dataSource’ is required”);
Assert.notNull(this.sqlSessionFactoryBuilder, “Property ‘sqlSessionFactoryBuilder’ is required”);
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
执行afterPropertiesSet

buildSqlSessionFactory()

首先通过一个关键字new创建了对象Configuration,这个对象是mybatis框架的一个核心类,在这里我们不做详细介绍,以后再剖析。
接着又创建了new SpringManagedTransactionFactory(),后面介绍这个类的作用,此处略过。
接着继续创建new Environment(this.environment, this.transactionFactory, this.dataSource),这个Environment类中持有事物工厂和数据源的引用。
接下来就是创建XMLMapperBuilder对象,并且调用了xmlMapperBuilder.parse()方法,这个方法的详细,不在此分析,也不是我们这篇文章要记录的重点,否则会偏离我们的主题,
parse()这个方法就是在解析mapperLocation变量所代表的就是mybatis的一个xml配置文件

xmlMapperBuilder.parse()方法执行完成之后,调用this.sqlSessionFactoryBuilder.build(configuration),这个sqlSessionFactoryBuilder 构造器在哪儿创建的呢?
其他它就是SqlSessionFactoryBean的一个私有类变量,初始化SqlSessionFactoryBean的时候,就实例化了这个sqlSessionFactoryBuilder。

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

总结起来,就是创建了几个对象,依次是mybatis的核心类Configuration、spring和mybatis集成的事物工厂类
SpringManagedTransactionFactory、mybatis的Environment类、mybatis的DefaultSqlSessionFactory类,同时还完成了对mybatis的xml文件解析,并将解析结果封装在Configuration类中。

执行onApplicationEvent方法

调用完上面的afterPropertiesSet方法之后,第二个被调用的就是onApplicationEvent方法,这个方法的调用时机是,spring容器初始化完成之后,该方法是接口ApplicationListener中的方法。

public void onApplicationEvent(ApplicationEvent event) {
    if (this.failFast && event instanceof ContextRefreshedEvent) {
        this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
}
调试发现this.failFast这个变量的值是false,所以这个方法不会执行,在此就不做重点分析了。

上面两个标签的解析过程分析完成,现在有两个问题没有答案,第一个是basePackage基础包下面扫描出来的mapper接口怎么实例化的?第二个是这两个标签创建出来的对象怎么配合使用的?

@Service
public class AuthUserServiceImpl implements IAuthUserService {
@Resource
private AuthUserMapper authUserMapper;

public AuthUserServiceImpl(){
    logger.info("创建 com.test.bean.AuthUserServiceImpl");
}

}

首先根据这个mapper的名字从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,AuthUserMapper对应的BeanClass就是MapperFactoryBean,
这是为什么呢?在上面分析的内容中提到过,也就是在创建MapperScannerConfigurer对象的时候设置的。

MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取authUserMapper对应的实现类,从上面图中可以看出来,最后返回的就是一个代理类,这个代理类使用jdk的动态代理创建出来的。

return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);

这个MapperProxy类就是InvocationHandler的实现类:

public class MapperProxy implements InvocationHandler, Serializable {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    } else {
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

}

程序在调用authUserMapper对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。