本篇我們在SpringBoot中整合Mybatis這個orm框架,畢竟分析一下其自動配置的源碼,我們先來回顧一下以前Spring中是如何整合Mybatis的,大家可以看看我這篇文章Mybaits 源碼解析 (十)----- Spring-Mybatis框架使用與源碼解析
Spring-Mybatis使用
添加maven依賴
<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>
在src/main/resources下添加mybatis-config.xml文件
<?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>
在src/main/resources/mapper路徑下添加User.xml
<?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>
在src/main/resources/路徑下添加beans.xml
<?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>
注解的方式
- 以上分析都是在spring的XML配置文件applicationContext.xml進行配置的,mybatis-spring也提供了基於注解的方式來配置sqlSessionFactory和Mapper接口。
- sqlSessionFactory主要是在@Configuration注解的配置類中使用@Bean注解的名為sqlSessionFactory的方法來配置;
- Mapper接口主要是通過在@Configuration注解的配置類中結合@MapperScan注解來指定需要掃描獲取mapper接口的包。
@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(); } }
最核心的有兩點:
- 創建一個SqlSessionFactoryBean,並設置數據源和Mapper.xml路徑,其中會解析Mapper.xml文件,最后通過getObject()返回一個SqlSessionFactory 注入Spring容器中
- 通過@MapperScan掃描所有Mapper接口,掃描過程會將Mapper接口生成MapperFactoryBean這個特殊的Bean,並且在其getObject()通過SqlSession().getMapper(this.mapperInterface)生成每個mapper接口真實的代理類
MapperFactoryBean
//最終注入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的
SpringBoot引入Mybatis
添加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>
全局配置文件中配置數據源和mybatis屬性
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
加入Mapper掃描注解@MapperScan
@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
我們看到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這個自動配置類做了啥?
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
<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-靜態代理和動態代理結合使用