tk.mybaits是mybatis的工具集合,是国人开源的一个工具集,用来简化mybaits的开发。
主要是两个工具:

  1. 通用mapper,提供常用的增删改查方法(CURD)
  2. paeghelper,提供常用分页功能

参考资料:

  • 官网: http://www.mybatis.tk/
  • 源码:https://github.com/abel533/Mapper
  • 文档:https://github.com/abel533/Mapper/wiki
  • 分页插件:https://github.com/pagehelper

    一、安装

    通用 Mapper 有很多种集成方式,这里会介绍大部分情况下的配置方式。
    Java 编码方式集成是最少见的一种情况,但是通过这种集成方式可以很容易让大家看清通用 Mapper 集成的入口。
    和 Spring 集成是最常见的,Spring Boot 也在慢慢成为主流,为了便于在集成通用 Mapper 的情况下仍然可以和第三方的工具集成,这里也会有很多种集成的方式。

    1.1 Java 编码方式集成

    Java 编码方式集成是最少见的一种情况,但是通过这种集成方式可以很容易让大家看清通用 Mapper 集成的入口,这里会提供两种方式。
  1. 最直接的方式
  2. 使用 Configuration 作为入口集成

    1.1.1 添加依赖

    在开始写代码前,先把依赖添加进来。
    在 Java 编码集成方式中,首先你肯定已经引入了 MyBatis 的依赖:
    1. <dependency>
    2. <groupId>org.mybatis</groupId>
    3. <artifactId>mybatis</artifactId>
    4. <version>版本号</version>
    5. </dependency>

所有版本号看这里:http://mvnrepository.com/artifact/org.mybatis/mybatis 通用 Mapper 支持 MyBatis 3.2.4+

在 mybatis 依赖的基础上,添加通用 Mapper 的依赖即可:

  1. <dependency>
  2. <groupId>tk.mybatis</groupId>
  3. <artifactId>mapper</artifactId>
  4. <version>最新版本</version>
  5. </dependency>

最新版本看这里:http://mvnrepository.com/artifact/tk.mybatis/mapper

如果你使用的 Jar 包,你可以通过上面提供的链接下载 Jar。

1.1.2 编写代码集成

使用 Java 编码方式时,正常情况下你都会有构建 SqlSessionFactory 的代码。
在创建 SqlSessionFactory 对象或者对应两种配置通用 Mapper 的方法,由于没有提供 mybatis-config.xml 文件的解析类,这里会推荐使用 创建后 的方式来创建。

1.1.2.1 创建后

在创建 SqlSessionFactory 后,在任何其他调用发生前,使用下面的方式配置通用 Mapper。

  1. //从刚刚创建的 sqlSessionFactory 中获取 session
  2. session = sqlSessionFactory.openSession();
  3. //创建一个MapperHelper
  4. MapperHelper mapperHelper = new MapperHelper();
  5. //特殊配置
  6. Config config = new Config();
  7. //主键自增回写方法,默认值MYSQL,详细说明请看文档
  8. config.setIDENTITY("MYSQL");
  9. //支持getter和setter方法上的注解
  10. config.setEnableMethodAnnotation(true);
  11. //设置 insert 和 update 中,是否判断字符串类型!=''
  12. config.setNotEmpty(true);
  13. //校验Example中的类型和最终调用时Mapper的泛型是否一致
  14. config.setCheckExampleEntityClass(true);
  15. //启用简单类型
  16. config.setUseSimpleType(true);
  17. //枚举按简单类型处理
  18. config.setEnumAsSimpleType(true);
  19. //自动处理关键字 - mysql
  20. config.setWrapKeyword("`{0}`");
  21. //设置配置
  22. mapperHelper.setConfig(config);
  23. //注册通用接口,和其他集成方式中的 mappers 参数作用相同
  24. //4.0 之后的版本,如果类似 Mapper.class 这样的基础接口带有 @RegisterMapper 注解,就不必在这里注册
  25. mapperHelper.registerMapper(Mapper.class);
  26. //配置 mapperHelper 后,执行下面的操作
  27. mapperHelper.processConfiguration(session.getConfiguration());

如果省略 Config 的配置,上述代码简化为:

  1. //从刚刚创建的 sqlSessionFactory 中获取 session
  2. session = sqlSessionFactory.openSession();
  3. //创建一个MapperHelper
  4. MapperHelper mapperHelper = new MapperHelper();
  5. mapperHelper.processConfiguration(session.getConfiguration());

通用 Mapper 默认就是通过 session.getConfiguration() 获取所有的 MyBatis 方法,然后对其中属于通用方法的方法进行处理。

1.1.2.2 创建前

创建前就是通过使用 tk.mybatis.mapper.session.Configuration 替换 MyBatis 中的 org.apache.ibatis.session.Configuration 来实现。配置代码如下:

  1. Configuration configuration = new Configuration();
  2. //这里可以参考上面的方式来配置 MapperHelper
  3. configuration.setMapperHelper(new MapperHelper());
  4. sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

实现原理: 这种配置方式是通过重写原 Configuration 中的 addMappedStatement 方法来实现的:

  1. @Override
  2. public void addMappedStatement(MappedStatement ms) {
  3. try {
  4. super.addMappedStatement(ms);
  5. //没有任何配置时,使用默认配置
  6. if (this.mapperHelper == null) {
  7. this.mapperHelper = new MapperHelper();
  8. }
  9. this.mapperHelper.processMappedStatement(ms);
  10. } catch (IllegalArgumentException e) {
  11. //这里的异常是导致 Spring 启动死循环的关键位置,为了避免后续会吞异常,这里直接输出
  12. e.printStackTrace();
  13. throw new RuntimeException(e);
  14. }
  15. }

通过上面其中一种方式配置后,通用方法都会生效。

接下来请继续看下一章内容。

1.1.3 细化依赖的用法

首先不推荐 Maven 初学者看这里,也不建议通用 Mapper 初学者看这里

在决定是否看之前,先看看细化依赖用法的特点:

  • 可以以最精简的方式引入通用 Mapper,按照需要引入。
  • 可以将某个依赖替换为自己的实现或者选择特定版本的依赖(比如某些依赖可以选择 Java 6 或者 Java 8 的版本)。

当你需要自己选择具体的依赖时,继续看下面的介绍。

通用 Mapper4 中,原来的 tk.mybatis:mapper 项目已经拆分,1.1.1 中添加的依赖是通过特殊打包方式将所有拆分的项目合并到了一个 jar 包中。

如果需要对依赖进行详细的定制,可以分别引入下面的依赖:

  1. <!-- 必备依赖,提供核心功能 -->
  2. <dependency>
  3. <groupId>tk.mybatis</groupId>
  4. <artifactId>mapper-core</artifactId>
  5. <version>${mapper-core.version}</version>
  6. </dependency>
  7. <!-- 如果需要通用 Mapper 自带的 Mapper 接口和系列方法,需要引入本依赖 -->
  8. <!-- 拆分 base 项目的目的在于以后可能会提供其他的方式来实现接口 -->
  9. <dependency>
  10. <groupId>tk.mybatis</groupId>
  11. <artifactId>mapper-base</artifactId>
  12. <version>${mapper-base.version}</version>
  13. </dependency>
  14. <!-- 针对开发人员的需要提供的一些额外的接口,都有特殊的使用要求 -->
  15. <dependency>
  16. <groupId>tk.mybatis</groupId>
  17. <artifactId>mapper-extra</artifactId>
  18. <version>${mapper-extra.version}</version>
  19. </dependency>
  20. <!-- 基于 Java8 方法引用的类 Example 对象 Weekend -->
  21. <dependency>
  22. <groupId>tk.mybatis</groupId>
  23. <artifactId>mapper-weekend</artifactId>
  24. <version>${mapper-weekend.version}</version>
  25. </dependency>
  26. <!-- 代码生成器 -->
  27. <dependency>
  28. <groupId>tk.mybatis</groupId>
  29. <artifactId>mapper-generator</artifactId>
  30. <version>${mapper-generator.version}</version>
  31. </dependency>

这些依赖的版本号可以通过 mapper-all 子模块进行查看 https://github.com/abel533/Mapper/tree/master/mapper-all/pom.xml 还可以通过下面的目录查找 http://central.maven.org/maven2/tk/mybatis/

1.2 Spring 集成

这是 MyBatis 最常用的一种的环境。通用 Mapper 提供了多种方式来和 Spring 进行集成。

1.2.1 添加依赖

在开始配置前,先添加相关的依赖。

正常情况下,Spring 和 MyBatis 的集成环境中,应该已经存在下面的依赖:

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis</artifactId>
  4. <version>版本号</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.mybatis</groupId>
  8. <artifactId>mybatis-spring</artifactId>
  9. <version>版本号</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-context</artifactId>
  14. <version>版本号</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework</groupId>
  18. <artifactId>spring-tx</artifactId>
  19. <version>版本号</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-jdbc</artifactId>
  24. <version>版本号</version>
  25. </dependency>

相关依赖和版本可以通过 http://mvnrepository.com/ 进行搜索 通用 Mapper 支持 MyBatis 3.2.4+

集成通用 Mapper 在上面的基础上添加下面的依赖:

  1. <dependency>
  2. <groupId>tk.mybatis</groupId>
  3. <artifactId>mapper</artifactId>
  4. <version>最新版本</version>
  5. </dependency>

通用 Mapper 最新版本看这里:http://mvnrepository.com/artifact/tk.mybatis/mapper tk.mybatis:mapper 依赖包含了通用 Mapper 的基础代码以及和 Spring 集成必须的代码

1.2.2 和 Spring 集成

和 Spring 进行集成时,分为 XML 和注解配置两种方式,每种方式又有不同的配置方式。

这里提供了很多配置方式,使用时选择一种改动最小的方式即可!

1.2.2.1 XML 配置

1.使用 MapperScannerConfigurer

和通用 Mapper 以前版本一样,可以直接使用 tk.mybatis 提供的 tk.mybatis.spring.mapper.MapperScannerConfigurer 进行配置,这个配置和 MyBatis 官方提供的 org.mybatis.spring.mapper.MapperScannerConfigurer 区别只是第一层的包名,tkorg。所以使用这种方式时,如果你项目已经使用 org. 进行了配置,只需要改成 tk. 即可。

  1. <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
  2. <property name="basePackage" value="扫描包名"/>
  3. </bean>

如果你需要对通用 Mapper 进行特殊配置,可以按下面的方式进行配置:

  1. <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
  2. <property name="basePackage" value="tk.mybatis.mapper.mapper"/>
  3. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
  4. <property name="properties">
  5. <value>
  6. 参数名=值
  7. 参数名2=值2
  8. ...
  9. </value>
  10. </property>
  11. </bean>

可用配置的参数请看后续的配置文档,配置参数时一行写一个值。

2.XML 配置使用 Configuration

如果某些第三方也需要特殊的 MapperScannerConfigurer 时,就不能用上面的方式进行配置了,此时可以选择下面这种方式,这种方式要求使用MyBatis (3.4.0+) 和 mybatis-spring (1.3.0+),配置方式如下:

  1. <!--使用 Configuration 方式进行配置-->
  2. <bean id="mybatisConfig" class="tk.mybatis.mapper.session.Configuration">
  3. <!-- 配置通用 Mapper,有三种属性注入方式 -->
  4. <property name="mapperProperties">
  5. <value>
  6. notEmpty=true
  7. </value>
  8. </property>
  9. </bean>
  10. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  11. <property name="dataSource" ref="dataSource"/>
  12. <property name="configuration" ref="mybatisConfig"/>
  13. </bean>
  14. <!-- 不需要考虑下面这个,注意这里是 org 的 -->
  15. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  16. <property name="basePackage" value="tk.mybatis.mapper.configuration"/>
  17. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
  18. </bean>

这里使用了 tk.mybatis.mapper.session.Configuration ,也就是不能通过读取 mybatis-config.xml 进行配置,上面这种配置更直接,使用 Spring setter 配置属性更方便。当需要配置通用 Mapper 时,使用 mapperProperties 属性配置即可,配置方式和前面的相同,一行一个配置即可。

配置了一个 mybatisConfig 的 bean 后,在 SqlSessionFactoryBean 中注入即可。

后面的 MapperScannerConfigurer 只是为了说明这里不需要使用 tk. 开头的类进行配置。

这种配置方式基本上和任何第三方都不会冲突,如果你遇到了第三方重写 SqlSessionFactoryBean 的情况,就使用前一种方式配置即可。

1.2.2.2 使用 @MapperScan 注解

如果要使用该注解进行配置,请确认选择的是 tk.mybatis.spring.annotation.MapperScan 注解(必须使用官方的注解时,看下面其他配置方式)。

tk 提供的这个注解相比官方的多了下面两个属性:

  1. /**
  2. * 通用 Mapper 的配置,一行一个配置
  3. *
  4. * @return
  5. */
  6. String[] properties() default {};
  7. /**
  8. * 还可以直接配置一个 MapperHelper bean
  9. *
  10. * @return
  11. */
  12. String mapperHelperRef() default "";

这两个配置方式中,mapperHelperRef 优先级更高,只需要选择其中一种方式进行配置即可。

1.使用 properties 配置时:

  1. @Configuration
  2. @MapperScan(value = "tk.mybatis.mapper.annotation",
  3. properties = {
  4. "mappers=tk.mybatis.mapper.common.Mapper",
  5. "notEmpty=true"
  6. }
  7. )
  8. public class MyBatisConfigProperties {

和 XML 中很像,仍然一个一个配置。

2.使用 mapperHelperRef 配置时:

  1. @Configuration
  2. @MapperScan(value = "tk.mybatis.mapper.annotation", mapperHelperRef = "mapperHelper")
  3. public static class MyBatisConfigRef {
  4. //其他
  5. @Bean
  6. public MapperHelper mapperHelper() {
  7. Config config = new Config();
  8. List<Class> mappers = new ArrayList<Class>();
  9. mappers.add(Mapper.class);
  10. config.setMappers(mappers);
  11. MapperHelper mapperHelper = new MapperHelper();
  12. mapperHelper.setConfig(config);
  13. return mapperHelper;
  14. }
  15. }

这里配置 mapperHelperRef = "mapperHelper",并且提供了一个名为 mapperHelperbean。对通用 Mapper 的任何配置直接通过编码对 MapperHelper 进行配置。

3.使用 Configuration 进行配置

这里可以使用 tk 或者 MyBatis 官方的 @MapperScan 注解。

  1. @Configuration
  2. @MapperScan(value = "tk.mybatis.mapper.annotation")
  3. public static class MyBatisConfigRef {
  4. @Bean
  5. public SqlSessionFactory sqlSessionFactory() throws Exception {
  6. SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  7. sessionFactory.setDataSource(dataSource());
  8. //tk.mybatis.mapper.session.Configuration
  9. Configuration configuration = new Configuration();
  10. //可以对 MapperHelper 进行配置后 set
  11. configuration.setMapperHelper(new MapperHelper());
  12. //设置为 tk 提供的 Configuration
  13. sessionFactory.setConfiguration(configuration);
  14. return sessionFactory.getObject();
  15. }
  16. }

@MapperScan 注解在 Spring Boot 集成中还会提到。

文档到这里就集成好通用 Mapper 了,接下来请继续看下一章内容。

1.2.3 细化依赖的用法

首先不推荐 Maven 初学者看这里,也不建议通用 Mapper 初学者看这里

在决定是否看之前,先看看细化依赖用法的特点:

  • 可以以最精简的方式引入通用 Mapper,按照需要引入。
  • 可以将某个依赖替换为自己的实现或者选择特定版本的依赖(比如某些依赖可以选择 Java 6 或者 Java 8 的版本)。
  • 支持 Spring 高版本的特殊用法(例如多个 @MapperScan注解,暂时还没提供)

通用 Mapper4 中,原来的 tk.mybatis:mapper 项目已经拆分,1.2.1 中添加的依赖是通过特殊打包方式将所有拆分的项目合并到了一个 jar 包中。

如果需要对依赖进行详细的定制,可以分别引入下面的依赖:

  1. <!-- 必备依赖,提供核心功能 -->
  2. <dependency>
  3. <groupId>tk.mybatis</groupId>
  4. <artifactId>mapper-core</artifactId>
  5. <version>${mapper-core.version}</version>
  6. </dependency>
  7. <!-- 如果需要通用 Mapper 自带的 Mapper 接口和系列方法,需要引入本依赖 -->
  8. <!-- 拆分 base 项目的目的在于以后可能会提供其他的方式来实现接口 -->
  9. <dependency>
  10. <groupId>tk.mybatis</groupId>
  11. <artifactId>mapper-base</artifactId>
  12. <version>${mapper-base.version}</version>
  13. </dependency>
  14. <!-- 针对开发人员的需要提供的一些额外的接口,都有特殊的使用要求 -->
  15. <dependency>
  16. <groupId>tk.mybatis</groupId>
  17. <artifactId>mapper-extra</artifactId>
  18. <version>${mapper-extra.version}</version>
  19. </dependency>
  20. <!-- 基于 Java8 方法引用的类 Example 对象 Weekend -->
  21. <dependency>
  22. <groupId>tk.mybatis</groupId>
  23. <artifactId>mapper-weekend</artifactId>
  24. <version>${mapper-weekend.version}</version>
  25. </dependency>
  26. <!-- 代码生成器 -->
  27. <dependency>
  28. <groupId>tk.mybatis</groupId>
  29. <artifactId>mapper-generator</artifactId>
  30. <version>${mapper-generator.version}</version>
  31. </dependency>
  32. <!-- Spring 集成必备 -->
  33. <dependency>
  34. <groupId>tk.mybatis</groupId>
  35. <artifactId>mapper-spring</artifactId>
  36. <version>${mapper-spring.version}</version>
  37. </dependency>

这些依赖的版本号可以通过 mapper-all 子模块进行查看 https://github.com/abel533/Mapper/tree/master/mapper-all/pom.xml 还可以通过下面的目录查找 http://central.maven.org/maven2/tk/mybatis/

1.3 Spring Boot 集成

Spring Boot 在微服务领域中已经成为主流。

这里介绍通用 Mapper 如何同 Spring Boot 进行集成。

为了能适应各种情况的用法,这里也提供了多种集成方式,基本上分为两大类。

  • 基于 starter 的自动配置
  • 基于 @MapperScan 注解的手工配置

1.3.1 mapper-spring-boot-starter

在 starter 的逻辑中,如果你没有使用 @MapperScan 注解,你就需要在你的接口上增加 @Mapper 注解,否则 MyBatis 无法判断扫描哪些接口。 这里的第一种用法没有用 @MapperScan 注解,所以你需要在所有接口上增加 @Mapper 注解。 以后会考虑增加其他方式。

你只需要添加通用 Mapper 提供的 starter 就完成了最基本的集成,依赖如下:

  1. <dependency>
  2. <groupId>tk.mybatis</groupId>
  3. <artifactId>mapper-spring-boot-starter</artifactId>
  4. <version>版本号</version>
  5. </dependency>

mybatis工具集 - 图1

最新版本号如上所示,你也可以从下面地址查看: http://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter 注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!

如果你需要对通用 Mapper 进行配置,你可以在 Spring Boot 的配置文件中配置 mapper. 前缀的配置。

例如在 yml 格式中配置:

  1. mapper:
  2. mappers:
  3. - tk.mybatis.mapper.common.Mapper
  4. - tk.mybatis.mapper.common.Mapper2
  5. notEmpty: true

在 properties 配置中:

  1. mapper.mappers=tk.mybatis.mapper.common.Mapper,tk.mybatis.mapper.common.Mapper2
  2. mapper.notEmpty=true

由于 Spring Boot 支持 Relax 方式的参数,因此你在配置 notEmpty 时更多的是用 not-empty,也只有在 Spring Boot 中使用的时候参数名不必和配置中的完全一致。

如果你对 Spring Boot 的 Environment 了解,你可以用 Spring Boot 支持的所有方式进行配置。

关于通用 Mapper 支持的所有配置请看后续的文档。

1.3.2 @MapperScan 注解配置

你可以给带有 @Configuration 的类配置该注解,或者直接配置到 Spring Boot 的启动类上,如下:

  1. @tk.mybatis.spring.annotation.MapperScan(basePackages = "扫描包")
  2. @SpringBootApplication
  3. public class SampleMapperApplication implements CommandLineRunner {

注意:这里使用的 tk.mybatis.spring.annotation.MapperScan !

你可以直接在 Spring Boot 的配置文件中直接配置通用 Mapper(参考1.3.1中的配置),还可以使用注解中提供的两个属性进行配置:

  1. /**
  2. * 通用 Mapper 的配置,一行一个配置
  3. *
  4. * @return
  5. */
  6. String[] properties() default {};
  7. /**
  8. * 还可以直接配置一个 MapperHelper bean
  9. *
  10. * @return
  11. */
  12. String mapperHelperRef() default "";

使用这种方式进行配置时,请参考 1.2.2.2 中的内容。

注意:这两个属性配置方式的优先级更高,所以建议在 Spring Boot 中通过配置文件(或 Environment)配置。

二、对象关系映射

通过上一章的文档集成好通用 Mapper 后,就可以继续看这里了。
通用 Mapper 使用 JPA 注解和自己提供的注解来实现对象关系映射,由于本章包含了很多细节,所以通过简单的示例先引领入门,然后在一步步深入去看详细的配置。
本章包含下面的内容,请按照顺序阅读。

2.1 简单示例

示例针对 MySql 数据库(数据库对主键影响较大,和 insert 关系密切)。

数据库有如下表:

  1. CREATE TABLE `country` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `countryname` varchar(255) DEFAULT NULL COMMENT '名称',
  4. `countrycode` varchar(255) DEFAULT NULL COMMENT '代码',
  5. PRIMARY KEY (`Id`)
  6. ) ENGINE=InnoDB AUTO_INCREMENT=10011 DEFAULT CHARSET=utf8 COMMENT='国家信息';

对应的 Java 实体类型如下:

  1. public class Country {
  2. @Id
  3. private Integer id;
  4. private String countryname;
  5. private String countrycode;
  6. //省略 getter 和 setter
  7. }

最简单的情况下,只需要一个 @Id 标记字段为主键即可。数据库中的字段名和实体类的字段名是完全相同的,这中情况下实体和表可以直接映射。

提醒:如果实体类中没有一个标记 @Id 的字段,当你使用带有 ByPrimaryKey 的方法时,所有的字段会作为联合主键来使用,也就会出现类似 where id = ? and countryname = ? and countrycode = ? 的情况。

第四章会介绍代码生成器,可以自动生成上面的实体和下面的接口代码

通用 Mapper 提供了大量的通用接口,这里以最常用的 Mapper 接口为例

该实体类对应的数据库操作接口如下:

  1. import tk.mybatis.mapper.common.Mapper;
  2. public interface CountryMapper extends Mapper<Country> {
  3. }

只要配置 MyBatis 时能注册或者扫描到该接口,该接口提供的方法就都可以使用。

该接口默认继承的方法如下:

  • selectOne
  • select
  • selectAll
  • selectCount
  • selectByPrimaryKey
  • 方法太多,省略其他…

从 MyBatis 中获取该接口后就可以直接使用:

  1. //从 MyBatis 或者 Spring 中获取 countryMapper,然后调用 selectAll 方法
  2. List<Country> countries = countryMapper.selectAll();
  3. //根据主键查询
  4. Country country = countryMapper.selectByPrimaryKey(1);
  5. //或者使用对象传参,适用于1个字段或者多个字段联合主键使用
  6. Country query = new Country();
  7. query.setId(1);
  8. country = countryMapper.selectByPrimaryKey(query);

如果想要增加自己写的方法,可以直接在 CountryMapper 中增加。

1. 使用纯接口注解方式时

  1. import org.apache.ibatis.annotations.Select;
  2. import tk.mybatis.mapper.common.Mapper;
  3. public interface CountryMapper extends Mapper<Country> {
  4. @Select("select * from country where countryname = #{countryname}")
  5. Country selectByCountryName(String countryname);
  6. }

这里只是举了个简单的例子,可以是很复杂的查询。

2. 如果使用 XML 方式,需要提供接口对应的 XML 文件

例如提供了 CountryMapper.xml 文件,内容如下:

  1. <!DOCTYPE mapper
  2. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4. <mapper namespace="tk.mybatis.sample.mapper.CountryMapper">
  5. <select id="selectByCountryName" resultType="tk.mybatis.model.Country">
  6. select * from country where countryname = #{countryname}
  7. </select>
  8. </mapper>

在接口中添加对应的方法:

  1. import tk.mybatis.mapper.common.Mapper;
  2. public interface CountryMapper extends Mapper<Country> {
  3. Country selectByCountryName(String countryname);
  4. }

在接口中添加其他方法的时候和只用 MyBatis 是完全一样的,但是需要注意,在对应的 XML 中,不能出现和继承接口中同名的方法!

多态! 在接口中,只要不是通过注解来实现接口方法,接口是允许重名的,真正调用会使用通用 Mapper 提供的方法。 例如在上面 CountryMapper 中提供一个带分页的 selectAll 方法:

  1. public interface CountryMapper extends Mapper<Country> {
  2. List<Country> selectAll(RowBounds rowBounds);
  3. }

在 Java 8 的接口中通过默认方法还能增加一些简单的间接调用方法,例如:

  1. public interface CountryMapper extends Mapper<Country> {
  2. //这个示例适合参考实现对乐观锁方法封装
  3. default void updateSuccess(Country country){
  4. Assert.assertEquals(1, updateByPrimaryKey(country));
  5. }
  6. }

在不熟悉 Java 语言和 MyBatis 特性前,不要轻易使用!

2.2 数据库映射

在 2.1 中看到的是最简单的情况,实际使用过程中也不会更复杂,下面是更详细的映射配置。

通用 Mapper 中,默认情况下是将实体类字段按照驼峰转下划线形式的表名列名进行转换。

例如 实体类的 userName 可以映射到表的 user_name 上。 如果想要修改默认的转换方式,可以在后续的配置中,修改 style 全局配置。

数据库映射主要涉及到一些注解和全局配置,这一节中会介绍所有注解,后面章节会有配置的介绍。

通用 Mapper 默认使用了几个简单的注解,其他 JPA 的注解默认并不支持,但是如果你开发自己的通用方法,你可以使用 JPA 注解或者引入自己的注解。

2.2.1@NameStyle 注解(Mapper)

这个注解可以在类上进行配置,优先级高于对应的 style 全局配置。

注解支持以下几个选项:

  1. normal, //原值
  2. camelhump, //驼峰转下划线
  3. uppercase, //转换为大写
  4. lowercase, //转换为小写
  5. camelhumpAndUppercase, //驼峰转下划线大写形式
  6. camelhumpAndLowercase, //驼峰转下划线小写形式

使用时,直接在类上配置即可,例如:

  1. @NameStyle(Style.camelhumpAndUppercase)
  2. public class Country

配置该注解后,对该类和其中的字段进行转换时,会将形如 userName 的字段转换为表中的 USER_NAME 字段。

2.2.2 @Table 注解(JPA)

@Table 注解可以配置 name,catalogschema 三个属性,配置 name 属性后,直接使用提供的表名,不再根据实体类名进行转换。其他两个属性中,同时配置时,catalog 优先级高于 schema,也就是只有 catalog 会生效。

配置示例如下:

  1. @Table(name = "sys_user")
  2. public class User

User 实体映射到 sys_user 表。

2.2.3 @Column 注解(JPA)

@Column 注解支持 name, insertableupdateable 三个属性。

name 配置映射的列名。

insertable 对提供的 insert 方法有效,如果设置 false 就不会出现在 SQL 中。

updateable 对提供的 update 方法有效,设置为 false 后不会出现在 SQL 中。

配置示例如:

  1. @Column(name = "user_name")
  2. private String name;

除了直接映射 nameuser_name 这种用法外,在使用关键字的情况,还会有下面的用法:

  1. @Column(name = "`order`")
  2. private String order;

对于关键字这种情况,通用 Mapper 支持自动转换,可以查看后续配置文档中的 wrapKeyword 配置。

2.2.4 @ColumnType 注解(Mapper)

这个注解提供的 column属性和 @Column 中的 name 作用相同。但是 @Column 的优先级更高。

除了 name 属性外,这个注解主要提供了 jdbcType 属性和 typeHandler 属性。

jdbcType 用于设置特殊数据库类型时指定数据库中的 jdbcType

typeHandler 用于设置特殊类型处理器,常见的是枚举。

用法示例如下:

  1. @ColumnType(
  2. column = "countryname",
  3. jdbcType = JdbcType.VARCHAR,
  4. typeHandler = StringTypeHandler.class)
  5. private String countryname;

2.2.5 @Transient 注解(JPA)

一般情况下,实体中的字段和数据库表中的字段是一一对应的,但是也有很多情况我们会在实体中增加一些额外的属性,这种情况下,就需要使用 @Transient 注解来告诉通用 Mapper 这不是表中的字段。

默认情况下,只有简单类型会被自动认为是表中的字段(可以通过配置中的 useSimpleType 控制)。

这里的简单类型不包含 Java 中的8种基本类型: byte,short,int,long,float,double,char,boolean 这是因为在类中,基本类型会有默认值,而 MyBatis 中经常会需要判断属性值是否为空,所以不要在类中使用基本类型,否则会遇到莫名其妙的错误。

对于类中的复杂对象,以及 Map,List 等属性不需要配置这个注解。

对于枚举类型作为数据库字段的情况,需要看配置中的 enumAsSimpleType 参数。

配置示例:

  1. @Transient
  2. private String otherThings; //非数据库表中字段

2.2.6 @Id 注解(JPA)

上面几个注解都涉及到映射。 @Id 注解和映射无关,它是一个特殊的标记,用于标识数据库中的主键字段。

正常情况下,一个实体类中至少需要一个标记 @Id 注解的字段,存在联合主键时可以标记多个。

如果表中没有主键,类中就可以不标记。

当类中没有存在标记 @Id 注解的字段时,你可以理解为类中的所有字段是联合主键。使用所有的 ByPrimaryKey 相关的方法时,有 where 条件的地方,会将所有列作为条件。

配置示例:

  1. @Id
  2. private Integer id;

或者联合主键:

  1. @Id
  2. private Integer userId;
  3. @Id
  4. private Integer roleId;

2.2.7 @KeySql 注解

主键策略注解,用于配置如何生成主键。

这是通用 Mapper 的自定义注解,改注解的目的就是替换 @GeneratedValue 注解。

关于该注解的用法可以查看 2.3 主键策略

2.2.8 @GeneratedValue 注解(JPA)

主键策略注解,用于配置如何生成主键。

由于不同类型数据库的配置不同,所以后面有一节专门介绍该注解的文档。

关于该注解的用法可以查看 2.3 主键策略

推荐使用上面的 @KeySql 注解。

2.2.9 @Version 注解(Mapper)

@Version 是实现乐观锁的一个注解,大多数人都不需要。

需要使用该注解的请看 2.4 乐观锁

2.2.10 @RegisterMapper 注解

为了解决通用 Mapper 中最常见的一个错误而增加的标记注解,该注解仅用于开发的通用接口,不是实体类上使用的,这里和其他注解一起介绍了。

4.0 版本提供的所有通用接口上都标记了该注解,因此自带的通用接口时,不需要配置 mappers 参数,该注解的具体用法会在 第五章 扩展通用接口 中介绍。

2.2.11 自定义注解

这部分提供给想要开发通用方法的朋友,如果只是使用,不需要阅读这部分内容。

在通用 Mapper 中,可以通过 EntityHelper.getColumns(entityClass) 方法来获取实体类的全部信息。

EntityColumn 中,通过下面的代码可以获取字段上的任意注解。

  1. //判断是否有某个注解
  2. boolean hasVersion = column.getEntityField().isAnnotationPresent(Version.class)
  3. //通过下面的代码可以获取注解信息
  4. Version version = column.getEntityField().getAnnotation(Version.class);

通过这种方式,在实现自己的通用方式时,可以根据需要来增加额外的注解来实现一些其他的用途。

2.3 主键策略

在 2.2 中介绍了所有其他注解的作用和用法,由于主键策略比较复杂,单独用一节来阐述。

首先主键策略和数据库关系很大,有些数据库支持主键自增,而有些数据库只能通过序列来获得。

新增的@KeySql 注解用于替换 @GeneratedValue 注解,因此 @KeySql 能以更简单方式实现原来的功能,下面的示例都先使用 @KeySql 进行配置,然后在使用 @GeneratedValue,大家可以自己选择。

2.3.1 JDBC 支持通过 getGeneratedKeys 方法取回主键的情况

这种情况首先需要数据库支持自增,其次数据库提供的 JDBC 支持 getGeneratedKeys 方法。

常见的如 MySql,SqlServer 支持这种模式。

这种情况下,配置主键策略最简单。

用法如下:

  1. @Id
  2. @KeySql(useGeneratedKeys = true)
  3. private Long id;

或:

  1. @Id
  2. @GeneratedValue(generator = "JDBC")
  3. private Long id;

为了让大家容易理解这里配置和 MyBatis 写法的关系,大家可以看看对应生成的 XML 代码:

  1. <insert id="insert" useGeneratedKeys="true" keyProperty="id">
  2. insert into country (id, countryname, countrycode)
  3. values (#{id},#{countryname},#{countrycode})
  4. </insert>

SqlServer 中使用时,需要设置 id 的 insertable=false

2.3.2 支持自增的数据库

支持自增的数据库列表如下:

  • DB2: VALUES IDENTITY_VAL_LOCAL()
  • MYSQL: SELECT LAST_INSERT_ID()
  • SQLSERVER: SELECT SCOPE_IDENTITY()
  • CLOUDSCAPE: VALUES IDENTITY_VAL_LOCAL()
  • DERBY: VALUES IDENTITY_VAL_LOCAL()
  • HSQLDB: CALL IDENTITY()
  • SYBASE: SELECT @@IDENTITY
  • DB2_MF: SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
  • INFORMIX: select dbinfo('sqlca.sqlerrd1') from systables where tabid=1

这个列表只是列举了比较常见的数据库,其他类似的也支持。 列表数据库名字后面对应的 SQL 是插入后取 id 的 SQL 语句

这类数据库主键策略配置示例如下:

  1. @Id
  2. //DEFAULT 需要配合 IDENTITY 参数(ORDER默认AFTER)
  3. @KeySql(dialect = IdentityDialect.DEFAULT)
  4. private Integer id;
  5. //建议直接指定数据库
  6. @Id
  7. @KeySql(dialect = IdentityDialect.MYSQL)
  8. private Integer id;

或:

  1. @Id
  2. @GeneratedValue(strategy = GenerationType.IDENTITY)
  3. private Integer id;

@GeneratedValue 用法还要配合 IDENTITY 参数(以及 ORDER 参数),这个参数值需要配置为对应的数据库,就是上面列表中的名字,具体的配置方法看后面章节的内容。

这种配置对应的 XML 形式为:

  1. <insert id="insertAuthor">
  2. <selectKey keyProperty="id" resultType="int" order="AFTER">
  3. SELECT LAST_INSERT_ID()
  4. </selectKey>
  5. insert into country (id, countryname, countrycode)
  6. values (#{id},#{countryname},#{countrycode})
  7. </insert>

IDENTITY 参数以及 ORDER 参数会决定 selectKey 中的 SQL 和 order 属性的值(这里是 AFTER)。

2.3.3 通过序列和任意 SQL 获取主键值

像 Oracle 中通过序列获取主键就属于这种情况,实际上 2.3.2 中的 SQL 也可以在这里配置。

除了类似序列获取值外,还可以是获取 UUID 的 SQL 语句,例如 select uuid()

2.3.3 和 2.3.2 的区别主要在于,2.3.2 是插入表之后才有 id 的值,2.3.3 是插入数据库前需要获取一个值作为主键。

在 Oracle 中,我们可以用下面的方式进行配置:

  1. @Id
  2. @KeySql(sql = "select SEQ_ID.nextval from dual", order = ORDER.BEFORE)
  3. private Integer id;

或:

  1. @Id
  2. @GeneratedValue(
  3. strategy = GenerationType.IDENTITY,
  4. generator = "select SEQ_ID.nextval from dual")
  5. private Integer id;

使用 @GeneratedValue 时也要配置一个 ORDER 全局参数,2.3.2 中提到的 AFTER,在 2.3.3 中需要配置为 BEFORE,具体配置方式看后面章节的内容。

这种配置对应的 XML 代码如下:

  1. <insert id="insertAuthor">
  2. <selectKey keyProperty="id" resultType="int" order="BEFORE">
  3. select SEQ_ID.nextval from dual
  4. </selectKey>
  5. insert into country (id, countryname, countrycode)
  6. values (#{id},#{countryname},#{countrycode})
  7. </insert>

这种用法中,values 中必须出现主键的值,否则就插不到数据库。

除了 Oracle 这种外,还有一种更普遍的用法,就是使用 UUID。

例如下面的配置:

  1. @Id
  2. @GeneratedValue(strategy = GenerationType.IDENTITY,generator = "select uuid()")
  3. private String id;

注意 SQL 返回值类型和这里接收的类型(String)一致。

2.3.4 @KeySql 介绍

上面的例子中都列举了 @KeySql 方式的用法。下面全面的看看这个注解:

  1. @Target({ElementType.FIELD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface KeySql {
  4. /**
  5. * 是否使用 JDBC 方式获取主键,优先级最高,设置为 true 后,不对其他配置校验
  6. *
  7. * @return
  8. */
  9. boolean useGeneratedKeys() default false;
  10. /**
  11. * 优先级第二,根据配置的数据库类型取回主键,忽略其他配置
  12. *
  13. * @return
  14. */
  15. IdentityDialect dialect() default IdentityDialect.DEFAULT;
  16. /**
  17. * 取主键的 SQL
  18. *
  19. * @return
  20. */
  21. String sql() default "";
  22. /**
  23. * 和 sql 可以配合使用,默认使用全局配置中的 ORDER
  24. *
  25. * @return
  26. */
  27. ORDER order() default ORDER.DEFAULT;
  28. }

通过上面的注释,大家可以看到主要的 3 个参数的优先级,useGeneratedKeys 优先级最高,其次是 dialect,最后是 sql。其中 order 只对 sql 方式有效。

useGeneratedKeysdialect 相当于预设的基本用法,和数据库以及驱动紧密相关。

sqlorder 更灵活。

2.4 乐观锁

乐观锁实现中,要求一个实体类中只能有一个乐观锁字段。

配置 @Version

想要使用乐观锁,只需要在实体中,给乐观锁字段增加 @tk.mybatis.mapper.annotation.Version 注解。

例如:

  1. public class User {
  2. private Long id;
  3. private String name;
  4. //...
  5. @Version
  6. private Integer version;
  7. //setter and getter
  8. }

@Version 注解有一个 nextVersion 属性,默认值为默认的实现,默认实现如下:

  1. package tk.mybatis.mapper.version;
  2. import java.sql.Timestamp;
  3. /**
  4. * @author liuzh
  5. * @since 3.5.0
  6. */
  7. public class DefaultNextVersion implements NextVersion {
  8. @Override
  9. public Object nextVersion(Object current) throws VersionException {
  10. if (current == null) {
  11. throw new VersionException("当前版本号为空!");
  12. }
  13. if (current instanceof Integer) {
  14. return (Integer) current + 1;
  15. } else if (current instanceof Long) {
  16. return (Long) current + 1L;
  17. } else if (current instanceof Timestamp) {
  18. return new Timestamp(System.currentTimeMillis());
  19. } else {
  20. throw new VersionException("默认的 NextVersion 只支持 Integer, Long" +
  21. " 和 java.sql.Timestamp 类型的版本号,如果有需要请自行扩展!");
  22. }
  23. }
  24. }

默认实现支持 Integer, Longjava.sql.Timestamp ,如果默认实现不能满足自己的需要,可以实现自己的方法,在配置注解时指定自己的实现即可。

支持的方法

  • delete
  • deleteByPrimaryKey
  • updateByPrimaryKey
  • updateByPrimaryKeySelective
  • updateByExample
  • updateByExampleSelective

这些方法在执行时会更新乐观锁字段的值或者使用乐观锁的值作为查询条件。

需要注意的地方

在使用乐观锁时,由于通用 Mapper 是内置的实现,不是通过 拦截器 方式实现的,因此当执行上面支持的方法时,如果版本不一致,那么执行结果影响的行数可能就是 0。这种情况下也不会报错!

所以在 Java6,7中使用时,你需要自己在调用方法后进行判断是否执行成功。

在 Java8+ 中,可以通过默认方法来增加能够自动报错(抛异常)的方法,例如:

  1. public interface MyMapper<T> extends Mapper<T> {
  2. default int deleteWithVersion(T t){
  3. int result = delete(t);
  4. if(result == 0){
  5. throw new RuntimeException("删除失败!");
  6. }
  7. return result;
  8. }
  9. default int updateByPrimaryKeyWithVersion(Object t){
  10. int result = updateByPrimaryKey(t);
  11. if(result == 0){
  12. throw new RuntimeException("更新失败!");
  13. }
  14. return result;
  15. }
  16. //...
  17. }

通过增加上面这种默认方法就能实现。

2.5 通用 Mapper @KeySql 注解 genId 方法详解

为了方便使用全局主键(例如:Vesta 是一款通用的ID产生器,互联网俗称统一发号器),通用 Mapper 4.0.2 版本增加了新的控制主键生成的策略。

@KeySql 注解增加了下面的方法:

  1. /**
  2. * Java 方式生成主键,可以和发号器一类的服务配合使用
  3. *
  4. * @return
  5. */
  6. Class<? extends GenId> genId() default GenId.NULL.class;

使用该功能的时候,需要配置 genId 属性。
由于生成主键的方式通常和使用环境有关,因此通用 Mapper 没有提供默认的实现。

GenId 接口如下:

  1. public interface GenId<T> {
  2. class NULL implements GenId {
  3. @Override
  4. public Object genId(String table, String column) {
  5. throw new UnsupportedOperationException();
  6. }
  7. }
  8. T genId(String table, String column);
  9. }

通过接口方法可以看出,在生成 Id 时,可以得到当前的表名和列名,我们可以使用这两个信息生成和表字段相关的 Id 信息。也可以完全不使用这两个信息,生成全局的 Id。

使用 genId 方式时,字段的值可以回写!

示例一,使用 UUID

完整测试代码: https://github.com/abel533/Mapper/tree/master/base/src/test/java/tk/mybatis/mapper/base/genid

1. 实现 GenId

使用 UUID 方式时,首先我们需要提供一个能产生 UUID 的实现类:

  1. public class UUIdGenId implements GenId<String> {
  2. @Override
  3. public String genId(String table, String column) {
  4. return UUID.randomUUID().toString();
  5. }
  6. }

这个实现类就是简单的返回了一个 String 类型的值,并且没有处理 UUID 中的 -

2. 配置 genId

例如有如下表(hsqldb 数据库):

  1. create table user (
  2. id varchar(64) NOT NULL PRIMARY KEY,
  3. name varchar(32),
  4. code VARCHAR(2)
  5. );

对应如下实体:

  1. public class User {
  2. @Id
  3. @KeySql(genId = UUIdGenId.class)
  4. private String id;
  5. private String name;
  6. private String code;
  7. public User() {
  8. }
  9. public User(String name, String code) {
  10. this.name = name;
  11. this.code = code;
  12. }
  13. //省略 setter 和 getter
  14. }

我们只需要在注解中配置 @KeySql(genId = UUIdGenId.class) 即可,需要注意的是,如果你使用了 @KeySql 提供的其他方式,genId 就不会生效,genId 是所有方式中优先级最低的。

3. 测试
  1. @Test
  2. public void testUUID(){
  3. SqlSession sqlSession = getSqlSession();
  4. try {
  5. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  6. for (int i = 0; i < countries.length; i++) {
  7. User user = new User(countries[i][0], countries[i][1]);
  8. Assert.assertEquals(1, mapper.insert(user));
  9. Assert.assertNotNull(user.getId());
  10. System.out.println(user.getId());
  11. }
  12. } finally {
  13. sqlSession.close();
  14. }
  15. }

输出的部分日志如下:

  1. DEBUG [main] - Setting autocommit to false on JDBC Connection [org.hsqldb.jdbc.JDBCConnection@3eb7fc54]
  2. DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,code ) VALUES( ?,?,? )
  3. DEBUG [main] - ==> Parameters: 94ee80ac-d156-412d-b63e-b75b49026874(String), Angola(String), AO(String)
  4. DEBUG [main] - <== Updates: 1
  5. 94ee80ac-d156-412d-b63e-b75b49026874
  6. DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,code ) VALUES( ?,?,? )
  7. DEBUG [main] - ==> Parameters: 0f21aab9-0637-4b7f-ade9-fe5bc4cf1693(String), Afghanistan(String), AF(String)
  8. DEBUG [main] - <== Updates: 1
  9. 0f21aab9-0637-4b7f-ade9-fe5bc4cf1693

示例二,简单的全局时序ID

完整测试代码: https://github.com/abel533/Mapper/tree/master/base/src/test/java/tk/mybatis/mapper/base/genid

1. 实现 GenId

使用 UUID 方式时,首先我们需要提供一个能产生 UUID 的实现类:

  1. public class SimpleGenId implements GenId<Long> {
  2. private Long time;
  3. private Integer seq;
  4. @Override
  5. public synchronized Long genId(String table, String column) {
  6. long current = System.currentTimeMillis();
  7. if (time == null || time != current) {
  8. time = current;
  9. seq = 1;
  10. } else if (current == time) {
  11. seq++;
  12. }
  13. return (time << 20) | seq;
  14. }
  15. }

这是一个简单的实现,通过同步方法保证唯一,不考虑任何特殊情况,不要用于生产环境。

2. 配置 genId

例如有如下表(hsqldb 数据库):

  1. create table country (
  2. id bigint NOT NULL PRIMARY KEY,
  3. countryname varchar(32),
  4. countrycode VARCHAR(2)
  5. );

对应如下实体:

  1. public class Country {
  2. @Id
  3. @KeySql(genId = SimpleGenId.class)
  4. private Long id;
  5. private String countryname;
  6. private String countrycode;
  7. public Country() {
  8. }
  9. public Country(String countryname, String countrycode) {
  10. this.countryname = countryname;
  11. this.countrycode = countrycode;
  12. }
  13. //省略 setter 和 getter
  14. }

我们只需要在注解中配置 @KeySql(genId = SimpleGenId.class) 即可,需要注意的是,如果你使用了 @KeySql 提供的其他方式,genId 就不会生效,genId 是所有方式中优先级最低的。

3. 测试
  1. @Test
  2. public void testGenId(){
  3. SqlSession sqlSession = getSqlSession();
  4. try {
  5. CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
  6. for (int i = 0; i < countries.length; i++) {
  7. Country country = new Country(countries[i][0], countries[i][1]);
  8. Assert.assertEquals(1, mapper.insert(country));
  9. Assert.assertNotNull(country.getId());
  10. System.out.println(country.getId());
  11. }
  12. } finally {
  13. sqlSession.close();
  14. }
  15. }

输出的部分日志如下:

  1. DEBUG [main] - Setting autocommit to false on JDBC Connection [org.hsqldb.jdbc.JDBCConnection@96def03]
  2. DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
  3. DEBUG [main] - ==> Parameters: 1598437075106922497(Long), Angola(String), AO(String)
  4. DEBUG [main] - <== Updates: 1
  5. 1598437075106922497
  6. DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
  7. DEBUG [main] - ==> Parameters: 1598437075176128513(Long), Afghanistan(String), AF(String)
  8. DEBUG [main] - <== Updates: 1
  9. 1598437075176128513
  10. DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
  11. DEBUG [main] - ==> Parameters: 1598437075178225665(Long), Albania(String), AL(String)
  12. DEBUG [main] - <== Updates: 1
  13. 1598437075178225665

示例三,基于 Vesta 的实现

通过前面两个例子应该能看出,通过不同的 GenId 实现就能切换不同的 ID 生成方式,ID 的类型需要保证一致。

这里提供一个能应用于生产环境的思路,由于和具体环境有关,因此这里没有直接可用的代码。

Vesta 地址:https://gitee.com/robertleepeak/vesta-id-generator

示例代码:

  1. public class VestaGenId implement GenId<Long> {
  2. public Long genId(String table, String column){
  3. //ApplicationUtil.getBean 需要自己实现
  4. IdService idService = ApplicationUtil.getBean(IdService.class);
  5. return idService.genId();
  6. }
  7. }

这个代码也很简单,由于注解只能配置类,不能注入,因此 VestaGenId 实现中不能直接注入 IdService,需要通过静态方法从 Spring Context 中获取,获取后就可以正常使用了。

对 Spring 熟悉的人可以很容易理解 ApplicationUtil.getBean 方法。 如果对 Spring 不了解,可以看看 http://sb33060418.iteye.com/blog/2372874 这里的SpringContextHolder

特殊的地方

使用 genId 时,在和数据库交互前,ID 值就已经生成了,由于这个 ID 和数据库的机制无关,因此一个实体中可以出现任意个 使用 genId 方式的 @KeySql 注解,这些注解可以指定不同的实现方法。

还没完,InsertListMapper 特殊支持

通用 Mapper 提供了好几个 InsertListMapper 接口,最早提供的都是和数据库相关的方法,所以在 Id 策略上都有和数据库相关的限制。

最新增加的 tk.mybatis.mapper.additional.insert.InsertListMapper 接口是一个和数据库无关的方法,他不支持任何的主键策略。但是有了 genId 方式后,这个接口增加了对 @KeySql 注解 genId 方法的支持。

下面是一个示例。

测试地址: https://github.com/abel533/Mapper/tree/master/extra/src/test/java/tk/mybatis/mapper/additional/insertlist

使用前面 示例一 中的 User 实体和表。

接口定义:

  1. import tk.mybatis.mapper.additional.insert.InsertListMapper;
  2. public interface UserMapper extends InsertListMapper<User> {
  3. }

测试代码:

  1. @Test
  2. public void testInsertList() {
  3. SqlSession sqlSession = getSqlSession();
  4. try {
  5. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  6. List<User> userList = new ArrayList<User>(countries.length);
  7. for (int i = 0; i < countries.length; i++) {
  8. userList.add(new User(countries[i][0], countries[i][1]));
  9. }
  10. Assert.assertEquals(countries.length, mapper.insertList(userList));
  11. for (User user : userList) {
  12. Assert.assertNotNull(user.getId());
  13. System.out.println(user.getId());
  14. }
  15. } finally {
  16. sqlSession.close();
  17. }
  18. }

部分日志:

  1. DEBUG [main] - Setting autocommit to false on JDBC Connection [org.hsqldb.jdbc.JDBCConnection@778d1062]
  2. DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,role ) VALUES ( ?,?,? ) ,
  3. ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) ,
  4. ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) ,
  5. ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? )
  6. DEBUG [main] - ==> Parameters:
  7. 1b8511b7-304a-47a7-abec-c2f38e498e69(String), Angola(String), AO(String),
  8. 2d31db7e-5d4c-4997-9db3-44163018a3c7(String), Afghanistan(String), AF(String),
  9. 31ac04d7-cb61-45b2-9b7d-d75e82a65529(String), Albania(String), AL(String),
  10. 39d77534-bf9a-47d4-aaa0-63d32c2d4562(String), Algeria(String), DZ(String),
  11. 5186f2e5-57b1-4837-8e06-0392f70e6dbe(String), Andorra(String), AD(String),
  12. 4ccab2fb-db59-4588-ae90-c33e9acc5a84(String), Anguilla(String), AI(String),

通过 genId 方式可以批量插入,并且可以回写 ID。

三、配置介绍

由于数据库存在各种各样的差异,因此有些时候要做一些必要的配置。

此外通用 Mapper 还提供了一些控制参数和一些和主键策略相关的参数。

文档提供的参数基于 4.0 及以后的版本 有些参数仍然存在,但是由于不推荐使用,这里不会介绍(如 UUID)。

提醒:看配置前,一定要看第二章的对象关系映射,否则不会明白这些参数的用途。

参数的配置方式在第一章中,针对不同的集成环境都写了详细的文档。

通用 Mapper 提供了下面这些参数:

  1. mappers
  2. IDENTITY
  3. ORDER(别名: order, before)
  4. catalog
  5. schema
  6. notEmpty
  7. style
  8. enableMethodAnnotation
  9. useSimpleType
  10. usePrimitiveType
  11. simpleTypes
  12. enumAsSimpleType
  13. wrapKeyword
  14. checkExampleEntityClass
  15. safeDelete
  16. safeUpdate
  17. useJavaType

下面分别对这些参数进行介绍。

3.1 mappers

在 4.0 以前这是一个非常重要的参数,当时只有通过 mappers 配置过的接口才能真正调用,由于很多人不注意看文档,通用 Mapper 90% 的问题都出在这个参数上。

4.0 之后,增加了一个 @RegisterMapper 注解,通用 Mapper 中提供的所有接口都有这个注解,有了该注解后,通用 Mapper 会自动解析所有的接口,如果父接口(递归向上找到的最顶层)存在标记该注解的接口,就会自动注册上。因此 4.0 后使用通用 Mapper 提供的方法时,不需要在配置这个参数。

当你自己扩展通用接口时,建议加上该注解,否则就要配置 mappers 参数。

3.2 IDENTITY

取回主键的方式,可以配置的值如 2.3.2 中所列的数据库类型:

  • DB2: VALUES IDENTITY_VAL_LOCAL()
  • MYSQL: SELECT LAST_INSERT_ID()
  • SQLSERVER: SELECT SCOPE_IDENTITY()
  • CLOUDSCAPE: VALUES IDENTITY_VAL_LOCAL()
  • DERBY: VALUES IDENTITY_VAL_LOCAL()
  • HSQLDB: CALL IDENTITY()
  • SYBASE: SELECT @@IDENTITY
  • DB2_MF: SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
  • INFORMIX: select dbinfo('sqlca.sqlerrd1') from systables where tabid=1

配置时,写为:

  1. IDENTITY=MYSQL

这个参数配置的同时,还经常伴着 3.2 中的 ORDER 参数。

3.3 ORDER(别名: order, before)

<selectKey>中的order属性,可选值为BEFOREAFTER

后来为了方便在 Spring Boot 中配置该参数,符合 Boot 的规范,增加了 orderbefore 两个别名。

在 3.1 的配置以及 2.3.2 和 2.3.3 中都提到了这个参数。

在支持主键递增,满足 2.3.2 中的情况时,配置如下:

  1. //Properties 方式配置时
  2. ORDER=AFTER
  3. //还可以
  4. order=AFTER
  5. //或
  6. before=false
  7. //Spring Boot 中,比上面多个前缀,并且 ORDER 不能使用
  8. mapper.order=AFTER
  9. //或者(Spring Boot)
  10. mapper.before=false

在类似Oracle序列或者通用的 UUID 时,配置如下:

  1. //Properties 方式配置时
  2. ORDER=BEFORE
  3. //还可以
  4. order=BEFORE
  5. //或
  6. before=true
  7. //Spring Boot 中,比上面多个前缀,并且 ORDER 不能使用
  8. mapper.order=BEFORE
  9. //或者(Spring Boot)
  10. mapper.before=true

上面示例中有多种写法,使用时按照需要选择一种即可,切勿同时配置!

3.4 catalog

数据库的catalog,如果设置该值,查询的时候表名会带catalog设置的前缀。

3.5 schema

catalogcatalog优先级高于schema

3.6 notEmpty

insertSelectiveupdateByPrimaryKeySelective 中,是否判断字符串类型 !=''

配置方式:

  1. notEmpty=true

3.7 style

实体和表转换时的默认规则,在 2.2 和 2.2.1 中都提到了这个参数,可选值如下:

  • normal:原值
  • camelhump:驼峰转下划线
  • uppercase:转换为大写
  • lowercase:转换为小写
  • camelhumpAndUppercase:驼峰转下划线大写形式
  • camelhumpAndLowercase:驼峰转下划线小写形式

配置方式如下:

  1. style=camelhumpAndUppercase

3.8 enableMethodAnnotation

可以控制是否支持(getter 和 setter)在方法上使用注解,默认false

配置方式如下:

  1. enableMethodAnnotation=true

启用后,可以按照下面方式使用:

  1. private String name;
  2. @Column(name = "user_name")
  3. public void setName(String name){
  4. this.name = name;
  5. }

3.9 useSimpleType

默认 true,启用后判断实体类属性是否为表字段时校验字段是否为简单类型,如果不是就忽略该属性,这个配置优先级高于所有注解。

注意:byte, short, int, long, float, double, char, boolean 由于存在默认值,这里不会作为简单类型对待!也就是默认情况下,这些字段不会和表字段进行映射。2.2.5 中也强调了这一点。

配置方式如下:

  1. useSimpleType=true

3.10 usePrimitiveType

为了方便部分还在使用基本类型的实体,增加了该属性,只有配置该属性,并且设置为 true 才会生效,启用后,会扫描 8 种基本类型。

配置方式如下:

  1. usePrimitiveType=true

3.11 simpleTypes

默认的简单类型在 SimpleTypeUtil 中,使用该参数可以增加额外的简单类型,通过逗号隔开的全限定类名添加。

配置方式如:

  1. simpleTypes=xxx.GenderEnum,xxx.JobEnum

3.12 enumAsSimpleType

用于配置是否将枚举类型当成基本类型对待。

默认 simpleType 会忽略枚举类型,使用 enumAsSimpleType 配置后会把枚举按简单类型处理,需要自己配置好 typeHandler

配置方式如下:

  1. enumAsSimpleType=true

3.13 wrapKeyword

配置后会自动处理关键字,可以配的值和数据库有关。

例如 sqlserver 可以配置为 [{0}],使用 {0} 替代原来的列名。

MySql 对应的配置如下:

  1. wrapKeyword=`{0}`

使用该配置后,类似 private String order 就不需要通过 @Column 来指定别名。

3.14 checkExampleEntityClass

默认 false 用于校验通用 Example 构造参数 entityClass 是否和当前调用的 Mapper<EntityClass> 类型一致。

假设存在下面代码:

  1. Example example = new Example(City.class);
  2. example.xxx...;//设置条件的方法
  3. countryMapper.selectByExample(example);

注意,这里使用 City 创建的 Example,本该使用 cityMapper 来调用,但是这里使用了 countryMapper,默认情况下会出现字段不匹配的错误,更特殊的情况下会正好匹配字段,但是却操作错了表!

配置该字段为 true 后就会对不匹配的情况进行校验!

配置如下:

  1. checkExampleEntityClass=true

3.15 safeDelete

配置为 true 后,delete 和 deleteByExample 都必须设置查询条件才能删除,否则会抛出异常。

配置如下:

  1. safeDelete=true

使用效果:

  1. Caused by: tk.mybatis.mapper.MapperException: 通用 Mapper 安全检查: 当前操作的方法没有指定查询条件,不允许执行该操作!
  2. at tk.mybatis.mapper.util.OGNL.notAllNullParameterCheck(OGNL.java:91)
  3. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  4. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  5. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  6. at java.lang.reflect.Method.invoke(Method.java:497)
  7. at org.apache.ibatis.ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:899)
  8. at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1544)
  9. ... 53 more

3.16 safeUpdate

配置为 true 后,updateByExample 和 updateByExampleSelective 都必须设置查询条件才能删除,否则会抛出异常(org.apache.ibatis.exceptions.PersistenceException)。

updateByPrimaryKey 和 updateByPrimaryKeySelective 由于要求必须使用主键,不存在这个问题。

配置如下:

  1. safeUpdate=true

使用效果:

  1. Caused by: tk.mybatis.mapper.MapperException: 通用 Mapper 安全检查: 当前操作的方法没有指定查询条件,不允许执行该操作!
  2. at tk.mybatis.mapper.util.OGNL.notAllNullParameterCheck(OGNL.java:91)
  3. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  4. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  5. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  6. at java.lang.reflect.Method.invoke(Method.java:497)
  7. at org.apache.ibatis.ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:899)
  8. at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1544)
  9. ... 53 more

3.17 useJavaType

设置 true 时,参数中会增加 javaType 设置,如 {id, javaType=java.lang.Long}。在 <resultMap> 中也会设置 javaType 属性。

配置如下:

  1. useJavaType=true

对于使用 User extends Pk<Long> 形式时,需要设置,否则 mybatis 低版本(<3.4.0) 无法识别类型。

建议升级到 mybatis 3.4.x 的最新版本

例如:

  1. class Pk<T> {
  2. private T id;
  3. public T getId() {
  4. return id;
  5. }
  6. public void setId(T id) {
  7. this.id = id;
  8. }
  9. }
  10. class User extends Pk<Long> {
  11. private String name;
  12. }

如果不设置 javaType,低版本的 MyBatis 会认为 id 的类型为 Object。升级 MyBatis 到 3.4.0+ 最好。

设置 useJavaType 只对通用 Mapper 自己方法有效,自己写的其他方法还需要自己指定。

特别注意 使用 useJavaType=true 后,调用 ByPrimaryKey 类的方法时,需要按照实体中对应的类型来指定,例如主键类型为 Long 时,必须 mapper.selectByPrimaryKey(1L)。 不配置时,没有这个限制,可以使用 mapper.selectByPrimaryKey(1)mapper.selectByPrimaryKey("1") 等类型。