mybatis源码分析

16,056 阅读5分钟

mybatis源码分析

mybatis的流程分析

首先mybatis的源码分两种情况:

  1. 单独的mybatis
  2. 和spring整合的mybatis源码 这两种情况下的源码分析会有点不同,比如如果是分析mybatis+spring的模式,那么mybatis的开始就是从spring的初始化的时候开始。

在整合spring的情况下,mybatis从哪里开始

首先分析得知mybatis和spring整合的情况下,主要是提供两处关联

  • @MapperScan
  • @Bean---->SqlSessionFactoryBean 通过源码得知MapperScan其实主要利用spring的Import技术和spring的扩展点ImportBeanDefinitionRegistrar, 而@Bean仅仅是利用了javaconfig技术配置了一个对象。如果你精通spring技术就可以知道spring最先处理的import也就是MapperScan

@MapperScan

如果你熟悉spring,对spring的初始化流程精通的话,那么就知道spring的初始化大概可以分为两大部分

  1. springBean的实例化之前的工作
  2. spring实例之中和之后的工作
springBean实例化之前的工作

通过分析源码可以得出@MapperScan主要做了3个事情

  1. 扫描出所有的mapper所对应的BeanDefinition。
  2. 把mapper变成FactoryBean,MapperFactoryBean的BeanDefinition
  3. 为BeanDefinition添加一个构造方法的值,因为mybatis的MapperFactoryBean有一个有参构造方法, spring在实例化这个对象的时候需要一个构造方法的值,这个值是一个class,后面spring在实例化过程中 根据这个class返回我们的代理对象
# 1.把mapper变成FactoryBean
MapperScannerRegistrar.registerBeanDefinitions()
ClassPathMapperScanner.doScan()
ClassPathMapperScanner.processBeanDefinitions()

//这里就是将这个class传递到有参构造方法
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//将所有的mapper接口改为MapperFactoryBean类
definition.setBeanClass(this.mapperFactoryBean.getClass());


public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
}

public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

# 2.spring实例化该BeanDefinition

ConstructorResolver.autowireConstructor()

//将传递给构造器的参数,获取到传递过来的mappeer接口
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();

spring实例化之中和之后的工作

mybatis主要利用spring的初始方法扩展点来完成对mapper信息的初始化,比如sql语句的初始化,这里的 spring扩展点主要就是afterPropertiesSet,如果你精通spring就会知道afterPropertiesSet这种机制 是如何工作的,说白就是利用MapperFactoryBean去实现initialzingBean,由于MapperFactoryBean是一个 FactoryBean,我们理解为就是一个mapper,所以又可以理解他就是自己获得自己的信息,然后把信息缓存起来, 缓存到一个map当中。mappedStatements

org.apache.ibatis.session.Configuration#mappedStatements
org.springframework.dao.support.DaoSupport#afterPropertiesSet
org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig
org.apache.ibatis.session.configuration#addMapper(this.mapperInterface);
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse();
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement()

关键代码
 SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

相关流程图

在这里插入图片描述

mybatis一级缓存的各种问题

1.为什么mybatis在整合spring一级缓存就失效了?

因为mybatis和spring的集成包当中扩展了一个类sqlSessionTemplate这个类在spring容器启动的时候被注入给了mapper。 这个类替代了原来的DefaultSqlSession,SqlSessionTemplate当中所有的查询方法不是直接查询,而是经过一个代理对象,代理对象增强了查询方法,主要是关闭了session。

  • 底层原理分析:
  • 在跟踪源码,可以看到这个sqlSession已经被改成了sqlSessionTemplate *在这里插入图片描述
  • 进入到sqlSessionTemplate类中,可以发现原来的DefaultSqlSession已经被增强了。
  • 在这里插入图片描述
  • 接着再跟踪到下一步,可以看到已经到DefaultSqlSession代理类的invocationHandler(jdk动态代理)
  • 在这里插入图片描述

和mybatis原生的mapper查询比较,可以看到原生的是使用DefaultSqlSession对象进行查询

  • 在这里插入图片描述

总结:通过源码分析可以看到mybatis一级缓存失效的原理,是sqlSessionTemplate的增强。 思考:为什么这里需要关闭sqlSession?

因为mybatis和spring整合后,将mybatis管理的mapper直接交给了spring管理,但是mybatis也没有将sqlSession暴露给spring,那么当spring执行完代码之后,就不能再拿到sqlSession对象,所以这里spring就只能在执行完之后,就将sqlSession关闭了,这也是没有办法的办法吧。

spring和mybatis整合之后,mybatis是怎么初始化的?

1.mybatis是怎么进行mapper接口扫描?

在上面已经谈论过了,mybatis和spring整合之后,是将mybatis所有的mapper接口,都交给了MapperFactoryBean处理。MapperFactoryBean是一个FactoryBean对象,实际返回的对象是getObject()方法返回的对象。

  • 进入源码分析阶段:
  • 1.通过@MapperScan扫描mybatis所有的mapper接口。原理是使用spring的import的扩展点(ImportBeanDefinitionRegistrar)
  • 在这里插入图片描述
  • 2.实现了ImportBeanDefinitionRegistrar,重写registerBeanDefinitions方法,拿到registry对象,可以往spring容器注册bean
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 3.开始扫描mapper接口
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 4.对扫描得到的mapper接口,进行处理。例如:添加属性,设置自动装配模式,改变beanClass,添加构造方法参数等。
  • 在这里插入图片描述

总结:这里主要是对mapper接口进行初始化的工作,最要是有3个作用。 1.通过@MapperScan注解,应用import技术,进行mapper扫描。 2.将扫描得到的mapper,改变beanClass,使用FactoryBean统一处理。 3.改变beanDefinition的自动装配模式,可以对MapperFactoryBean对象的属性进行自动装配,而不需要使用 @Autowired,避免对spring的强制依赖。

2.mybatis的Mapper接口怎么进行实例化的?
  • 1.mapper接口的MapperFactoryBean对象属性赋值。
  • 在这里插入图片描述
  • 在这里插入图片描述

通过查看MapperFactoryBean,可以看到相关属性,暂且估计以下属性是需要自动装配的: 1.mapperInterface 2.addToConfig 3.setSqlSessionFactory() 4.setSqlSessionTemplate()

  • 下面通过追踪源码进行分析:
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 接着跟踪到autowireByType方法。
  • 在这里插入图片描述
  • 在这里插入图片描述

总结:MapperFactoryBean需要自动装配的属性:sqlSessionFactory,addToConfig

  • 2.MapperFactoryBean接口实例化之后,怎么扫描该mapper接口对应的sql.xml或注解?

实现了InitializingBean接口,重写了afterPropertiesSet()方法,然后在这个方法进行初始化操作。 问题:为什么不使用@PostConstruct,而使用实现InitializingBean接口?

因为实现InitializingBean接口,重写了afterPropertiesSet()方法,afterPropertiesSet顾名思义,就是要等到所有Properties属性初始化,才执行这个方法。所以mybatis的初始化就是要等到所有属性都初始化了,才初始化mybatis的内容,例如:扫描mapper等操作。

查看MapperFactoryBean的结构有: MapperFactoryBean --- > SqlSessionDaoSupport ---> DaoSupport ---> impl InitializingBean 所有可以得到MapperFactoryBean ,实现了InitializingBean接口。

InitializingBean是spring的技术,实现了该类,需要重写afterPropertiesSet()方法。 afterPropertiesSet(),顾名思义就是在类所有属性property自动装配后执行。

  • 跟踪源码分析:
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 在这里插入图片描述

spring和mybatis整合总结

到此,mybatis整合到spring所有的关键点就已经分享到这里了。关键点总结一下吧: 1.@MapperSacn,import,ImportBeanDefinitionRegistrar 2.FactoryBean,InitializingBean,AutowireMode