项目简介
Mybatis通用Mapper插件,用于解决大多数基础CRUD,简化sql语法并提高动态执行效率,拥有更丰富的Api。用最少的配置,提供一个健全的使用体系。
注意:此项目是一款完全开源的项目,您可以在任何适用的场景使用它,商用或者学习都可以,如果您有任何项目上的疑问,可以在issue上提出您问题,我会在第一时间回复您,如果您觉得它对您有些许帮助,希望能留下一个您的星星(★),谢谢。
此项目遵照 Apache 2.0 License 开源许可
技术讨论QQ群:947460272
核心亮点
- 无侵入:100%兼容mybatis,不与mybatis冲突,只添加功能,对现有程序无任何影响;
- 配置少:所有配置均在原始mybatis的基础上读取,不增加额外配置消耗;
- 无捆绑关系:与XML文件独立,有无独立XML均可;
- 效率高:由Xml+OGNL模式转向Java+ExpressionEngine的模式,省去了XML的解析和OGNL解析的时间消耗;
- 灵活的规则制定:灵活多元化的(表/列)规则制定,完美融合各种(名字/类型等)不统一的场景;
- SQL模板语法:模板语法简单易掌握,一次开发,终生受用,避免因数据库变化而大量更改SQL语句的场景。表达式会在程序完全启动之前编译成完整的SQL,仅留下需要动态解析的表达式,对效率的影响微乎甚微;
- CRUD增强:除了提供基础的CRUD外还提供一系列高级辅助方法(Example/ForUpdate/聚合函数/逻辑删除/回收站/乐观锁等);
- 便捷的条件查询:更简单便捷的Example条件查询,提供符合SQL语义化的链式条件函数调用;
- 支持多主键:支持多主键场景,CRUD中可选择特定主键进行操作;
- 丰富的主键生成策略:支持JDBC自增主键、自定义主键SQL查询、雪花ID、UUID、自定义主键生成策略等;
- 支持常量值:灵活化的常量值生成策略,可针对不同场景做不同操作,均支持雪花ID/UUID等内置生成器;
- 可排序字段:可对SQL字段排列顺序进行干扰,简直是强迫症重度患者的福音啊;
- 粘连性小:可以独立使用,纯Java环境/spring/springboot都提供了单独的组件。
- 可扩展的开发模式:继承Marker/Mapper/BaseMapper即可继续增强基础CRUD。
关联文档
关于整合spring,请移步到:https://github.com/tangxbai/mybatis-mapper-spring
关于整合springboot,请移步到:https://github.com/tangxbai/mybatis-mapper-spring-boot
项目演示
功能列表
- 支持模板语法
- 支持SQL注释
- 支持各种主键生成策略
- 支持多主键场景
- 更方便快捷的条件查询
- 支持聚合函数统计
- 支持数据库乐观锁
- 支持各种逻辑删除
- 支持逻辑删除数据的恢复
- 支持查询自定义返回Bean类型
- 支持零MapperXML配置文件
- 扩展插件Api
- 提供各种场景的日志打印
- 提供更丰富的API
快速开始
Maven方式(推荐)
<dependency>
<groupId>com.viiyue.plugins</groupId>
<artifactId>mybatis-mapper</artifactId>
<version>[VERSION]</version>
</dependency>
如果你没有使用Maven构建工具,那么可以通过以下途径下载相关jar包,并导入到你的编辑器。
如何获取最新版本?点击这里获取最新版本
如何使用
1、配置 mybatis.xml
这个文件具体如何配置不作过多说明,你可以拉取相关demo查看详细配置,在配置上也没有什么区别,需要注意的是 typeAliases(实体别名配置)一定要配置,不然插件可能无法正常工作。
2、配置数据库实体Bean
@Table( prefix = "t_" ) // 表名生成规则,可以配置更多详细说明
@NamingRule( NameStyle.UNDERLINE ) // 字段和数据库列之间的转换规则
@ValueRule( ValueStyle.SHORT ) // 值的生成规则,类似于:#{id}
@ExpressionRule( ExpressionStyle.SHORT ) // 表达式生成规则,类似于: id = #{id}
@DefaultOrderBy( "#pk" ) // #默认排序字段,"pk"为主键占位符,指向当前生效的主键字段,也可以直接写"id"
public class YourModelBean {
@Id // 主键可以配置多个,但是只会有一个生效,Api方法中如果想要使用其他主键请指明所在下标位置
@Index( Integer.MIN_VALUE )
@GeneratedKey( useGeneratedKeys = true ) // JDBC支持的自增主键获取方式
// @GeneratedKey( valueProvider = SnowFlakeIdValueProvider.class ) // 雪花Id,插件提供的两种主键生成策略之一
// @GeneratedKey( statement = "MYSQL" ) // 枚举引用
// @GeneratedKey( statement = "SELECT LAST_INSERT_ID()" ) // 自增主键SQL查询语句
// @GeneratedKey( statementProvider = YourCustomStatementProvider.class ) // 通过Provider提供SQL语句
private Long id;
@Index( Integer.MAX_VALUE - 4 )
@Column( jdcbType = Type.CHAR ) // 对字段进行详细描述
@LogicallyDelete( selectValue = "Y", deletedValue = "N" ) // 开启逻辑删除支持,只能配置一次
private Boolean display;
@Index( Integer.MAX_VALUE - 3 )
private Date createTime;
@Index( Integer.MAX_VALUE - 2 )
private Date modifyTime;
@Version // 开启乐观锁支持,只能配置一次
@Index( Integer.MAX_VALUE - 1 )
@Column( insertable = false )
private Long version;
// setter/getter...
// ----------------------------------------------------------------
// @Index主要对字段出现顺序进行干扰,对字段进行干扰以后,输出的顺序大概是这样:
// => id, ..., display, create_time, modify_time, version
// 如果您未使用@Index注解,那么字段的原始顺序是这样的:
// => id, display, create_time, modify_time, version, ...
// 默认输出会将父类的字段排在最前面
// ----------------------------------------------------------------
}
3、Mapper接口需要继承 BaseMapper
或者 Mapper
// 继承Mapper
public interface YourMapper extends Mapper<YourModelBean, YourModelBeanDTO, Long> {
}
// 或者继承BaseMapper
public interface YourMapper extends BaseMapper<YourModelBean, YourModelBeanDTO, Long> {
}
4、使用方式
SqlSessionFactory factory = new MyBatisMapperFactoryBuilder().build( Resources.getResourceAsStream("your-mybatis.xml"));
SqlSession session = factory.openSession();
YourMapper mapper = session.getMapper(YourMapper.class);
mapper.xxx(...);
session.commit();
请注意:MyBatisMapperFactoryBuilder 是插件提供的一个 SqlSessionFactory 工厂构造器,这里我们需要用插件提供的MyBatisMapperFactoryBuilder替换mybatis原始的 SqlSessionFactoryBuilder 以启用插件相关Api功能。
配置注解
类型 | 注解 | 描述 |
---|---|---|
类注解 | @Table | 配置表名生成规则 |
@ResultMap | 自定义ResultMap,默认使用BaseResultMap |
|
@Excludes | 排除不需要的字段属性,一般用于子类排除父类某字段的场景 | |
@DefaultOrderBy | 默认排序字段,#pk 内置占位符,隐式地指向当前生效的主键 |
|
规则注解 | @NamingRule | 配置字段名和数据库列的转换规则 |
@ValueRule | 配置值的生成规则,类似:#{id, ...} |
|
@ExpressionRule | 配置表达式的生成规则,类似:id = #{id, ...} |
|
以上两种类型的注解都只能配置在类上,主要用于描述数据库实体Bean的一些基础信息,通常建议配置在父类上,进而避免大量重复代码的产生。 | ||
成员注解 | @Id | 主键标识,默认使用第一个标注字段,否则使用primary 为true的字段作为主键 |
@Index | 干扰字段的排列顺序,默认按照Bean定义的顺序从父类到子类排列 | |
@Column | 显式地配置字段和数据库列的规则说明 | |
@GeneratedKey | 主键生成策略,必须和@Id 组合使用,否则无效,且只能出现一次 |
|
@GeneratedValue | 生成常量值,插件提供 SnowFlakeId/UUID,可自行拓展 | |
@Conditional | 条件表达式,默认使用 = 桥接前后条件,可更改条件规则 |
|
@LogicallyDelete | 启用逻辑删除,只能出现一次 | |
@Version | 启用乐观锁,只能出现一次 | |
成员注解主要是对实体字段的一些描述 | ||
标识注解 | @EnableResultMap | 标注在Mapper方法上,是否使用ResultMap结果映射 |
@Reference | 标注在Mapper方法上,指向@XXXProvider(type)的其他非同名方法 | |
标识注解主要用于扩展插件Api的时候用的场景多一些,你也可以在自己的Mapper上使用默认的ResultMap,申明一下即可。 |
偏好配置
属性说明
属性 | 描述 | 类型 | 默认 |
---|---|---|---|
enableLogger | 是否启用日志 | Boolean | true |
enableMapperScanLog | 是否开启Mapper扫描日志 | Boolean | true |
enableRuntimeLog | 是否开启实时日志 | Boolean | true |
enableCompilationLog | 是否开启编译日志 | Boolean | true |
enableKeywordsToUppercase | 关键字大小写转换 | Boolean | false |
databaseColumnStyle | 数据库列样式 | String | # |
特别说明:#
是一个占位符,代表据数据库中的列名,比如mysql中默认列使用 `column`,那么你可以这样配置 `#`,默认是没有任何样式修饰符的。
-- 默认样式 - #
select id, name, age, weight from ... where ...
-- Mysql样式 - `#`
select `id`, `name`, `age`, `weight` from ... where ...
-- Oracle - [#]
select [id], [name], [age], [weight] from ... where ...
-- 自定义样式 - L-#-R
select L-id-R, L-name-R, L-age-R, L-weight-R from ... where ...
配置方式
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<properties resource="jdbc.properties">
<property name="enableLogger" value="true"/>
<property name="enableRuntimeLog" value="true"/>
<property name="enableCompilationLog" value="true"/>
<property name="enableMapperScanLog" value="true"/>
<property name="enableKeywordsToUppercase" value="true"/>
<property name="databaseColumnStyle" value="`#`"/>
</properties>
</configuration>
支持模板语法
以下是目前已经支持的模板语法,你可以在自定义 DynamicProvider
中无条件的使用它们,也可以开启XML模板语法支持并使用它。
@{expression}
- 静态模板,会在程序启动过程中被解析成完整的文本。%{expression}
- 动态模板,会在SQL执行过程被解析成完整的文本,类似mybatis判断条件。[kewords]
- 关键字模板,会根据配置自动转换成大写或小写关键字。{{value.expression}}
- 取值表达式,可以获取执行方法的传入参数或程序上下文数据。<error>message</error>
- 错误信息,用于隐藏错误信息,不影响程序启动,但会在执行过程中抛出。
关于this关键字
可能你会在模板看到 this
字样的关键字,这个关键字默认指向当前Mapper对应的数据库Bean的 解析对象,如果你想在模板语法中使用其他Bean解析对象的话,请使用Mybatis提供的 类对象别名 进行调用,这将会类似于 @{this.table} 或者 @{user.table}/@{role.table}/@{xxx.table} 等。
this = EntityParser.getEntity(Bean.class);
特别说明
首先,插件分两部分,默认是不支持XML模板语法解析的,所以可以根据个人喜好,选择开启或者不使用它,插件的运行和XML没太大关系,基础功能是基于Mybatis内部的 @SelectProvider
、@UpdateProvider
、@InsertProvider
、@DeleteProvider
注解提供运行所需的 SqlSource
,所以即使无任何XML文件也可正常工作,因为都是对单表的扩展Api,所以如果涉及一些任何复杂SQL,请添加XML文件并书写自己的SQL逻辑脚本。
开启XML模板语法
如果你需要在XML也使用这些模板语法,请配置解析XML的扩展 LanguageDriver
,插件提供的扩展Driver是 MyBatisMapperLanguageDriver
,Mybatis的LanguageDriver接口无任何限制,任何人都可以对其进行自定义更改,属于Mybatis的一种扩展机制,具体使用的话,可以在单个XML语句节点上配置 lang
属性来开启单个Statement支持,也可以覆盖Mybatis的默认 XMLLanguageDriver
解析驱动。
1、单个语句块,使用lang属性即可
<select id="xxx" lang="com.viiyue.plugins.mybatis.MyBatisMapperLanguageDriver">
[select] @{this.columns}
[from] @{this.table}
[where] @{this.column.yourFieldName} = #{yourFieldName}
</select>
2、全局范围,需要覆盖mybatis默认语言驱动
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<settings>
<setting name="defaultScriptingLanguage" value="com.viiyue.plugins.mybatis.MyBatisMapperLanguageDriver"/>
</settings>
</configuration>
3、DynamicProvider无条件支持任何模板语法
public final class YourCustomProvider extends DynamicProvider {
// 实现一
// 方法名必须和你接口中定义的方法名一致
public String selectAll( MappedStatement ms ) {
return "[select] @{this.columns} [from] @{this.table}";
}
// 实现二
// 两个参数,第二个为当前对应的Mapper数据库实体Class类型
public String selectAll( MappedStatement ms, Class<?> yourModelBeanType ) {
System.out.println(yourModelBeanType);
return "[select] @{this.columns} [from] @{this.table}";
}
}
接下来对每一种模板语法作一些详细说明,方便大家能更好的理解并熟练的使用它们。现在假设当前Mapper是下面这样的,那么 this
关键字会指向 User的解析对象,将从其中获取各种元数据信息。
// DO
// 省略各种注解配置
public class User {
private Long id;
private String name;
private Integer age;
}
// Mapper
public interface UserMapper extends Mapper<User, UserDTO, Long> {
}
// DO → User
// DTO → UserDTO
// PK → Long
// this → EntityParser.getEntity(User.class);
@{expression} - 静态模板
-- 关于表
-- @see com.viiyue.plugins.mybatis.template.builder.TableBuilder
@{this.table} → t_user
@{this.table.prefix("xxx")} → xxx.t_user
@{this.table.alias("t")} → t_user as 't'
@{this.table.prefix("xxx").alias("t")} → xxx.t_user as 't'
-- 关于单列
-- @see com.viiyue.plugins.mybatis.template.builder.ColumnBuilder
@{this.column.yourFieldName} → your_field_name
@{this.column.yourFieldName.prefix("a")} → a.your_field_name
@{this.column.yourFieldName.suffix("_suffix")} → your_field_name_suffix
@{this.column.yourFieldName.javaType} → Integer/Long/YourFieldType
@{this.column.yourFieldName.jdbcType} → INT/BIGINT/OTHERS
@{this.column.yourFieldName.property} → yourFieldName
@{this.column.yourFieldName.prefix("a").suffix("_suffix")} → a.your_field_name_suffix
-- 关于所有列
-- @see com.viiyue.plugins.mybatis.template.builder.ColumnsBuilder
@{this.columns} → id, name, age
@{this.columns.include("id,name")} → id, name
@{this.columns.exclude("id,age")} → name
@{this.columns.prefix("t")} → t.id, t.name, t.age
@{this.columns.prefix("t").include("id,name").exclude("id")} → t.name
-- 关于值
-- @see com.viiyue.plugins.mybatis.template.builder.ValuesBuilder
@{this.values} → #{id}, #{name}, #{age}
@{this.values.alias("user")} → #{user.id}, #{user.name}, #{user.age}
@{this.values.include("id,name")} → #{id}, #{name}
@{this.values.exclude("id,age")} → #{name}
@{this.values.alias("user").include("name,age")} → #{user.name}, #{user.age}
-- 关于默认排序(需要在类上配置@DefaultOrderBy)
-- 没有配置相关注解的话,表达式返回一段空白文本("")
-- @see com.viiyue.plugins.mybatis.template.builder.LogicallyDeleteBuilder
@{this.defaultOrderBy} → order by sorter desc
@{this.defaultOrderBy.desc} → order by sorter desc
@{this.defaultOrderBy.asc} → order by sorter asc
@{this.defaultOrderBy.prefix("t")} → order by t.sorter desc
-- 关于逻辑删除(需要在字段上配置@LogicallyDelete)
-- 没有配置相关注解的话,表达式返回一段空白文本("")
-- @see com.viiyue.plugins.mybatis.template.builder.LogicallyDeleteBuilder
@{this.tryLogicallyDelete} → deleted = 'N'
@{this.tryLogicallyDelete.prefix("t")} → t.deleted = 'N'
@{this.tryLogicallyDelete.useWhereQuery} → where deleted = 'N'
@{this.tryLogicallyDelete.useAndQuery} → and deleted = 'N'
@{this.tryLogicallyDelete.useOrQuery} → or deleted = 'N'
@{this.tryLogicallyDelete.useQueryValue} → or deleted = 'Y'
@{this.tryLogicallyDelete.useDeletedValue} → or deleted = 'N'
-- 关于乐观锁(需要在类上配置@Version)
-- 没有配置相关注解的话,表达式返回一段空白文本("")
-- @see com.viiyue.plugins.mybatis.template.builder.OptimisticLockBuilder
@{this.tryOptimisticLock} → version = #{version}
@{this.tryOptimisticLock.prefix("t")} → t.version = #{version}
@{this.tryOptimisticLock.useWhere} → where version = #{version}
@{this.tryOptimisticLock.useAnd} → and version = #{version}
@{this.tryOptimisticLock.useOr} → or version = #{version}
%{expression} - 动态模板
-- 关于列
-- $ 是当前传入的参数,如果单个参数,可以直接写 $ 符号,多参数使用 $.param 的形式。
-- 会筛选出Bean中所有non-null的属性,值得注意的是 @Column(nullable=true/false)
-- @see com.viiyue.plugins.mybatis.template.builder.ColumnBuilder
%{this.columns.dynamic($)} → id, name
%{this.columns.dynamic($).include("id,name")} → id, name
%{this.columns.dynamic($).exclude("id,age")} → name
%{this.columns.dynamic($).prefix("t")} → t.id, t.name, t.age
%{this.columns.dynamic($).prefix("t").include("id,name").exclude("id")} → t.name
-- 关于修改
-- 注意:如果开启了乐观锁的话,版本字段的取值不再和字段名一样,类似:next(?)Value
-- @see com.viiyue.plugins.mybatis.template.builder.UpdateSetBuilder
%{this.set} → id = #{id}, name = #{name}, version = #{nextVersionValue}
%{this.set.prefix("t")} → t.id = #{id}, t.name = #{name}, t.age = #{age}
%{this.set.alias("user")} → id = #{user.id}, name = #{user.name}, age = #{user.age}
%{this.set.include("id,name")} → id = #{id}, name = #{name}
%{this.set.exclude("id,name")} → age = #{age}
%{this.set.dynamic($)} → name = #{name}
%{this.set.dynamic($.user).prefix("t").alias("user")} → t.name = #{user.name}
-- 关于动态值
-- 构造器会自动筛选出所有non-null的属性,这里只是一个示例效果。
-- @see com.viiyue.plugins.mybatis.template.builder.ValuesBuilder
%{this.values.dynamic($)} → #{id}, #{name}, #{age}
%{this.values.dynamic($).alias("user")} → #{user.id}, #{user.name}, #{user.age}
%{this.values.dynamic($).include("id,name")} → #{id}, #{name}
%{this.values.dynamic($).exclude("id,age")} → #{name}
%{this.values.dynamic($).alias("u").include("name,age")} → #{u.name}, #{u.age}
-- 关于Where条件
-- 构造器会自动筛选出所有non-null的属性,这里只是一个示例效果。
-- @see com.viiyue.plugins.mybatis.template.builder.WhereBuilder
%{this.where($)} → where name = #{name} and age = #{age}
%{this.where($).prefix("t")} → where t.name = #{name} and t.age = #{age}
%{this.where($).include("name")} → where name = #{name}
%{this.where($).exclude("name")} → where age = #{age}
%{this.where($).tryLogicallyDeleteQuery} → where deleted = 'Y' and name = #{name}
%{this.where($).tryLogicallyDeleteQuery.useQueryValue} → where deleted = 'Y' and name = #{name}
%{this.where($).tryLogicallyDeleteQuery.useDeletedValue} → where deleted = 'N' and name = #{name}
[keywords] - 关键字转换
需要说明一下,如果不做偏好配置的话,插件默认全部转换为小写关键字,如果想使用大写关键字,请更改相关配置。
[select] * [from] t_user [where] name = #{name}
-- enableKeywordsToUppercase = true
SELECT * FROM t_user WHERE name = #{name}
-- enableKeywordsToUppercase = false / default
select * from t_user where name = #{name}
{{expression}} - 取值表达式
这种表达式主要是 Example 查询的时候用的相对多一些,其他情况你要使用也是可以的。此表达式不输出任何修饰符,大家使用的话记得添加修饰符,比如:引号。
-- Example
select {{$.example.columns}} from @{this.table} {{$.example.where}}
-- 获取系统值,表达式具体的值在运行时生效
-- 目前{{system.xxx}}仅支持下面四个属性
update @{this.table} set modify_time = {{system.now}} where ... -- Date
update @{this.table} set millis = {{system.systime}} where ... -- CurrentTimeMillis
update @{this.table} set order_code = '{{system.uuid}}' where ... -- UUID
update @{this.table} set sort_value = {{system.rundom.nexInt(5)}} where ... -- Number
-- 获取环境变量值,表达式具体的值在运行时生效
-- 更多属性调用请参照以下常量类的ENV_PROP_NAMES属性值
-- @see com.viiyue.plugins.mybatis.Constants#ENV_PROP_NAMES
insert into @{this.table} (name, text) values ('{{env.osName}', '{{env.osVersion}}')
<error>message</error> - 异常表达式
这种情况不要轻易写在SQL脚本中,如果你写在了自己的SQL脚本中,执行方法时会把标签内部的文本信息以 RuntimeException
的形式抛出来,目前也只是插件内部用来判断一些特殊情况时才会使用到。
支持SQL注释
众所周知,XML文件中的SQL脚本是不支持注释的,但是我们可以帮你实现在XML添加脚本注释,你可以直接从DB软件中整个复制过来,而不需要单独剔除你添加的注释,不要惊慌,多余的注释会在你程序启动过程中被移除掉,不会有任何效率上的影响,所以放心拥抱SQL注释吧。
<select id="xxx" resultMap="xxx">
// 单行注释
// 支持大小写关键字转换,凡是包裹在“[]”中的任何文本,都会被转换成全大写或全小写文本。
[select]
-- SQL注释
-- 输出表的所有列
-- 注意:注释不支持#注释,#会和mybatis的取值表达式冲突,所以不要使用#注释
@{this.columns}
[from]
/* 单行文本注释 */
/* 输出表名 */
@{this.table} /* --- [where] --- */
/**
* 多行文本注释多行文本注释
* 多行文本注释多行文本注释
* 多行文本注释多行文本注释
* 多行文本注释多行文本注释
*/
// 所有程序通用的单行注释也是支持的
// 内容可以直接写表达式,也可以结合mybatis的逻辑标签,没有任何限制。
// @{this.column.loginName} = #{loginName}
// 可以结合各种条件标签使用模板语法
<trim prefix="[where]" prefixOverrides="and|AND">
<if test="loginName != null">[and] @{this.column.xxx} = #{xxx}</if>
</trim>
<!-- 不影响原有XML注释 -->
// 你也可以加入逻辑删除表达式
@{this.tryLogicallyDelete.useAndQuery}
// 或者使用乐观锁表达式
@{this.tryOptimisticLock.useAndQuery}
</select>
支持各种主键生成策略
目前插件主要支持以下四种主键生成策略,使用 @Id
标识主键,然后使用 @GeneratedKey
对主键生成策略进行配置。
// 省略各种注解配置
public class YouBean {
@Id
// 1、使用JDBC的自增主键获取方式
@GeneratedKey( useGeneratedKeys = true )
// 2、直接生成主键值,插件提供两种默认主键值生成器(SnowFlakeId/UUID)
// @GeneratedKey( valueProvider = SnowFlakeIdValueProvider.class )
// @GeneratedKey( valueProvider = UUIDValuePrivoder.class )
// 3、对于不支持JDBC获取自增主键值的数据库来说,可以像下面这样使用:
// 具体参照com.viiyue.plugins.mybatis.enums.AutoIncrement里面的枚举值,
// 里面预置了部分获取自增主键的SQL,可以直接写枚举名字,没有的话也可以自己写SQL脚本。
// @GeneratedKey( statement = "MYSQL" ) // MYSQL是枚举名,通过枚举找到对应SQL脚本
// @GeneratedKey( statement = "SELECT T_USER.YYYY()" ) // 直接写SQL脚本
// 4、自定义自增主键SQL提供者
// 如果枚举里面没有你需要的,可以通过statementProvider来提供你自己的SQL主键查询
// @GeneratedKey( statementProvider = OracleAutoIncrementStatementProvider.class )
private Long id;
}
// 自定义自增主键SQL脚本
public class OracleAutoIncrementStatementProvider implements AutoIncrementStatementProvider {
@Override
public String getAutoIncrementSqlStatement( GeneratedKeyInfo info ) {
return "SELECT " + info.getTableName() + ".NEXTVAL FROM DUAL";
}
}
支持多主键场景
Bean的定义
可以对多个主键字段进行 @Id
标注,没有特殊指明的话,默认会使用对象中第一个标注的主键,否则将会使用 @Id(primary=true)
的那一个主键。
// 省略各种注解配置
public class YouBean {
@Id
private Long id;
@Id(primary = true)
private Long id2;
}
使用上的区别
mapper.selectByPrimaryKey(PK); // 使用默认主键
mapper.selectByPrimaryKeyIndex(Index, PK); // 使用指定下标的主键,多主键顺序由实体Bean决定
mapper.selectByPrimaryKeyGroup(Pk...); // 使用默认主键
mapper.selectByPrimaryKeyGroupIndex(Index, PK...); // 使用指定下标的主键,多主键顺序由实体Bean决定
更方便快捷的条件查询
Example example = null;
// query
// 方法后面可以直接跟各种Where条件
example = Example.query(User.class).equal("id", 1L).lt("age", 60).xxx(...);
// select
// 该方法可以对列进行操作,条件筛选的话使用when()来桥接
example = Example.select(User.class).includes( "id", "loginName", "password" );
example.when().equal("id", 1L).lt("age", 60).xxx(...);
// update
// 使用set/values可以对单个字段进行设值,不支持乐观锁
example = Example.update(User.class).set(xx, yy, zz).values(XX, YY, ZZ);
example.when().equal("id", 1L).lt("age", 60).xxx(...);
// update
// 还可以绑定某个实体对象来修改
User user = null;
Example.update(User.class).bind(user).when().equal("id", 1L).xxx(...);
// 具体使用
mapper.selectByExample(example);
mapper.updateByExample(example);
mapper.deleteByExample(example);
支持聚合函数统计
Example example = null;
// 支持多字段统计,* 仅能在count函数中使用,其他函数使用会出现异常
example = Example.count(User.class, "*", ...); // 统计行数
example = Example.summation(User.class, "price", "num", ...); // 求和
example = Example.maximum(User.class, "price", "num", ...); // 求最大值
example = Example.minimum(User.class, "price", "num", ...); // 求最小值
example = Example.average(User.class, "price", "num", ...); // 求平均值
// 条件筛选
example.when().equal("id", 1L).lt("age", 60).xxx();
// 统计单个值,为了兼容不同的数据类型,统一使用BigDecimal接口,大家可自行转换成需要的数据类型
// 这种情况只能统计单列,如果统计多个字段,会出现SQL异常,此时请使用下面这种方式
BigDecimal result = mapper.selectStatisticByAggregateFunction(example);
// 统计多列,返回的是对应的实体对象,统计的字段值会自动封装到对象同名字段中
List<DTO> results = mapper.selectStatisticListByAggregateFunction(example);
支持数据库乐观锁
只需要在你的字段上标注 @Version
即可,乐观锁注解只能出现一次,默认为版本自增实现,数据类型支持 Short
、Integer
、Long
、Timestamp
,还可以选择雪花版本值,甚至你可以自己实现版本值的获取,实现 NextVersionProvider
接口即可。
// 省略各种注解配置
public class User {
@Version
// 默认:DefaultNextVersionProvider.class
// @Version(nextVersion = SnowFlakeIdNextVersionProvider.class)
private Long version;
}
支持各种逻辑删除
逻辑删除需要配合 @LogicallyDelete
注解一起使用,如无任何注解配置,方法执行将会抛出异常,只可配置一次。
// 逻辑删除所有
mapper.logicallyDeleteAll();
// 根据特定条件逻辑删除部分
mapper.logicallyDelete(Object);
// 通过主键逻辑删除指定数据
mapper.logicallyDeleteByPrimaryKey(PK);
// 通过主键数组逻辑删除特定数据
mapper.logicallyDeleteByPrimaryKeyGroup(Pk...);
// 多主键情况下,通过主键下标和主键值逻辑删除特定数据
mapper.logicallyDeleteByPrimaryKeyIndex(Integer, Pk);
// 多主键情况下,通过主键下标和主键数组逻辑删除特定数据
mapper.logicallyDeleteByPrimaryKeyIndexGroup(Index, Pk...);
// 通过自定义条件筛选逻辑删除特定数据
mapper.logicallyDeleteByExample(example);
支持逻辑删除数据的恢复
数据恢复也需要配合 @LogicallyDelete
注解一起使用,如无任何注解配置,方法执行将会抛出异常。
// 查询所有被逻辑删除过的数据
mapper.selectAllDeleted();
// 根据特定条件还原指定数据
mapper.restore(Object);
// 还原所有被逻辑删除过的数据
mapper.restoreAllDeleted();
// 通过主键还原指定Id的数据
mapper.restoreByPrimaryKey(Pk);
// 通过主键数组批量还原数据
mapper.restoreByPrimaryKeyGroup(PK...);
// 多主键的情况,通过指定主键下标和主键值还原特定数据
mapper.restoreByPrimaryKeyIndex(Integer, Pk);
// 多主键的情况,通过指定主键下标和主键数组还原特定数据
mapper.restoreByPrimaryKeyIndexGroup(Index, Pk...);
// 通过自定义条件筛选还原特定数据
mapper.restoreByExample(example);
支持查询自定义返回Bean类型
Mapper接口提供三个泛型参数,依次为<数据库实体类型,返回数据类型,主键类型>。对于返回数据类型你可以任意定义,但是 ResultMap 结果映射只会生成和 数据库实体 同名的匹配字段,其他不匹配的字段值将一直为 null
。
public class YourMapper extends Mapper<User, UserDTO, Long> {
// 那么你的返回数据类型就是:UserDTO
}
支持零MapperXML配置文件
因为插件通过Mybatis的注解 @SelectProvider
、@UpdateProvider
、@InsertProvider
、@DeleteProvider
来提供基础 SqlSource
,所以即使你没有配置任何XML也是可以正常工作的。
扩展插件Api
Mybatis提供的注解SQL功能本身就是一种扩展机制,所以扩展就很好理解了,你可以写自己的@xxxProvider,也可以在插件的基础上实现 DynamicProvider
进而扩展插件的Api,这样的话你就可以在通用的Mapper上调用你自己的Api了。
接口定义,需要继承Marker
public interface Mapper<DO, DTO, PK extends Serializable> extends Marker<DO, DTO, PK> {
@SelectProvider( type = YourProvider.class, method = DynamicProvider.dynamicSQL )
List<DTO> selectCustomApi( @Param("param1") String param1, @Param("param2") String param2 );
}
具体实现,需要继承DynamicProvider
public final class YourProvider extends DynamicProvider {
public String selectCustomApi( MappedStatement ms ) {
return "[select] @{this.columns} [from] @{this.table} [where] @{this.column.name} = #{param1} [and] @{this.column.xxx} = #{param2}";
}
}
提供各种场景的日志打印
编译日志,需要开启 enableCompilationLog 配置
... --------------------------------------------------------------------------------
... ----- Target: YourMapper( BaseSelectProvider )
... -- Namespace: xxx.xxx.xxx.YourMapper.selectAll
... Template SQL: [select] @{this.columns} [from] @{this.table} @{this.tryLogicallyDelete.useWhereQuery} @{this.defaultOrderBy}
... Compiled SQL: SELECT `id`, `name`, `version` FROM `t_user` WHERE deleted = 'N' ORDER BY `id` DESC
... ------- Time: 1ms
... --------------------------------------------------------------------------------
实时动态日志,需要开启 enableRuntimeLog 配置
... --------------------------------------------------------------------------------
... ==> Compile runtime SQL ...
... --------------------------------------------------------------------------------
... ==> - Template: SELECT `id`, `name`, `version` FROM `t_user` %{this.where($).tryLogicallyDeleteQuery} ORDER BY `id` DESC
... ==> - Compiled: SELECT `id`, `name`, `version` FROM `t_user` WHERE deleted = 'N' AND `name` = #{name} ORDER BY `id` DESC
... ==> Parameters: xxx.xxx.xxx.User@145eaa29
... <== ----- Time: 7ms
... --------------------------------------------------------------------------------
... ==> Preparing: SELECT `id`, `name`, `version` FROM `t_user` WHERE delete = 'N' AND `name` = ? ORDER BY `id` DESC
... ==> Parameters: xxx(String)
... <== Total: 3
... --------------------------------------------------------------------------------