10、Mybatis Starter原理解析

谁说我不能喝 提交于 2020-02-26 00:53:05

1.1、Mybatis Starter作用

1) 自动检测工程中的DataSource
2) 创建并注册SqlSessionFactory实例
3) 创建并注册SqlSessionTemplate实例
4) 自动扫描mappers并将其注入BeanFactory,这样我们就直接可以@Autowired使用

1.2、Mybatis使用指南

下面介绍下在SpringBoot中引入Mybatis starter的步骤
1) 在pom文件中引入相关依赖,使用的SpringBoot的版本是2.1.7.RELEASE
<dependency>  
   <groupid>org.mybatis.spring.boot</groupid>  
   <artifactid>mybatis-spring-boot-starter</artifactid>  
   <version>2.1.0</version>  
</dependency>  
  
<dependency>  
   <groupid>mysql</groupid>  
   <artifactid>mysql-connector-java</artifactid>  
   <version>5.1.39</version>  
</dependency>
2) 使用mybatis逆向工程,现在pom中引入依赖
<build>  
    <!--单元测试时引用src/main/resources下的资源文件-->  
    <testresources>  
       <testresource> 
	   <directory>src/test/resources</directory>  
       </testresource> 
       <testresource> 
           <directory>src/main/resources</directory>  
       </testresource> 
    </testresources> 
	
 <plugins> 
    <plugin>
       <groupid>org.springframework.boot</groupid>  
       <artifactid>spring-boot-maven-plugin</artifactid>  
    </plugin>
    <plugin> 
       <groupid>org.mybatis.generator</groupid>  
       <artifactid>mybatis-generator-maven-plugin</artifactid>  
       <version>1.3.5</version>  
       <dependencies> 
	  <dependency>
	     <groupid>mysql</groupid>  
             <artifactid>mysql-connector-java</artifactid>  
             <version>5.1.38</version>  
          </dependency>
       </dependencies> 
    </plugin>
 </plugins>
</build>
3) 在resources目录下新建generatorConfig.xml文件

4) 配置数据库即Mybatis属性
spring.datasource.username=root  
spring.datasource.password=2109  
spring.datasource.url=jdbc:mysql://localhost:3306/test  
spring.datasource.driver-class-name=com.mysql.jdbc.Driver  
  
mybatis.mapper-locations=classpath:mapper/*.xml  
mybatis.type-aliases-package=com.lwh.springboot.bean  
#开启下划线-驼峰命名转换  
mybatis.configuration.map-underscore-to-camel-case=true
5) 在主配置类上加上包扫描
@SpringBootApplication  
@MapperScan("com.lwh.springboot.mapper")  
public class SpringbootApplication {

也可以是这样,二选一即可
@Mapper  
public interface UserMapper {
6) 运行generator逆向工程,如下图,发现在对应的位置中生成了bean、Mapper接口、Mapper文件

6) 写一个单元测试,发现可以插入表数据
@RunWith(SpringRunner.class)  
@SpringBootTest(classes = {SpringbootApplication.class})  
public class Springboot2ApplicationTests {  

  @Autowired  
  private UserMapper userMapper;    
  
  @Test  
  public void testInsert(){  
      User user = new User();  
      user.setName("xx");  
      user.setAge(25);  
      userMapper.insert(user);  
  }  
}

1.3、Mybatis starter原理解析

1.3.1、配置类引入原理

// 发现里面有两个自动配置类,MybatisAutoConfiguration和MybatisLanguageDriverAutoConfiguration,
MybatisLanguageDriverAutoConfiguration主要是用来处理注解版的SQL语句,观察其类,发现很多报红,不满足Condition条件.
我们主要看MybatisAutoConfiguration

@org.springframework.context.annotation.Configuration  
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })  
@ConditionalOnSingleCandidate(DataSource.class)  
@EnableConfigurationProperties(MybatisProperties.class)  
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })  
public class MybatisAutoConfiguration implements InitializingBean {
// 数据源的相关配置
@Configuration  
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })  
@EnableConfigurationProperties(DataSourceProperties.class)  
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })  
public class DataSourceAutoConfiguration {
// MybatisAutoConfiguration会给容器中注入两个关键的bean
@Bean  
@ConditionalOnMissingBean  
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// SqlSessionTemplate是Mybatis为了接入Spring引入的,其内部也是封装了SqlSession和SqlSessionFactory
@Bean  
@ConditionalOnMissingBean  
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {  
    ExecutorType executorType = this.properties.getExecutorType();  
    if (executorType != null) {  
       return new SqlSessionTemplate(sqlSessionFactory, executorType);  
    } else {  
       return new SqlSessionTemplate(sqlSessionFactory);  
    }  
}

public class SqlSessionTemplate implements SqlSession, DisposableBean {  
    private final SqlSessionFactory sqlSessionFactory;  
    private final ExecutorType executorType;
    // 里面的方法是通过SqlSessionProxy来执行的
    private final SqlSession sqlSessionProxy;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,  
                                                               PersistenceExceptionTranslator exceptionTranslator) {  
    this.sqlSessionFactory = sqlSessionFactory;  
    this.executorType = executorType;  
    this.exceptionTranslator = exceptionTranslator;  
	// 发现sqlSessionProxy是SqlSession的代理对象,其方法会被SqlSessionInterceptor代理执行
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),  
                                              new Class[] { SqlSession.class }, new SqlSessionInterceptor());  
}
private class SqlSessionInterceptor implements InvocationHandler {  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,  
                              SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);  
        try {  
            Object result = method.invoke(sqlSession, args); 
            return result;  
        }
    }
}

1.3.2、注解扫描原理

1.3.2.1、@Mapper注解扫描
// MybatisAutoConfiguration里面有两个静态内部类
@org.springframework.context.annotation.Configuration  
// 发现这个类主要是给容器中导入了一个组件,是AutoConfiguredMapperScannerRegistrar,也就是下面的类
@Import(AutoConfiguredMapperScannerRegistrar.class)  
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })  
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
// 这个类是ImportBeanDefinitionRegistrar实现,之前已经分析过了,可以给容器中导入一些bean定义
// This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use  
// {@link org.mybatis.spring.annotation.MapperScan}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware,
                                                           ImportBeanDefinitionRegistrar {
  private BeanFactory beanFactory;  
  
  @Override  
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  
     // 获取的就是主配置类所在包
     List<string> packages = AutoConfigurationPackages.get(this.beanFactory);  
     // 生成一个BeanDefinition,并设置相关属性,后面要用到
     BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);  
     builder.addPropertyValue("processPropertyPlaceHolders", true);  
     builder.addPropertyValue("annotationClass", Mapper.class);  
     builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));  
     BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);  
     Stream.of(beanWrapper.getPropertyDescriptors())  
        // Need to mybatis-spring 2.0.2+  
       .filter(x -> x.getName().equals("lazyInitialization")).findAny()  
       .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); 
     // 将这个BeanDefinition注册到容器中
     registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());  
  }  
}
// 上面注册的bean是MapperScannerConfigurer,发现它是BeanDefinitionRegistryPostProcessor
public class MapperScannerConfigurer  
      implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    // 会调用这个方法
    @Override  
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {  
    // 上面设置的,为true,这边会做属性占位符替换
    if (this.processPropertyPlaceHolders) {  
        processPropertyPlaceHolders();  
    }  
   
    // 创建一个ClassPathMapperScanner对象
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);  
    if (StringUtils.hasText(lazyInitialization)) {  
       scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));  
    }  
    // 关注一下这个方法
    scanner.registerFilters();  
    scanner.scan(  
      StringUtils.tokenizeToStringArray(this.basePackage, 
	                                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

public void registerFilters() {  
    boolean acceptAllInterfaces = true;  
  
    // if specified, use the given annotation and / or marker interface  
    if (this.annotationClass != null) {  
       // 添加一个IncludeFilter,实现注解扫描
       addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));  
       acceptAllInterfaces = false;  
    }
// scanner.scan
@Override  
public Set<beandefinitionholder> doScan(String... basePackages) {  
    // 这边就是扫描这个路径,然后通过IncludeFilter去过滤
    Set<beandefinitionholder> beanDefinitions = super.doScan(basePackages);  
  
    if (beanDefinitions.isEmpty()) {  
        // 打个日志记录下
    } else {  
	// 走这边,下面在1.3.3中分析这边
        processBeanDefinitions(beanDefinitions);  
    }  
    return beanDefinitions;  
}
1.3.2.2、@MapperScan注解扫描
@SpringBootApplication  
@MapperScan("com.lwh.springboot.mapper")  
public class SpringbootApplication {
// 上面我们分析了@Mapper实现扫描,现在看下@MapperScan,发现它注解上通过@Import引入了一个
// MapperScannerRegistrar.class,而上面我们@Mapper生效的条件就是容器中没有这个bean
// 那么我们可以知道,如果指定了@MapperScan扫描,@Mapper扫描就失效了,如果两者共存,@Mapper负责的Mapper不在
// @MapperScan扫描的包路径下,这个Mapper就不会注入到容器中,使用就会报错
// No qualifying bean of type 'com.lwh.springboot.test.UserMapper' available: expected at least 1 bean which
// qualifies as autowire candidate
@Import(MapperScannerRegistrar.class)  
public @interface MapperScan {
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })  
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    @Override  
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  
  	 // 获取@MapperScan注解上的属性
         AnnotationAttributes mapperScanAttrs = AnnotationAttributes  
                          .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));  
         if (mapperScanAttrs != null) {  
             registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));  
         }  
    }
	
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {  
         // 该方法最关键的就是往容器中注入了一个MapperScannerConfigurer,这个我们上面已经分析过了
         BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

1.3.3、Mapper类生成原理

private void processBeanDefinitions(Set<beandefinitionholder> beanDefinitions) {  
    GenericBeanDefinition definition;  
    for (BeanDefinitionHolder holder : beanDefinitions) {  
	// 遍历这些BeanDefinitions,修改其beanClass
        definition = (GenericBeanDefinition) holder.getBeanDefinition();  
        String beanClassName = definition.getBeanClassName();  
	// 比如com.lwh.springboot.mapper.UserMapper,将其替换为MapperFactoryBean.class
	// mapperFactoryBeanClass = MapperFactoryBean.class
	// MapperFactoryBean是FactoryBean的实现,那么以后我们创建UserMapper的实例时,实际上会调用MapperFactoryBean的
	// getObject方法
        definition.setBeanClass(this.mapperFactoryBeanClass);
public class MapperFactoryBean<t> extends SqlSessionDaoSupport implements FactoryBean<t> {

    @Override  
    public T getObject() throws Exception {  
        return getSqlSession().getMapper(this.mapperInterface);  
    }
public T newInstance(SqlSession sqlSession) {  
    // 上面的getMapper方法会走到这里,创建mapperInterface的代理对象,由mapperProxy代理
    final MapperProxy<t> mapperProxy = new MapperProxy&lt;&gt;(sqlSession, mapperInterface, methodCache);  
    return newInstance(mapperProxy);  
}

1.3.4、Mapper类执行原理

@Test  
public void testInsert(){  
    User user = new User();  
    user.setName("xx");  
    user.setAge(25);  
     // 经过上述分析,我们知道userMapper是MapperProxy类型的代理对象,那么它会先走到MapperProxy的invoke方法
    userMapper.insert(user);  
}
public class MapperProxy<t> implements InvocationHandler, Serializable

@Override  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    try {  
         if (Object.class.equals(method.getDeclaringClass())) {  
            return method.invoke(this, args);  
         } else if (method.isDefault()) {  
            return invokeDefaultMethod(proxy, method, args);  
         }  
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); 
    // 一般情况下会走到这里,下面就是Mybatis的源码了,根据传入SQL类型,进行增删改查操作
    return mapperMethod.execute(sqlSession, args);  
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!