Spring Boot系列(三):Spring Boot整合Mybatis源碼解析

1、Mybatis回顧

  一、MyBatis介紹

  Mybatis是一個半ORM框架,它使用簡單的 XML 或註解用於配置和原始映射,將接口和Java的POJOs(普通的Java 對象)映射成數據庫中的記錄。html

  二、Mybatis總體架構

 2、Spring Boot整合Mybatis + Druid

  一、在應用中導入maven依賴以下:

     <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--database pool-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.8</version>
        </dependency>
        <!--mysql數據庫-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

  二、在應用中加配置

  ① 配置Druid數據源參數:java

#配置數據源
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.druid.username=root
spring.datasource.druid.password=123qwe
spring.datasource.druid.driverClassName=com.mysql.jdbc.Driver
spring.datasource.druid.initialSize: 5
spring.datasource.druid.minIdle: 5
spring.datasource.druid.maxActive: 20
spring.datasource.druid.maxWait: 60000
spring.datasource.druid.timeBetweenEvictionRunsMillis: 60000
spring.datasource.druid.minEvictableIdleTimeMillis: 300000
spring.datasource.druid.validationQuery: SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle: true
spring.datasource.druid.testOnBorrow: false
spring.datasource.druid.testOnReturn: false
spring.datasource.druid.poolPreparedStatements: true
spring.datasource.druid.filters: stat,wall
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize: 20
spring.datasource.druid.useGlobalDataSourceStat: true
spring.datasource.druid.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

  ② 編寫Druid數據源屬性接收類:mysql

/**
 * @desc: 自定義druid的屬性
 * @author: toby
 */
@ConfigurationProperties(prefix = "spring.datasource.druid")
@Data
public class DruidDataSourceProperties {
    private String username;
    private String password;
    private String url;
    private String driverClassName;
    private Integer initialSize;
    private Integer maxActive;
    private Integer minIdle;
    private Long maxWait;
    ......
}

  ③ 編寫Druid數據源配置類:spring

/**
 * @desc: 自定義druid配置,如不自定義,配置文件設置的屬性不生效自行測試
 * @author: toby
 */
@Configuration
@EnableConfigurationProperties(value = DruidDataSourceProperties.class)
@MapperScan(basePackages="com.toby.mapper", value="sqlSessionFactory")
public class DruidDataSourceConfig {

    @Autowired
    private DruidDataSourceProperties druidDataSourceProperties;

    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername(druidDataSourceProperties.getUsername());
        druidDataSource.setPassword(druidDataSourceProperties.getPassword());
        druidDataSource.setUrl(druidDataSourceProperties.getUrl());
        druidDataSource.setDriverClassName(druidDataSourceProperties.getDriverClassName());
        druidDataSource.setInitialSize(druidDataSourceProperties.getInitialSize());
        druidDataSource.setMinIdle(druidDataSourceProperties.getMinIdle());
        druidDataSource.setMaxActive(druidDataSourceProperties.getMaxActive());
        druidDataSource.setMaxWait(druidDataSourceProperties.getMaxWait());
        druidDataSource.setFilters(druidDataSourceProperties.getFilters());
        druidDataSourceProperties.setPoolPreparedStatements(druidDataSourceProperties.getPoolPreparedStatements());
        return druidDataSource;
    }
}

  三、在應用中加@MapperScan註解,他的主要做用就是掃描basePackage包下面的TobyMapper接口,而後getBean的時候經過JDK的動態代理,生成代理對象,因此咱們程序中看到的是TobyMapper接口,實際上是被動態代理過的。驗證方式很簡單,DEUBG到TobyMapper的方法裏面,就能夠發現其就是個動態代理

/**
 * @desc: spring boot 啓動類
 * @author: toby
 */
@SpringBootApplication
@MapperScan(basePackages="com.toby.mapper")
public class MybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisApplication.class, args);
    }
}

3、源碼解析

  一、Mybatis自動裝配

  自動裝配的流程圖:sql

  具體詳細見Spring Boot系列(二):Spring Boot自動裝配原理解析中的Spring Boot自動裝配流程圖。Mybatis的自動配置類給咱們配置了什麼組件,咱們接下來看下MybatisAutoConfiguration類數據庫

  ① SqlSessionFactory:當容器中沒有SqlSessionFactory這個類型的Bean的時候,Spring就加載該組件。apache

  ② SqlSessionTemplate:一樣當容器中沒有SqlSessionTemplate這個類型的Bean的時候,Spring就加載該組件。session

   到此SqlSessionFactory和SqlSessionTemplate組件有了。mybatis

  二、@MapperScan註解

  @MapperScan註解:他的做用就是掃描basePackages包下面的TobyMapper接口,而後getBean("tobyMapper")的時候把該接口經過JDK的動態代理,生成代理對象,用於和數據庫打交道。架構

  ① 從@MapperScan入手:

  ② 導入了MapperScanner註冊類MapperScannerRegistrar:

  它是一個ImportBeanDefinitionRegistrar,Spring在經過@Import導入Bean的時候,會調用其registerBeanDefinitions,往Spring容器中註冊bean定義信息,以便後面能夠經過getBean獲取到被註冊進行的bean的定義信息所對應的Bean,其往Spring容器中註冊的是MapperFactoryBean類型的Bean定義信息,爲何是MapperFactoryBean類型,而不是TobyMapper類型?緣由很簡單,Spring容器在getBean的時候,會忽略掉接口,接口是不能new的,而Spring容器默認在實例化的時候就是經過調用beanDefinition的beanClass屬性所對應的類的無參構造方法。

   ③ 進去到註冊bean定義信息的registerBeanDefinitions方法以下:

   ④ 進入到ClassPathMapperScanner的doScan,其做用是掃描basePackages全部的包,這裏ClassPathMapper Scanner繼承了Spring的包掃描ClassPathBeanDefinitionScanner

   重寫(覆蓋)了判斷是不是候選的Component方法isCandidateComponent,由於Spring默認的isCandidateComponent是會過濾掉接口的,顯然不知足,因此重寫了該方法

  /**
     * Spring默認的,獨立的非接口,非抽象類
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return (metadata.isIndependent() && (metadata.isConcrete() ||
                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }

    /**
     * ClassPathMapperScanner的能夠是獨立的接口
     */
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

  接下來進入處處理TobyMapper的bean的定義信息方法:

   ⑤ 處理TobyMapper的bean定義信息,主要由3個重要改動:

//第一:修改構造函數爲有參的構造函數;
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//第二:修改bean的class爲MapperFactoryBean,該MapperFactoryBean是FactoryBean;
definition.setBeanClass(this.mapperFactoryBean.getClass());
//第三:修改注入類型爲按照類型注入;
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

  到此,掃描TobyMapper的時候,往Spring容器中註冊的是beanClass爲MapperFactoryBean,一個有參數的構造函數,按照類型注入的這麼一個Bean定義信息。

  三、經過getBean("tobyMapper")獲取TobyMapper的動態代理類

  咱們知道,此時的tobyMapper的bean定義信息中的beanClass的屬性是MapperFactoryBean.class,而MapperFactoryBean又是一個FactoryBean,FactoryBean的特色就是在getBean的時候會調用其getObject方法;

  ① 咱們找到MapperFactoryBean的getObject方法:

   ② 咱們再進入org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper方法,其實已經到了Mybatis的邏輯了。

   ③ 最後調用到org.apache.ibatis.binding.MapperRegistry#getMapper,其實就是建立JDK的動態代理了。

@SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            //建立代理實例
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

   代理的邏輯在org.apache.ibatis.binding.MapperProxy#invoke方法,到此tobyMapper建立完成,能夠操做數據庫。

 4、掃描Mapper和Mapper動態代理對象生成流程圖

相關文章
相關標籤/搜索