2023-03-15
原文作者:java-chen-hao 原文地址:https://www.cnblogs.com/java-chen-hao/p/11829056.html

本篇我们在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

202303152210489821.png

我们看到mybatis-spring-boot-starter实际上引入了jdbc的场景启动器,这一块我们上一篇文章已经分析过了,还引入了mybatis-spring的依赖,最终还引入了mybatis-spring-boot-autoconfigure这个依赖,其实mybatis-spring-boot-starter只是引入各种需要的依赖,最核心的代码是在引入的mybatis-spring-boot-autoconfigure这个项目当中,我们来看看这个项目

202303152210494392.png

202303152210502023.png

我们看到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-静态代理和动态代理结合使用

阅读全文