SpringBoot 源碼解析 (九)----- Spring Boot的核心能力 - 整合Mybatis


本篇我們在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
​在 MybatisAutoConfiguration自動配置類中,SpringBoot默認自動配置了兩個Bean,分別是 SqlSessionFactorySqlSessionTemplate。我們看到上面代碼中第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-靜態代理和動態代理結合使用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM