本篇我们在SpringBoot中整合Mybatis这个orm框架,毕竟分析一下其自动配置的源码,我们先来回顾一下以前Spring中是如何整合Mybatis的,大家可以看看我这篇文章 Mybaits 源码解析 (十)—– Spring-Mybatis框架使用与源码解析
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.chenhao.bean.User" />
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chenhao.mapper.UserMapper">
<select id="getUser" parameterType="int"
resultType="com.chenhao.bean.User">
SELECT *
FROM USER
WHERE id = #{id}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.chenhao.mapper" />
</bean>
</beans>
@Configuration
@MapperScan("com.chenhao.mapper") public class AppConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("schema.sql")
.build();
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
//创建SqlSessionFactoryBean对象
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); //设置数据源
sessionFactory.setDataSource(dataSource()); //设置Mapper.xml路径
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); // 设置MyBatis分页插件
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
pageInterceptor.setProperties(properties);
sessionFactory.setPlugins(new Interceptor[]{pageInterceptor});
return sessionFactory.getObject();
}
}
最核心的有两点:
//最终注入Spring容器的就是这里的返回对象
public T getObject() throws Exception {
//获取父类setSqlSessionFactory方法中创建的SqlSessionTemplate
//通过SqlSessionTemplate获取mapperInterface的代理类
//我们例子中就是通过SqlSessionTemplate获取com.chenhao.mapper.UserMapper的代理类
//获取到Mapper接口的代理类后,就把这个Mapper的代理类对象注入Spring容器
return this.getSqlSession().getMapper(this.mapperInterface);
}
接下来我们看看SpringBoot是如何引入Mybatis的
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
spring: datasource: url: jdbc:mysql:///springboot username: root password: admin type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 minIdle: 5 maxActive: 20 mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml type-aliases-package: org.com.cay.spring.boot.entity
@SpringBootApplication
@EnableScheduling
@ServletComponentScan
@MapperScan("com.supplychain.app.mapper") public class Application {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
System.setProperty("user.timezone", "GMT+8");
SpringApplication.run(Application.class, args);
}
}
我们看到mybatis-spring-boot-starter实际上引入了jdbc的场景启动器,这一块我们上一篇文章已经分析过了,还引入了mybatis-spring的依赖,最终还引入了mybatis-spring-boot-autoconfigure这个依赖,其实mybatis-spring-boot-starter只是引入各种需要的依赖,最核心的代码是在引入的mybatis-spring-boot-autoconfigure这个项目当中,我们来看看这个项目
我们看到mybatis-spring-boot-autoconfigure也像spring-boot-autoconfigure一样配置了spring.factories这个配置文件,并且在配置文件中配置了MybatisAutoConfiguration这个自动配置类,我们知道SpringBoot启动时会获取所有spring.factories配置文件中的自动配置类并且进行解析其中的Bean,那么我们就来看看MybatisAutoConfiguration这个自动配置类做了啥?
1 @org.springframework.context.annotation.Configuration
2 @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
3 @ConditionalOnBean(DataSource.class)
4 //引入MybatisProperties配置类
5 @EnableConfigurationProperties(MybatisProperties.class) 6 @AutoConfigureAfter(DataSourceAutoConfiguration.class)
7 public class MybatisAutoConfiguration {
8
9 private final MybatisProperties properties;
10
11 private final Interceptor[] interceptors;
12
13 private final ResourceLoader resourceLoader;
14
15 private final DatabaseIdProvider databaseIdProvider;
16
17 private final List<ConfigurationCustomizer> configurationCustomizers;
18
19 public MybatisAutoConfiguration(MybatisProperties properties,
20 ObjectProvider<Interceptor[]> interceptorsProvider,
21 ResourceLoader resourceLoader,
22 ObjectProvider<DatabaseIdProvider> databaseIdProvider,
23 ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
24 this.properties = properties;
25 this.interceptors = interceptorsProvider.getIfAvailable();
26 this.resourceLoader = resourceLoader;
27 this.databaseIdProvider = databaseIdProvider.getIfAvailable();
28 this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
29 }
30
31 @Bean
32 @ConditionalOnMissingBean
33 //往Spring容器中注入SqlSessionFactory对象 34 //并且设置数据源、MapperLocations(Mapper.xml路径)等
35 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
36 SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 37 factory.setDataSource(dataSource); 38 factory.setVfs(SpringBootVFS.class);
39 if (StringUtils.hasText(this.properties.getConfigLocation())) {
40 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
41 }
42 Configuration configuration = this.properties.getConfiguration();
43 if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
44 configuration = new Configuration();
45 }
46 if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
47 for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
48 customizer.customize(configuration);
49 }
50 }
51 factory.setConfiguration(configuration);
52 if (this.properties.getConfigurationProperties() != null) {
53 factory.setConfigurationProperties(this.properties.getConfigurationProperties());
54 }
55 if (!ObjectUtils.isEmpty(this.interceptors)) {
56 factory.setPlugins(this.interceptors);
57 }
58 if (this.databaseIdProvider != null) {
59 factory.setDatabaseIdProvider(this.databaseIdProvider);
60 }
61 if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
62 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
63 }
64 if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
65 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
66 }
67 if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
68 factory.setMapperLocations(this.properties.resolveMapperLocations()); 69 }
70 //获取SqlSessionFactoryBean的getObject()中的对象注入Spring容器,也就是SqlSessionFactory对象
71 return factory.getObject(); 72 }
73
74 @Bean
75 @ConditionalOnMissingBean
76 //往Spring容器中注入SqlSessionTemplate对象
77 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
78 ExecutorType executorType = this.properties.getExecutorType();
79 if (executorType != null) {
80 return new SqlSessionTemplate(sqlSessionFactory, executorType); 81 } else {
82 return new SqlSessionTemplate(sqlSessionFactory);
83 }
84 }
85
86 //other code...
87 }
在自动配置的时候会导入一个Properties配置类MybatisProperties,咱们来看一下
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
/**
* Location of MyBatis xml config file.
*/
private String configLocation; /**
* Locations of MyBatis mapper files.
*/
private String[] mapperLocations; /**
* Packages to search type aliases. (Package delimiters are ",; /t/n")
*/
private String typeAliasesPackage; /**
* Packages to search for type handlers. (Package delimiters are ",; /t/n")
*/
private String typeHandlersPackage;
/**
* Indicates whether perform presence check of the MyBatis xml config file.
*/
private boolean checkConfigLocation = false;
/**
* Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}.
*/
private ExecutorType executorType;
/**
* Externalized properties for MyBatis configuration.
*/
private Properties configurationProperties;
/**
* A Configuration object for customize default settings. If {@link #configLocation}
* is specified, this property is not used.
*/
@NestedConfigurationProperty
private Configuration configuration;
//other code...
}
该Properties配置类作用主要用于与yml/properties中以mybatis开头的属性进行一一对应,如下
mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml type-aliases-package: org.com.cay.spring.boot.entity
在
MybatisAutoConfiguration自动配置类中,SpringBoot默认自动配置了两个Bean,分别是
SqlSessionFactory和
SqlSessionTemplate。我们看到上面代码中第71行,其实是返回的factory.getObject();,也就是注入Spring容器中的是SqlSessionFactory对象,SqlSessionFactory主要是将Properties配置类中的属性赋值到SqlSessionFactoryBean中,类似以前xml中配置的SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/cn/mapper/*.xml"></property>
...
</bean>
另外一个Bean为SqlSessionTemplate,通过SqlSessionFactory来生成SqlSession代理类:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator;
//other code...
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;
//生成SqlSessioin代理类
this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
}
而@MapperScan注解是和Spring整合Mybatis的使用是一样的,都是在配置类上指定Mapper接口的路径,大家可以看一下我以前的一篇文章 Mybaits 源码解析 (十一)—– @MapperScan将Mapper接口生成代理注入到Spring-静态代理和动态代理结合使用