前言

Mybatis与Spring的集成实现有两种方式,一种是通过XML配置,另一种是通过注解的信息进行配置,上一章节介绍了通过XML的方式来集成,这一章节来介绍如何通过注解的形式来在Spring Boot环境中集成Mybatis

环境准备

  • 基于MyBatis-Spring-Boot-Starter 快速在Spring Boot环境中集成Mybatis,使用注解解决一切问题

  • 基于Spring Boot环境中添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
    </dependency>
  • 配置类中添加注解

    1
    @MapperScan("com.songsy.iframe.mapper")
  • 添加配置(选填)

    1
    2
    3
    4
    5
    6
    7
    mybatis:
    type-aliases-package: com.songsy.iframe.model
    type-handlers-package: com.songsy.iframe.typehandler
    configuration:
    map-underscore-to-camel-case: true
    default-fetch-size: 100
    default-statement-timeout: 30
  • 以上完成之后就可以使用Mybatis了

原理解析

  • 打开mybatis-spring-boot-starter 源码可以看到是个空壳子
image
image
  • 打开里面的pom.xml文件,可以看到其依赖,里面已经帮我们导入了mybatismybatis-spring的包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>1.3.1</version>
    </parent>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <name>mybatis-spring-boot-starter</name>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    </dependency>
    </dependencies>
  • 关注mybatis-spring-boot-autoconfigure 包,这里完成了其自动配置的功能,可以看到里面就只有几个类

image
image
  • MybatisProperties.java是属性配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @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;

    ...
  • MybatisAutoConfiguration 是完成自动配置的主要实现类,可以看到这里定义了SqlSessionFactorySqlSessionTemplate Bean,这里完成了之前使用xml配置bean的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    /**
    * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
    * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
    *
    * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
    * configuration file is specified as a property, those will be considered,
    * otherwise this auto-configuration will attempt to register mappers based on
    * the interface definitions in or under the root auto-configuration package.
    *
    * @author Eddú Meléndez
    * @author Josh Long
    * @author Kazuki Shimizu
    * @author Eduardo Macarrón
    */
    @org.springframework.context.annotation.Configuration
    @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
    @ConditionalOnBean(DataSource.class)
    @EnableConfigurationProperties(MybatisProperties.class)
    @AutoConfigureAfter(DataSourceAutoConfiguration.class)
    public class MybatisAutoConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

    private final MybatisProperties properties;

    private final Interceptor[] interceptors;

    private final ResourceLoader resourceLoader;

    private final DatabaseIdProvider databaseIdProvider;

    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisAutoConfiguration(MybatisProperties properties,
    ObjectProvider<Interceptor[]> interceptorsProvider,
    ResourceLoader resourceLoader,
    ObjectProvider<DatabaseIdProvider> databaseIdProvider,
    ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }

    @PostConstruct
    public void checkConfigFileExists() {
    if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
    Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
    Assert.state(resource.exists(), "Cannot find config location: " + resource
    + " (please add config file or check your Mybatis configuration)");
    }
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
    factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
    customizer.customize(configuration);
    }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
    factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
    factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
    factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
    factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
    factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
    return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
    return new SqlSessionTemplate(sqlSessionFactory);
    }
    }

    /**
    * This will just scan the same base package as Spring Boot does. If you want
    * more power, you can explicitly use
    * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed
    * mappers working correctly, out-of-the-box, similar to using Spring Data JPA
    * repositories.
    */
    public static class AutoConfiguredMapperScannerRegistrar
    implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    logger.debug("Searching for mappers annotated with @Mapper");

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    try {
    if (this.resourceLoader != null) {
    scanner.setResourceLoader(this.resourceLoader);
    }

    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
    for (String pkg : packages) {
    logger.debug("Using auto-configuration base package '{}'", pkg);
    }
    }

    scanner.setAnnotationClass(Mapper.class);
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(packages));
    } catch (IllegalStateException ex) {
    logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
    }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
    }
    }

    /**
    * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
    * creating instances of {@link MapperFactoryBean}. If
    * {@link org.mybatis.spring.annotation.MapperScan} is used then this
    * auto-configuration is not needed. If it is _not_ used, however, then this
    * will bring in a bean registrar and automatically register components based
    * on the same component-scanning path as Spring Boot itself.
    */
    @org.springframework.context.annotation.Configuration
    @Import({ AutoConfiguredMapperScannerRegistrar.class })
    @ConditionalOnMissingBean(MapperFactoryBean.class)
    public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
    logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
    }

    }
  • 通过注解配置来集成Spring、Mybatis 的方式可以看到是通过这个注解@MapperScan来实现的,查看注解可以看到通过@Import(MapperScannerRegistrar.class)把实例加入springIOC容器中

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class) // 通过导入的方式实现把实例加入springIOC容器中
@Repeatable(MapperScans.class)// 被此注解修饰的注解是可以重复的。注解的参数是可重复注解的存储容器注解类型。@Repeatable括号内的就相当于用来保存该注解内容的容器。
public @interface MapperScan {
  • 查看MapperScannerRegistrar类,可以看到实现了 ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法, 由于实现了该接口让该类成为了拥有注册bean的能力
image
image
  • 进入registerBeanDefinitions方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 拿到注解信息,内部实现是 LinkedHashMap
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
    .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
    registerBeanDefinitions(mapperScanAttrs, registry);
    }
    }
  • 进入第二个registerBeanDefinitions方法,可以看到又出现了ClassPathMapperScanner这个类,果不其然,还是调用了scanner.doScan(StringUtils.toStringArray(basePackages)); 这个方法完成了Mapper的注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
    // 获得spring的注册器registry
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
    scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
    scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
    scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
    scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<>();
    // 如果配置了包路径则将入进去
    basePackages.addAll(
    Arrays.stream(annoAttrs.getStringArray("value"))
    .filter(StringUtils::hasText)
    .collect(Collectors.toList()));
    // 与上面功能一致
    basePackages.addAll(
    Arrays.stream(annoAttrs.getStringArray("basePackages"))
    .filter(StringUtils::hasText)
    .collect(Collectors.toList()));

    basePackages.addAll(
    Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
    .map(ClassUtils::getPackageName)
    .collect(Collectors.toList()));

    scanner.registerFilters();
    // 开始扫描包
    scanner.doScan(StringUtils.toStringArray(basePackages));
    }

总结

  • 使用注解的方式集成Mybatis比xml配置的方式更为简洁,在Spring Boot项目中就是以这种方式来配置的
  • mybatis-spring-boot-autoconfigure 帮助我们完成了以下功能
    1
    2
    3
    4
    自动检测现有的DataSource。
    将创建并注册的一个实例的SqlSessionFactory传递一个数据源作为使用输入SqlSessionFactoryBean的。
    将创建并注册SqlSessionTemplate的实例从SqlSessionFactory中获取。
    自动扫描映射器,将它们链接到SqlSessionTemplate并将它们注册到Spring上下文,以便将它们注入到bean中。

参考

http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/