Mybatis增强组件
组件编码
hzero-starter-mybatis-mapper
一、简介
1.1 概述
增强ORM框架Mybatis的数据库DML处理能力,支持分页、数据多语言、基于对象的SQL编写,数据防篡改等功能。
1.2 组件坐标
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-boot-starter-mybatis-mapper</artifactId>
<version>${hzero.starter.version}</version>
</dependency>
1.3 特性
- 基于猪齿鱼choerodon-starter-mybatis-mapper组件拓展
- 支持复杂条件查询
- 扩展多语言支持
- 添加数据防篡改功能
- 添加数据加密存储功能
- 添加唯一校验功能
二、组件功能
2.1 CRUD支持
2.1.1 新增支持
int insert(T record)
: 插入一条记录int insertSelective(T record)
: 插入一条记录,Bean中null的字段不会被插入int insertOptional(T record)
: 插入一条记录,指定插入的列,插入前调用io.choerodon.mybatis.helper.OptionalHelper#optional(java.util.List<java.lang.String>)
方法int insertList(List<T> recordList)
: 批量插入,如果主键名称不叫id
,需要再mapper中重新覆写该方法,在注解中声明主键的名称。
@Options(useGeneratedKeys = true, keyProperty = "主键名称")
@InsertProvider(type = SpecialProvider.class, method = "dynamicSql")
int insertList(List<T> recordList);
2.1.2 更新支持
int updateByPrimaryKey(T record)
: 根据主键更新实体全部字段,null值会被更新int updateByPrimaryKeySelective(T record)
: 根据主键更新属性不为null的值int updateOptional(T record)
: 更新一条记录,指定更新的列,更新前调用io.choerodon.mybatis.helper.OptionalHelper#optional(java.util.List<java.lang.String>)
方法
2.1.3 删除支持
int delete(T record)
: 根据实体属性作为条件进行删除,查询条件使用等号int deleteByPrimaryKey(Object key)
: 根据主键字段进行删除,方法参数必须包含完整的主键属性
2.1.4 查询支持
List<T> select(T record)
: 根据实体中的属性值进行查询,查询条件使用等号List<T> selectAll()
: 查询全表结果,慎用T selectByPrimaryKey(Object key)
: 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号List<T> selectByIds(String ids)
: 根据主键字符串进行查询,类中只有存在一个带有@Id
注解的字段,多个主键使用,
分割int selectCount(T record)
: 根据实体中的属性查询总数,查询条件使用等号T selectOne(T record)
: 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号List<T> selectByCondition(Object condition)
: 根据Condition条件进行查询int selectCountByCondition(Object condition)
: 根据Condition条件进行查询总数
mapper.selectByCondition(
org.hzero.mybatis.domian.Condition.builder(Entity.class)
.andWhere(
org.hzero.mybatis.util.Sqls.custom()
.andEqualTo(FIELD1, VALUE1)
.andLike(FIELD2, VALUE2)
).build()
);
List<T> selectOptional(T condition, Criteria criteria)
:复杂查询,condition
参数是查询条件,criteria
可以指定查询的列以及一些其他的查询参数,以及排序等。该方法支持多表关联查询,使用关联查询时需要在与其他表的关联字段上添加@org.hzero.mybatis.common.query.JoinTable
注解,该注解指定关联的表以及关联方式和关联字段,然后需要在Entity
中新建关联表中的字段,并且添加@org.hzero.mybatis.common.query.JoinColumn
注解,如果字段作为查询条件,必须添加@Where
注解。
// Entity
@Table(name = "hmsg_user_receive_config")
class UserReceiveConfig extends AuditDomain {
// other field ...
@Where
private Long userId;
@ApiModelProperty(value = "hmsg_receive_config .receiver_code", required = true)
@JoinTable(name = "receiveConfigJoin", target = ReceiveConfig.class, on = @JoinOn(joinField = ReceiveConfig.FIELD_RECEIVE_CODE))
private String receiveCode;
@Transient
@JoinColumn(joinName = "receiveConfigJoin", field = ReceiveConfig.FIELD_DEFAULT_RECEIVE_TYPE)
private String defaultReceiveType;
// getter and setter ...
}
// Entity
@Table(name = "hmsg_receive_config")
class ReceiveConfig extends AuditDomain {
// other field ...
private String receiveCode;
private String defaultReceiveType;
// getter and setter ...
}
// Repository
userReceiveConfigRepository
.selectOptional(new UserReceiveConfig().setUserId(1L),
new Criteria().select("userReceiveId", "receiveCode", "receiveType", "defaultReceiveType", "userId"));
-- Result SQL
SELECT
A.object_version_number,
A.user_receive_id,
A.receive_code,
A.receive_type,
B.default_receive_type AS default_receive_type,
A.user_id
FROM
hmsg_user_receive_config A
INNER JOIN hmsg_receive_config B
ON A.receive_code = B.receive_code
WHERE
(A.user_id = ?)
2.2 多语言支持
2.2.1 功能说明
- 在业务处理中,经常会有有一些数据需要做多语言支持,根据用户选择的语言来动态切换显示内容。使用多语言组件时,提供的查询方法会自动join多语言表,不必开发人员再去手写SQL,自己创建的mapper接口需要自行join多语言表。
2.2.2 使用说明
- 创建表时创建对应的多语言表,多语言表的表明需要在原表明的基础上增加
_tl
,所以在设计多语言表的时候主表表名长度不要超过23,以免多语言表字段长度超出限制,多语言表中需要包含对应表的主键,以及需要多语言的列,再加上lang varchar(30)
字段。 - 在数据库对应的多语言java实体类上继承
io.choerodon.mybatis.domain.AuditDomain
类,添加@io.choerodon.mybatis.annotation.MultiLanguage
注解,在对应的多语言列上添加@io.choerodon.mybatis.annotation.MultiLanguageField
注解 - 新增/更新数据时,实体类json中需要添加多语言map,结构示例:
{
// other field ...
_tls:{
roleName : {
zh_CN : '管理员',
en_GB : 'Admin'
},
description : {
zh_CN : '管理员',
en_GB : 'administrator'
}
}
}
- 如果因为一些特殊需求,需要在新增或者删除时关闭多语言支持,可以调用
org.hzero.mybatis.helper.MultiLanguageHelper#close
方法临时关闭多语言支持,在一次mybatis操作之后,恢复启用状态,只在当前线程内生效。 - 自行创建的mapper方法,在关联多语言时,可以在xml中添加
<bind name="lang" value="@io.choerodon.mybatis.helper.LanguageHelper@language()"/>
标签来获取当前用户的语言。
<select id="selectEntity" resultType="c.x.Entity">
<bind name="lang" value="@io.choerodon.mybatis.helper.LanguageHelper@language()"/>
SELECT
t.id,
ttl.multi_lang
-- other column ..
FROM table1 t
JOIN table1_tl ttl ON t.id = ttl.id AND ttl.lang = #{lang}
WHERE
-- condition
</select>
2.3 数据防篡改
2.3.1 功能说明
- 数据从后端传输到前端之后,在进行更新操作时,经常需要对主键字段做校验,防止恶意篡改主键导致后端数据被破坏,数据防篡改功能将数据主键进行加密,进行数据更新时,对加密信息做校验用来验证主键信息有没有被篡改。多语言支持也是基于数据防篡改实现的。
2.3.2 使用说明
- 如果是和数据库对应的实体类,只需要继承
io.choerodon.mybatis.domain.AuditDomain
即可,如果是自定义的VO/DTO
,也可以继承AuditDomain
类,或者实现org.hzero.mybatis.domian.SecurityToken
接口,接口中的set_token
方法用于往VO/DTO
中保存加密信息,get_token
方法用于获取VO/DTO
中保存的加密信息,associateEntityClass
方法用于将VO/DTO
与数据库对应的实体类关联在一起,需要注意在VO/DTO
中必须和实体类主键属性 - 在更新数据前调用
org.hzero.mybatis.helper.SecurityTokenHelper#validToken(..)
方法校验主键有没有被篡改。
class Entity extends io.choerodon.mybatis.domain.AuditDomain {
@Id
private long id;
// other field ...
// getter and setter ...
}
class EntityDTO implements org.hzero.mybatis.domian.SecurityToken {
private long id;
// other field ...
// getter and setter ...
private String _token;
@Override
public void set_token(String _token) {
this._token = _token;
}
@Override
public String get_token() {
return this._token;
}
@Override
public Class<? extends SecurityToken> associateEntityClass() {
return Entity.class;
}
}
// controller
// GET
public ResponseEntity<List<EntityDTO>> selectEntity(...){
List<EntityDTO> list = // select ...
return Results.success(list)
}
// PUT
public ResponseEntity<Entity> updateEntity(Entity entity){
org.hzero.mybatis.helper.SecurityTokenHelper.validToken(entity);
// update ...
}
2.4 数据加密存储
2.4.1 功能说明
- 有一些保密新比较强的信息需要加密之后保存到数据库,例如配置的用户的邮箱密码,某些其他服务的密钥等,在数据库做加密存储主要是防止数据库信息被盗取导致用户信息泄露。
2.4.2 使用说明
- 在数据库对应的实体类只需要加密的字段上添加
@org.hzero.mybatis.annotation.DataSecurity
注解。 - 在新增/更新数据之前,调用
org.hzero.mybatis.helper.DataSecurityHelper#open
方法开启数据加密 - 在查询数据之前,调用
org.hzero.mybatis.helper.DataSecurityHelper#open
方法开
- 加密使用AES加密
2.5 租户条件过滤
2.5.1 功能说明
- 为了防止Saas模式下的租户功能越权(查询到不属于自己租户的数据),在没有租户参数进行数据过滤控制的情况下,增加了后端通用过滤规则。
2.5.2 使用说明
1.注解模式,此模式针对使用平台封装好的查询方法生效。
1.1 在Controller类方法上添加注解@org.hzero.mybatis.annotation.TenantLimitedRequest
注解,默认SQL拼装为IN
模式,如:
WHERE 1 = 1
AND tenant_id IN (可访问租户ID列表)
1.2 设置注解属性TenantLimitedRequest(equal=true)
,SQL拼装为=
模式,如:
WHERE 1 = 1
AND tenant_id = (当前租户ID)
- 针对自定义Mapper的SQL语句,注解方式不支持自动改写,需要在
Mapper
中使用bind的方式进行引用
2.1 获取可访问租户列表函数,示例:
<bind name="__tenantIds" value="@org.hzero.mybatis.helper.TenantLimitedHelper@tenantIds()" />
引入后,在使用到的地方进行使用,如:
<if test="__tenantIds != null and !__tenantIds.isEmpty()">
and tenant_id in
<foreach colletion="__tenantIds" item="__tenantId" separator="," open="(" close=")">
#{__tenantId}
</foreach>
</if>
2.2 获取当前租户函数,示例:
<bind name="__tenantId" value="@org.hzero.mybatis.helper.TenantLimitedHelper@tenantId()" />
引入后,在使用到的地方进行使用,如:
<if test="__tenantId != null">
and tenant_id = #{__tenantId}
</if>
2.6 数据唯一校验
2.6.1 功能说明
- 在开发过程中,经常需要到传到后端的数据在数据库中做唯一校验,该功能是对该需求的封装,旨在简化开发过程中重复的工作,降低开发量。
2.6.2 使用说明
- 声明唯一校验字段:在
Entity
需要校验唯一的字段上添加注解@org.hzero.mybatis.annotation.Unique
- 调用校验方法:
org.hzero.mybatis.helper.UniqueHelper#valid(T)
方法,该方法返回一个布尔值,如果返回true
表示校验通过,返回false
表示数据已存在。
Assert.isTrue(UniqueHelper.valid(bank), BaseConstants.ErrorCode.DATA_EXISTS);
2.7 自定义主键策略
2.6.1 雪花ID
雪花ID是Twitter推出的分布式全局唯一ID的一套解决方案,相比于UUID,雪花ID有以下优点:
- 按照时间有序生成
- 长度最长为19位的数字(Long)
- 效率较高,在整个分布式系统内不会产生碰撞
雪花ID结构:
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
- 第
1
位:符号位。由于ID一般采用整数,所以首位一般固定0
; - 后
41
位:时间戳位。雪花ID需要有一个固定的起始时间,这里记录的是毫秒级的当前时间戳差值,最多可以使用越 69 年,(1L « 41) / (1000L * 60 * 60 * 24 * 365) = 69; - 后
10
位:机器标识位:可以部署1024
个节点,包含5
位(32
个)数据中心ID(dataCenterId)和5
位(32
个)工作机器ID(workerId); - 后
12
位:序列位:毫秒内的计算。12
位的计数顺序号支持每个节点,每毫秒生成4096
个序号(也就是理论上每个分布式节点在1
毫秒内可以生成4096
个ID); - 总
64
位:合并组成一个Long
数字。
配置属性:
mybatis:
configuration:
key-generator: snowflake
snowflake:
start-timestamp: 1577808000000
meta-provider: redis
meta-provider-redis-db: 1
meta-provider-redis-refresh-interval: 540000
meta-provider-redis-expire: 600000
data-center-id: 1
worker-id: 1
key-generator
:主键生成策略,snowflake
表示使用雪花IDstart-timestamp
:雪花ID起始时间,默认1577808000000
,也就是2020/01/01 00:00:00
meta-provider
:雪花ID元数据生成支持,自动生成dataCenterId
和workerId
,默认redis
,可选值:none
:简易模式,dataCenterId
使用当前服务名的hash
值的绝对值然后和32
求余,workerId
取[0-32)
之间的随机整数,节点之间可能会重复redis
:Redis模式,自动注册dataCenterId
和workerId
,支持最多32
个服务,每个服务32
个实例,超出限制会注册报错,节点之间不会重复
meta-provider-redis-db
:仅在Redis模式下生效,默认1
,Redis模式自动注册的DBmeta-provider-redis-refresh-interval
:仅在Redis模式下生效,默认540000
,也就是9
分钟,Redis模式自动注册刷新间隔meta-provider-redis-expire
:仅在Redis模式下生效,默认600000
,也就是10
分钟,Redis模式自动注册的失效时间,请保证过期时间大于刷新间隔,否则设置无效data-center-id
:数据中心ID,可以手工指定[0-31)
,如果手工指定了数据中心ID,provider
就会使用手工指定的值worker-id
:工作机器ID,可以指定[0-31)
,如果手工指定了工作机器ID,provider
就会使用手工指定的值
**注意:**一般来说只需要指定key-generator: snowflake
就可以了,其他的配置都有默认值,按需设置
三、版本更新日志
0.8.0.RELEASE [2019-03-29]
- 添加复杂查询
selectOptional