[Mybatis]1 - MyBatis-Bean初始化[1]

这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

MyBatis

其实按照上面JDBC的步骤,第一步肯定是先加载驱动,然后是创建连接。

然而,在以Spring作为容器,MyBatis作为ORM的时候,总需要做一些适配工作,随后把对应的信息包装成Bean,从而放入到对应使用的区域中,随后才能在服务中进行使用。

因此,这里就先从这一步开始。

对应依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.5</version>
</dependency>

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
</dependency>
        
<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

<!--这里是数据库连接池druid-->
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.4</version>
        </dependency>
复制代码

项目配置

在yml文件中,配置的信息:

spring:
  datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: 
      username: 
      password: 



mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.biadisa.mock.domain.DO
复制代码

在对应的目录下建立文件包。

由于在每个mapper文件上都加上了@Mapper注解,因此在启动类上没有加**@MapperScan**。

对应的DB表为:

CREATE TABLE `student` (
  `number` int(11) NOT NULL AUTO_INCREMENT COMMENT '学号',
  `name` varchar(5) DEFAULT NULL COMMENT '姓名',
  `major` varchar(30) DEFAULT NULL COMMENT '专业',
  PRIMARY KEY (`number`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='学生信息表';
复制代码

其他的mapper文件、pojo就不贴了,依葫芦画瓢那一套。

自动注入

如果对于在SpringBoot中,中间件如何注入到容器中有了解的话,应该对spring.factories这个文件有点印象。

对于想要注入到Spring中的中间件而言,都需要通过Spring的SPI把需要的配置在Spring容器启动的时候进行初始化,并生成对应的Bean,而这部分的内容一般都在xxx-spring-boot-autoconfigure包下,XXX是对应的中间件名称。

对于MyBatis,这里的内容是:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
复制代码

对于上面的MybatisLanguageDriverAutoConfiguration,我的理解是一些用来生成MyBatis的mapper文件的配置。

其中包括:freemarker,Velocity,Thymeleaf,freemarker在常用的mapperStruct中就是用来根据接口生成对应的实现bean的。

由于这里没有配置对应的包也没导进来,如果用的是idea可以看到这里的**@ConditionalOnClass**里面一片红,因此这里就先不考虑了。

重点是下面这个MybatisAutoConfiguration

MybatisAutoConfiguration - 连接处理

解析组件

首先先来看看类头上这一大坨东西:

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
复制代码

如果要探究Spring中多数据源配置为什么要手动配并标注不执行DataSourceAutoConfiguration,原因就在第三行,以及第五行:

  • 只有DataSource在容器中实例有且仅有1个的时候,才会走这个类进行配置
  • 并且,这个类的自动配置,是在DataSourceAutoConfiguration之后的。

随后看一下这里返回的两个bean:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource){//...}
    
@Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){//...}
复制代码

这里也说明了上述多数据源配置需要的bean:

  • sqlSessionFactory
  • SqlSessionTemplate

根据这里的DataSource,也可以看到一个问题:实际上MyBatis并没有帮我们解决数据库连接的问题,这部分的问题就交给其他中间件去做了。

SqlSessionFactory

这里hardCode的代码较多,大概就是设置了一些解析mapper文件(xml)以及sql返回值包装的必要解析参数(这里会把设置了),以及上面传入的数据源。将这些东西统统设置到FactoryBean中,最后生成对应的bean - SqlSessionFactory。

根据类上的注解,这个类是通过connection或DataSource来创建sqlSession的,里面的参数是用一个Configuration来保存的,光参数就写了小一百行。。。

而这个SqlSessionFactory,除了get里面的配置,重写了一大堆openSession方法,返回的这里得注意一下是MyBatis的SqlSession,而不是在之前JDBC中的Session。

这里按照类的顺序,可以做一下和JDBC中的对照,具体的初始化流程后面详细展开。

graph LR
加载驱动-->连接数据库Connection-->创建数据库操作对象session-->使用数据库操作对象执行SQL-->从数据库操作对象获取结果集

SqlSessionFactory-- openSession -->sqlSession-->executor-->handler-->transaction.commit

connection的信息,在MyBatis中是在事务对象(Transaction类)中存储的;而这个Transaction,本质上的连接还是通过DataSource来打开连接的。

SqlSessionTemplate

这个类根据注释,实际上是通过上面的SqlSessionFactory来建立SqlSession的。

这里的bean可以看一看:

ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
  return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
  return new SqlSessionTemplate(sqlSessionFactory);
}
复制代码

这里的ExecutorType是个枚举,需要在配置文件里指定:

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}
复制代码

实际上就对应了3种具体的Executor。

这里是我们没有配,默认的就是SIMPLE。

不妨看一下这部分的代码,可以发现:实际上sql的执行是要通过这个类去执行的,具体的执行对象是这里的sqlSessionProxy

这个代理是在构造方法中指定的:

这个类的所有构造方法最终调用的都是这个

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
复制代码

也就是说:

  • 到后面如果执行SQL,那么必然是要经过这个代理类(SqlSessionInterceptor)的。

这个代理的执行逻辑等到后面执行的时候再做一下解析。

总的来说,对于类SqlSessionTemplate,目前可以粗浅地认为是一个MyBatis中类似JDBC中的Session的地位:执行SQL。

具体是怎么执行,如何执行的,等到执行的时候再分析。

AutoConfiguredMapperScannerRegistrar - scanner注册

从上面自动配置的内容,其实可以看到的一点是:

  • 自动配置的还是和连接相关的。

那么,项目中的mapper是怎么变成可执行的bean的,SQL是如何绑定的?

如果了解过类似的框架(比如feign),大概也知道这些接口配置、同时不生成对应class文件的,一般都是通过ImportBeanDefinitionRegistrar进行一系列bean生成放到容器中的。

对于Mybatis而言,这个registrar就是AutoConfiguredMapperScannerRegistrar,恰好也在MybatisAutoConfiguration类中。

但和feign有所不同的是,Mybatis这里仅仅是注册了一个scanner,而不是在这里把对应的bean生成好了。

240行

registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
复制代码

那么,要探究mapper对应的bean何时生成的,就得看这个MapperScannerConfigurer

MapperScannerConfigurer

这个类对应的继承关系是:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware{
    //...
}
复制代码

那么,对应的回调方法就是postProcessBeanDefinitionRegistry

在这个类中,回调方法会根据这里已配置的参数,构造一个ClassPathMapperScanner

需要注意的是:sqlSession相关的(就是上面自动注入里生成的两个bean),在这里也用到了:

scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
复制代码

构造完了之后,就是执行这个scanner:

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
复制代码

这里的scan方法,就留到下一篇讲解。

附录

注入顺序

graph TD
MybatisAutoConfiguration--注册-->SqlSessionFactory
--接着注册-->SqlSessionTemplate
--接着注册BD-->AutoConfiguredMapperScannerRegistrar
-->注册MapperScannerConfigurer的BeanDefition
-->MapperScannerConfigurer生成ClassPathMapperScanner
-->ClassPathMapperScanner.scan注册mapper对应bean

Supongo que te gusta

Origin juejin.im/post/7068889882067730445
Recomendado
Clasificación