MybatisBoost Maven central Build Status Coverage Status

Mybatis SQL开发神器MybatisBoost,为Mybatis带来诸多官方没有的高级特性,包含通用CrudMapperMybatis语法增强字段生成JSON映射智能方法查询无感知分页SQL指标与监控、流式查询(开发中...)等功能,使用MybatisBoost来提升开发效率,轻松编写SQL代码!

使用MybatisBoost的最低要求:

快速开始

基于Spring Boot以及mybatis-spring-boot-starter项目的快速开始。

Maven:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>cn.mybatisboost</groupId>
    <artifactId>mybatis-boost-spring-boot-starter</artifactId>
    <version>2.3.3-SNAPSHOT</version>
</dependency>

Gradle:

compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
compile 'cn.mybatisboost:mybatis-boost-spring-boot-starter:2.3.3-SNAPSHOT'

在手动创建SqlSessionFactory Bean的情况下,请确保MybatisBoost的Mybatis Plugin有被加载。

@Bean
public SqlSessionFactory sqlSessionFactory(ObjectProvider<Interceptor[]> interceptorsProvider) {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setPlugins(interceptorsProvider.getIfAvailable()); // 确保加载了所有的Mybatis Plugin
    sessionFactory.setTypeHandlers(new TypeHandler[]{new JsonTypeHandler()}); // 确保加载了JSON映射所需的Mybatis TypeHandler
    ...
}

如果你的数据库Table名称与项目中的POJO类名一致,Table的列名称与POJO属性的名称命名方式也一致的话(大小写忽略),那么恭喜你,你已经成功引入了MybatisBoost,可以跳过下一章《名称映射》的内容。

后续内容将使用术语“表”来代表数据库中的表,“列”来代表数据库表中的列,“POJO”来代表对应的实体类,“属性”和“字段”来代表POJO中的成员变量

名称映射

配置名称映射是为了使MybatisBoost能自动地找到POJO对应的表,以及POJO中的属性对应的列。名称映射分为自动映射和手动标注两种方案。

对于表名与POJO类名之间的自动映射,MybatisBoost内置有几个常用的表名映射器,如果内置的表名映射器无法满足你的需求,你也可以基于NameAdaptor接口实现自己的表名映射器。

表名映射器 POJO类名 映射到的表名
NoopNameAdaptor DemoTable DemoTable
TPrefixedNameAdaptor DemoTable T_DemoTable
SnakeCaseNameAdaptor DemoTable demo_table

MybatisBoost默认使用NoopNameAdaptor表名映射器,对应的application.properties配置如下:

mybatisboost.name-adaptor=cn.mybatisboost.core.adaptor.NoopNameAdaptor

对于列名与属性名之间的自动映射,MybatisBoost采用了Mybatis内置的MapUnderscoreToCamelCase功能,默认使用CamelCase命名方式。如果你的数据库列名命名方式为snake_case命名方式,请使用如下的application.properties配置:

mybatis.configuration.map-underscore-to-camel-case=true

除了自动映射方案,MybatisBoost同样提供手动标注的功能。

现在假设你的表名为“DEMO_ThisTable”,你的POJO类名为“ThatTable”,表名和POJO类名之间并无任何联系,则可以使用JPA提供的标准注解进行手动标注。

同样地,主键也可以使用JPA提供的标准注解进行手动标注。

对于列名与POJO属性名之间的映射,MybatisBoost采用约定大于配置的思想,不提供手动标注的功能。

@Table(name = "DEMO_ThisTable")
public class ThatTable {

    @Id // 默认以名称为“id”的字段作为主键,否则需要使用@Id注解手动标注
    private Long myId;
    private String myField;
    ...
}

到此,你已经可以开始使用MybatisBoost了,下面将逐一介绍MybatisBoost的各种功能特性。

基础知识

为了使MybatisBoost生效,你的Mybatis Mapper接口必须直接或间接地继承GenericMapper<T>接口,范型<T>代表此Mapper对应的POJO类。

MybatisBoost提供的所有通用Mapper都继承于GenericMapper<T>接口

通用CrudMapper

继承于CrudMapper<T>接口的Mybatis Mapper接口即自动拥有了CrudMapper接口的所有功能。

CrudMapper接口中的方法使用POJO中所有的属性参与CRUD,但不包括以Selective结尾的方法,这些方法会过滤值为null的属性,即POJO中值为null的属性不参与CRUD。

带有properties参数的方法,可使用properties参数指定参与插入、更新的属性。如果properties参数的第一个字符串为!,则代表排除后续指定的属性,如new String[]{"!", "id"}则代表除“id”以外,其他属性都参与CRUD。

同样地,带有conditionProperties参数的方法,可使用conditionProperties参数指定用于WHERE条件的属性。

CrudMapper中的所有方法如下:

public interface CrudMapper<T> {

    int count(T entity, String... conditionProperties);
    T selectOne(T entity, String... conditionProperties);
    List<T> select(T entity, String... conditionProperties);
    List<T> selectWithRowBounds(T entity, RowBounds rowBounds, String... conditionProperties);
    int countAll();
    List<T> selectAll();
    List<T> selectAllWithRowBounds(RowBounds rowBounds);
    T selectById(Object id);
    List<T> selectByIds(Object... ids);
    int insert(T entity, String... properties);
    int batchInsert(List<T> entities, String... properties);
    int insertSelective(T entity, String... properties);
    int batchInsertSelective(List<T> entities, String... properties);
    int update(T entity, String... conditionProperties);
    int updatePartial(T entity, String[] properties, String... conditionProperties);
    int updateSelective(T entity, String... conditionProperties);
    int updatePartialSelective(T entity, String[] properties, String... conditionProperties);
    int delete(T entity, String... conditionProperties);
    int deleteByIds(Object... ids);
}

如果你不需要CrudMapper接口里的所有方法,可以把CrudMapper接口中你所需的方法复制到你自己的Mybatis Mapper里即可。(需要把方法上的注解也一并复制。)

MySQL CrudMapper

除了通用的CrudMapper,MybatisBoost还提供专用于MySQL的MysqlCrudMapper<T>接口,在CrudMapper的基础上,增加了几个支持MySQL特性的方法。

public interface MysqlCrudMapper<T> extends CrudMapper<T> {

    int save(T entity, String... properties);
    int saveSelective(T entity, String... properties);
    int batchSave(List<T> entity, String... properties);
    int batchSaveSelective(List<T> entity, String... properties);
    int replace(T entity, String... properties);
    int replaceSelective(T entity, String... properties);
    int batchReplace(List<T> entity, String... properties);
    int batchReplaceSelective(List<T> entity, String... properties);
}

其中,save方法使用的是MySQL的“ON DUPLICATE KEY UPDATE”特性,replace方法使用的是“REPLACE INTO”特性。

Mybatis语法增强

为了使SQL的编写变得更简单,MybatisBoost提供了数个Mybatis和SQL语法增强的功能,包括自动参数映射、INSERT语法增强、UPDATE语法增强、表名变量、空值检测和集合参数映射。每个增强都可以单独使用,也可以联合使用。

自动参数映射

Mybatis设计之中的一个不合理之处,在于舍弃了JDBC原生的参数占位符(即?)。显而易见的是,简单的SQL语句根本没有必要使用Mybatis的#{variable}语法去做多余的映射,这种麻烦在编写INSERT和UPDATE语句的时候尤为明显。

为此,MybatisBoost恢复了JDBC原生的参数占位符功能,MybatisBoost会自动按照参数的声明顺序做出正确的映射。

@Update("UPDATE table SET column1 = ? WHERE condition1 = ?")
int update(String a, String b);

自动参数映射目前还不支持嵌套属性,即不支持自动映射到对象中的属性

INSERT语法增强

MybatisBoost提供了更为简洁的INSERT语法,使得SQL的编写变得更为简单。

@Insert("INSERT *")
int insertOne1(T entity); // 插入一条记录,插入所有字段

@Insert("INSERT column1, column2, column3")
int insertOne2(T entity); // 插入一条记录,只插入column1、column2、column3三个字段

@Insert("INSERT NOT column4, column5")
int insertOne3(T entity); // 插入一条记录,插入除了column4、column5以外的所有字段

@Insert("INSERT *")
int insertMany(List<T> entities); // 批量插入,插入POJO中的所有字段

UPDATE语法增强

同样地,MybatisBoost提供了更为简洁的UPDATE语法。

@Update("UPDATE SET *")
int update1(T entity); // 更新所有字段

@Update("UPDATE SET column1, column2, column3")
int update2(T entity); // 只更新column1、column2、column3三个字段

@Update("UPDATE SET NOT column4, column5")
int update3(T entity); // 更新除了column4、column5以外的所有字段

@Update("UPDATE SET column1, column2 WHERE condition1 = ?")
int update3(String a, String b, String c); // 更新column1、column2两个字段,并且条件是“condition1 = c”

表名变量

在编写SQL语句时,SQL中的表名可使用#t代替,MybatisBoost会自动替换成正确的表名。

此功能不仅简化了表名的编写,还使得SQL语句具有了可重用性。

SELECT * FROM #t

空值检测

现有一条SQL语句如下:

SELECT * FROM Post WHERE id = #{id} AND Name != #{name}

假设传入的id参数和name参数都为null,则SQL会自动重写为如下的形式:

SELECT * FROM Post WHERE id IS NULL AND Name IS NOT NULL

集合参数映射

使用MybatisBoost之前,集合参数的映射方法:

<select>
    SELECT * FROM Post WHERE id IN
    <foreach item="item" index="index" collection="list"
             open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

使用MybatisBoost之后,集合参数的映射方法就如同普通参数一样:

SELECT * FROM Post WHERE id IN #{list}

字段生成

在执行INSERT语句前,MybatisBoost会检查待插入的POJO,如果发现其中的字段被标记了JPA的@GeneratedValue注解,则会调用相应的ValueGenerator<T>自动填充字段。

以下示例中,“id”字段使用了内置的UuidGenerator生成器生成主键:

public class Post {

    @GeneratedValue(generator = "cn.mybatisboost.generator.UuidGenerator")
    private String id;
    ...
}

目前,MybatisBoost提供了一个UuidGenerator生成器和Snowflake算法的实现,你也可以基于ValueGenerator<T>接口实现自己的字段生成器。

JSON映射

JSON映射功能可以自动地将嵌套对象序列化成JSON保存到数据库中,同样地,也可以自动地将数据库中的JSON反序列化成嵌套对象。

class Post {

    private String id;
    private Article article;
    private List<Article> articleList;
    private Map<String,Article> articleMap;
    ...
}

class Article extends JsonType {

    private String id;
    private String name;
    ...
}

在插入Project对象时,article、articleList、articleMap三个字段会自动序列化成JSON字符串。相反地,在查询Project对象时,数据库中的JSON字符串会反序列化成相应的嵌套对象。

嵌套对象必须继承于JsonType

智能方法查询

简单的SQL语句千篇一律,能否不再编写那些显而易见的SQL语句呢?答案是肯定的。

public interface PostMapper extends GenericMapper<Post> {

    @org.apache.ibatis.annotations.Mapper
    List<Post> selectByPostIdAndPostDateBw(int a, Date b, Date c);
}

以上代码片段是一个简单的方法查询,MybatisBoost会自动识别并生成对应的SQL语句。

SELECT * FROM #t WHERE PostId = ? AND PostDate BETWEEN ? AND ?

只要以Mybatis的@Mapper注解标记的方法,MybatisBoost都会智能的生成相应SQL语句,让你的双手解放于编写千篇一律的简单SQL语句。

下面我们就以“selectByPostIdAndPostDateBw”为例来分析下如何编写智能方法查询,分解后的单词如下:select By PostId And PostDate Bw。其中“select”称为“方法词”,“By”称为“辅助词”,“PostId”和“PostDate”为POJO中的属性,“And”和“Bw”(BETWEEN的缩写)为SQL关键字,其中,方法词和辅助词都是必须的,其他的为可选项。

目前支持的方法词:select、count、delete。

目前支持的关键字:

关键字 缩写 对应的SQL片段
And And AND
Or Or OR
Is Is = ?
Equals E = ?
Between Bw BETWEEN ? AND ?
NotBetween Nbw NOT BETWEEN ? AND ?
LessThan Lt < ?
LessThanEqual Lte <= ?
GreaterThan Gt > ?
GreaterThanEqual Gte >= ?
After Af > ?
Before Bf < ?
IsNull N IS NULL
IsNotNull Nn IS NOT NULL
IsEmpty E = ''
IsNotEmpty Ne != ''
Like L LIKE ?
NotLike Nl NOT LIKE ?
OrderBy Ob ORDER BY
Not Not != ?
In In IN ?
NotIn Ni NOT IN ?
IsTrue T = TRUE
IsFalse F = FALSE
Asc Asc ASC
Desc Desc DESC

同时,智能查询方法还支持分页功能:

public interface PostMapper extends GenericMapper<Post> {

    @Mapper
    List<Post> selectAllOffset10Limit100();

    @Mapper
    List<Post> selectTop3();

    @Mapper
    Post selectFirst();
}

无感知分页

Mybatis本身其实已经提供了分页功能,可惜它的实现并不优雅。为此,MybatisBoost在使用方法不变的前提下,透明的修改了实现,做到了真正的物理分页

List<T> selectAll(RowBounds rowBounds); // RowBounds内含offset和limit字段

目前暂时只支持MySQL和PostgreSQL数据库,后续支持敬请期待

SQL指标与监控

默认情况下不开启SQL指标与监控功能。话不多说,直接上配置,简单易懂。

# 开启SQL指标与监控功能
mybatisboost.metric.enabled=true
# 在日志中打印SQL和执行时间
mybatisboost.showQuery=true

# 以下配置为可选配置

# 打印SQL时是否同时打印SQL参数
mybatisboost.showQueryWithParameters=boolean
# 慢SQL阈值(默认情况下,慢SQL会打印在日志中)
mybatisboost.slowQueryThresholdInMillis=long
# 慢SQL回调处理器(参数一为SQL语句,参数二为执行时间ms),可编写代码实现一些自定义逻辑,比如报警
mybatisboost.slowQueryHandler=Class<? extends BiConsumer<String, Long>>

欢迎使用

光看文档太抽象?mybatis-boost-test模块下有各种使用case,敬请参考。

MybatisBoost中没有你想要的功能?亦或是MybatisBoost有BUG?欢迎各位提出issues!