这是我参与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