MyBatis-Flex一个高雅的 MyBatis 增强结构。

更轻量

MyBatis-Flex 除了 MyBatis 本身,再无任何第三方依靠,因而会带来更高的自主性、把控性和稳定性。在任何一个体系中,依靠越多,稳定性越差。

更灵敏

MyBatis-Flex 供给了十分灵敏的 QueryWrapper,支撑相关查询、多表查询、多主键、逻辑删去、达观锁更新、数据填充、数据脱敏、等等….

更高的功用

MyBatis-Flex 经过共同的架构,没有任何 MyBatis 拦截器、在 SQL 履行的过程中,没有任何的 SQL Parse,因而会带来指数级的功用增长。

官网供给的和同类结构的功用对比

功用或特点 MyBatis-Flex MyBatis-Plus Fluent-MyBatis
对 entity 的根本增修改查
分页查询
分页查询之总量缓存
分页查询无 SQL 解析规划(更轻量,及更高功用)
多表查询: from 多张表
多表查询: left join、inner join 等等
多表查询: union,union all
单主键装备
多种 id 生成策略
支撑多主键、复合主键
字段的 typeHandler 装备
除了 MyBatis,无其他第三方依靠(更轻量)
QueryWrapper 是否支撑在微服务项目下进行 RPC 传输 不知道
逻辑删去
达观锁
SQL 审计
数据填充 ✔️ (收费)
数据脱敏 ✔️ (收费)
字段权限 ✔️ (收费)
字段加密 ✔️ (收费)
字典回写 ✔️ (收费)
Db + Row
Entity 监听
多数据源支撑 借助其他结构或收费
多数据源是否支撑 Spring 的业务管理,比方@TransactionalTransactionTemplate
多数据源是否支撑 “非Spring” 项目
多租户
动态表名
动态 Schema

官网供给的和同类结构的功用对比

  • MyBatis-Flex 的查询单条数据的速度,大概是 MyBatis-Plus 的 5 ~ 10+ 倍。
  • MyBatis-Flex 的查询 10 条数据的速度,大概是 MyBatis-Plus 的 5~10 倍左右。
  • Mybatis-Flex 的分页查询速度,大概是 Mybatis-Plus 的 5~10 倍左右。
  • Mybatis-Flex 的数据更新速度,大概是 Mybatis-Plus 的 5~10+ 倍。

亮点功用

除了Mybatis-plus带的那些功用,Mybatis-Flex供给了多主键、复合主键功用;供给了相关查询;特别是相关查询在日常业务开发碰到的场景许多。

Mybatis-Flex供给了1对1、一对多、多对一、多对多的场景。

1对1相关查询@RelationOneToOne

假定有一个账户,账户有身份证,账户和身份证的联系是1对1的联系,代码如下所示:

Account.java :

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationOneToOne(selfField = "id", targetField = "accountId")
    private IDCard idCard;
    //getter setter
}

IDCard.java :

@Table(value = "tb_idcard")
public class IDCard implements Serializable {
    private Long accountId;
    private String cardNo;
    private String content;
    //getter setter
}

@RelationOneToOne装备描绘:

  • selfField当时实体类的特点
  • targetField方针方针的联系实体类的特点

PS: 若selfField是主键,且当时表只要 1 个主键时,能够不填写。因而,以上的装备能够简化为@RelationOneToOne(targetField = "accountId")

假定数据库 5 条 Account 数据,然后进行查询:

List<Account> accounts = accountMapper.selectAllWithRelations();
System.out.println(accounts);

其履行的 SQL 如下:

SELECT `id`, `user_name`, `age` FROM `tb_account`
SELECT `account_id`, `card_no`, `content` FROM `tb_idcard`
WHERE account_id IN (1, 2, 3, 4, 5)

查询打印的成果如下:

[
 Account{id=1, userName='孙悟空', age=18, idCard=IDCard{accountId=1, cardNo='0001', content='内容1'}},
 Account{id=2, userName='猪八戒', age=19, idCard=IDCard{accountId=2, cardNo='0002', content='内容2'}},
 Account{id=3, userName='沙和尚', age=19, idCard=IDCard{accountId=3, cardNo='0003', content='内容3'}},
 Account{id=4, userName='六耳猕猴', age=19, idCard=IDCard{accountId=4, cardNo='0004', content='内容4'}},
 Account{id=5, userName='王麻子叔叔', age=19, idCard=IDCard{accountId=5, cardNo='0005', content='内容5'}}
 ]

一对多相关查询@RelationOneToMany

假定一个账户有许多本书本,一本书只能归属一个账户所有;账户和书本的联系是一对多的联系,代码如下:

Account.java :

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationOneToMany(selfField = "id", targetField = "accountId")
    private List<Book> books;
    //getter setter
}

Book.java :

@Table(value = "tb_book")
public class Book implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;
    //getter setter
}

@RelationOneToMany装备描绘:

  • selfField当时实体类的特点
  • targetField方针方针的联系实体类的特点

PS: 若selfField是主键,且当时表只要 1 个主键时,能够不填写。因而,以上的装备能够简化为@RelationOneToOne(targetField = "accountId")

假定数据库 5 条 Account 数据,然后进行查询:

List<Account> accounts = accountMapper.selectAllWithRelations();
System.out.println(accounts);

其履行的 SQL 如下:

SELECT `id`, `user_name`, `age` FROM `tb_account`
SELECT `id`, `account_id`, `title`, `content` FROM `tb_book`
WHERE account_id IN (1, 2, 3, 4, 5)

Map 映射

Account.books是一个Map,而非List,那么,咱们需求经过装备mapKeyField来指定运用用个列来充当MapKey, 如下代码所示:

java

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationOneToMany(selfField = "id", targetField = "accountId"
        , mapKeyField = "id") //运用 Book 的 id 来填充这个 map 的 key
    private Map<Long, Book> books;
    //getter setter
}

多对多注解@RelationManyToMany也是如此。

多对一相关查询@RelationManyToOne

假定一个账户有许多本书本,一本书只能归属一个账户所有;账户和书本的联系是一对多的联系,书本和账户的联系为多对一的联系,代码如下:

Account.java:

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    //getter setter
}

Book.java 多对一的装备:

@Table(value = "tb_book")
public class Book implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;
    @RelationManyToOne(selfField = "accountId", targetField = "id")
    private Account account;
    //getter setter
}

@RelationManyToOne装备描绘:

  • selfField当时实体类的特点
  • targetField方针方针的联系实体类的特点

PS: 若targetField方针方针的是主键,且方针方针的表只要 1 个主键时,能够不填写。因而,以上的装备能够简化为@RelationManyToOne(selfField = "accountId")

多对多相关查询@RelationManyToMany

假定一个账户能够有多个人物,一个人物也能够有多个账户,他们是多对多的联系,需求经过中心件表tb_role_mapping来维护:

tb_role_mapping的表结构如下:

CREATE TABLE  `tb_role_mapping`
(
    `account_id`  INTEGER ,
    `role_id`  INTEGER
);

Account.java 多对多的装备:

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationManyToMany(
            joinTable = "tb_role_mapping", // 中心表
            selfField = "id", joinSelfColumn = "account_id",
            targetField = "id", joinTargetColumn = "role_id"
    )
    private List<Role> roles;
    //getter setter
}

Role.java 多对多的装备:

@Table(value = "tb_role")
public class Role implements Serializable {
    private Long id;
    private String name;
    //getter setter
}

@RelationManyToMany装备描绘:

  • selfField 当时实体类的特点
  • targetField 方针方针的联系实体类的特点
  • joinTable 中心表
  • joinSelfColumn 当时表和中心表的联系字段
  • joinTargetColumn 方针表和中心表的联系字段

注意:selfField 和 targetField 装备的是类的特点名,joinSelfColumn 和 joinTargetColumn 装备的是中心表的字段名。

selfFieldtargetField分别是两张联系表的主键,且表只要 1 个主键时,能够不填写。因而,以上装备能够简化如下:

public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationManyToMany(
            joinTable = "tb_role_mapping", // 中心表
            joinSelfColumn = "account_id",
            joinTargetColumn = "role_id"
    )
    private List<Role> roles;
    //getter setter
}

父子联系查询​

比方在一些体系中,比方菜单会有一些父子联系,例如菜单表如下:

CREATE TABLE `tb_menu`
(
    `id`        INTEGER auto_increment,
    `parent_id`        INTEGER,
    `name`      VARCHAR(100)
);

Menu.java 定义如下:

@Table(value = "tb_menu")
public class Menu implements Serializable {
    private Long id;
    private Long parentId;
    private String name;
    @RelationManyToOne(selfField = "parentId", targetField = "id")
    private Menu parent;
    @RelationOneToMany(selfField = "id", targetField = "parentId")
    private List<Menu> children;
    //getter setter
}

查询尖端菜单:

QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));
List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);
System.out.println(JSON.toJSONString(menus));

SQL 履行如下:

SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE `parent_id` = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE id = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE parent_id IN (1, 2, 3)

JSON 输出内容如下:

[  {    "children": [      {        "id": 4,        "name": "子菜单",        "parentId": 1      },      {        "id": 5,        "name": "子菜单",        "parentId": 1      }    ],
    "id": 1,
    "name": "尖端菜单1",
    "parentId": 0
  },
  {
    "children": [],
    "id": 2,
    "name": "尖端菜单2",
    "parentId": 0
  },
  {
    "children": [
      {
        "id": 6,
        "name": "子菜单",
        "parentId": 3
      },
      {
        "id": 7,
        "name": "子菜单",
        "parentId": 3
      },
      {
        "id": 8,
        "name": "子菜单",
        "parentId": 3
      }
    ],
    "id": 3,
    "name": "尖端菜单3",
    "parentId": 0
  }
]

在以上的父子联系查询中,默许的递归查询深度为 3 个层级,若需求查询指定递归深度,需求增加如下装备:

QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));
//设置递归查询深度为 10 层
RelationManager.setMaxDepth(10);
List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);

RelationManager.setMaxDepth(10)的装备,只在当时第一次查询有效,查询后会清除设置。

MyBatis-Flex 逻辑删去

假定在 tb_account 表中,存在一个为 is_deleted 的字段,用来标识该数据的逻辑删去,那么 tb_account 表 对应的 “Account.java” 实体类应该装备如下:

@Table("tb_account")
public class Account {
    @Column(isLogicDelete = true)
    private Boolean isDelete;
    //Getter Setter...
}

此时,当咱们履行如下的删去代码是:

accountMapper.deleteById(1);

MyBatis 履行的 SQL 如下:

UPDATE `tb_account` SET `is_delete` = 1
WHERE `id` = ? AND `is_delete` = 0

能够看出,当履行 deleteById 时,MyBatis 只是进行了 update 操作,而非 delete 操作。

注意事项

当 “tb_account” 的数据被删去时( is_delete = 1 时),咱们经过 MyBatis-Flex 的 selectOneById 去查找数据时,会查询不到数据。 原因是selectOneById会主动增加上is_delete = 0条件,履行的 sql 如下:

SELECT * FROM tb_account where id = ? and is_delete = 0

不仅仅是 selectOneById 办法会增加is_delete = 0条件,BaseMapper 的以下办法也都会增加该条件:

  • selectOneBy**
  • selectListBy**
  • selectCountBy**
  • paginate

一起,比方 Left Join 或许子查询等,若子表也设置了逻辑删去字段, 那么子表也会增加相应的逻辑删去条件,例如:

QueryWrapper query1 = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(ARTICLE).as("a").on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
    .where(ACCOUNT.AGE.ge(10));

其履行的 SQL 如下:

SELECT * FROM `tb_account`
    LEFT JOIN `tb_article` AS `a` ON `tb_account`.`id` = `a`.`account_id` 
WHERE `tb_account`.`age` >= 10 
  AND `tb_account`.`is_delete` = 0 AND `a`.`is_delete` = 0

主动增加上tb_account.is_delete = 0 AND a.is_delete = 0条件。

示例 2:

QueryWrapper query2 = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(
            //子查询
            select().from(ARTICLE).where(ARTICLE.ID.ge(100))
    ).as("a").on(
            ACCOUNT.ID.eq(raw("a.id"))
    )
    .where(ACCOUNT.AGE.ge(10));

其履行的 SQL 如下:

SELECT * FROM `tb_account`
    LEFT JOIN (
        SELECT * FROM `tb_article` WHERE `id` >= 100 AND `is_delete` = 0
        ) AS `a` 
    ON `tb_account`.`id` = a.id 
WHERE `tb_account`.`age` >= 10 AND `tb_account`.`is_delete` = 0

数据脱敏

数据脱敏是什么

跟着《网络安全法》的公布实施,对个人隐私数据的维护现已上升到法律层面。 数据脱敏是指对某些敏感信息经过脱敏规矩进行数据的变形, 完成敏感隐私数据的可靠维护。在触及客户安全数据或许一些商业性敏感数据的情况下,在不违背体系规矩条件下,对真实数据进行改造并供给运用, 如身份证号、手机号、卡号、客户号等个人信息都需求进行数据脱敏。

@ColumnMask

MyBatis-Flex 供给了@ColumnMask()注解,以及内置的 9 种脱敏规矩,帮助开发者方便的进行数据脱敏。例如:

java

@Table("tb_account")
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    @ColumnMask(Masks.CHINESE_NAME)
    private String userName;
}

以上的示例中,运用了CHINESE_NAME的脱敏规矩,其主要用于处理 “中文名字” 的场景。当咱们查询到 userName 为张三丰的时分,其内容主动被处理成张**

除此之外,MyBatis-Flex 还供给了如下的 8 中脱敏规矩(共9种),方便开发者直接运用:

  • 手机号脱敏
  • 固定电话脱敏
  • 身份证号脱敏
  • 车牌号脱敏
  • 地址脱敏
  • 邮件脱敏
  • 密码脱敏
  • 银行卡号脱敏

自定义脱敏规矩

当 Mybaits-Flex 内置的 9 中脱敏规矩无法满足要求时,咱们还能够自定义脱敏规矩,其过程如下:

1、经过MaskManager注册新的脱敏规矩:

MaskManager.registerMaskProcesser("自定义规矩称号"
        , data -> {
            return data;
        })

2、运用自定义的脱敏规矩

@Table("tb_account")
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    @ColumnMask("自定义规矩称号")
    private String userName;
}

撤销脱敏处理

在某些场景下,程序希望查询得到的数据是原始数据,而非脱敏数据。比方要去查询用户的手机号,然后给用户发送短信。又或许说,咱们进入修改页面修改用户数据, 假如修改页面展示的是脱敏数据,然后再次点击保存,那么数据库的真实数据也会被脱敏掩盖。

因而,MaskManager 供给了execWithoutMaskskipMaskrestoreMask三个办法来处理这种场景:

引荐运用execWithoutMask办法,该办法运用了模版办法规划形式,确保越过脱敏处理并履行相关逻辑后主动康复脱敏处理。

execWithoutMask办法完成如下:

public static <T> T execWithoutMask(Supplier<T> supplier) {
    try {
        skipMask();
        return supplier.get();
    } finally {
        restoreMask();
    }
}

运用办法:

AccountMapper mapper = ...;
List<Account> accounts = MaskManager.execWithoutMask(mapper::selectAll);
System.out.println(accounts);

skipMaskrestoreMask办法需配套运用,引荐运用try{...}finally{...}形式,如下例所示。 运用这两个办法能够自主控制越过脱敏处理和康复脱敏处理的机遇。 当越过脱敏处理和康复脱敏处理无法放在同一个办法中时,能够运用这两个办法。 此时需求细心处理代码分支及反常,以防止越过脱敏处理后未康复脱敏处理,导致安全隐患。

try {
    MaskManager.skipMask();
    //此处查询到的数据不会进行脱敏处理
    accountMapper.selectListByQuery(...);
} finally {
    MaskManager.restoreMask();
}

SQL 审计

SQL 审计是一项十分重要的工作,是企业数据安全体系的重要组成部分,经过 SQL 审计功用为数据库请求进行全程记录,为过后追溯溯源供给了一手的信息,一起能够经过能够对恶意拜访及时正告管理员,为防护策略优化供给数据支撑。

一起、供给 SQL 拜访日志长时刻存储,满足等保合规要求。

敞开审计功用^1.0.5​

Mybaits-Flex 的 SQL 审计功用,默许是关闭的,若敞开审计功用,许增加如下装备。

AuditManager.setAuditEnable(true)

默许情况下,Mybaits-Flex 的审计音讯(日志)只会输出到控制台,如下所示:

>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024523, elapsedTime=1}
>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024854, elapsedTime=3}
>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991025100, elapsedTime=2}

Mybaits-Flex 音讯包含了如下内容:

  • platform:渠道,或许是运转的应用
  • module:应用模块
  • url:履行这个 SQL 触及的 URL 地址
  • user:履行这个 SQL 触及的 渠道用户
  • userIp:履行这个 SQL 的渠道用户 IP 地址
  • hostIp:履行这个 SQL 的服务器 IP 地址
  • query:SQL 内容
  • queryParams:SQL 参数
  • queryTime:SQL 履行的时刻点(当时时刻)
  • elapsedTime:SQL 履行的耗费时刻(毫秒)
  • metas:其他扩展元信息

业务管理

MyBatis-Flex 供给了一个名为Db.tx()的办法^1.0.6,用于进行业务管理,若运用 Spring 结构的场景下,也可运用@Transactional注解进行业务管理。

Db.tx()办法定义如下:

boolean tx(Supplier<Boolean> supplier);
boolean tx(Supplier<Boolean> supplier, Propagation propagation);
<T> T txWithResult(Supplier<T> supplier);
<T> T txWithResult(Supplier<T> supplier, Propagation propagation);

办法:

  • tx:回来成果为 Boolean,回来null或许false或许 抛出反常,业务回滚
  • txWithResult:回来成果由Supplier参数决议,只要抛出反常时,业务回滚

参数:

  • supplier:要履行的内容(代码)
  • propagation:业务传播特点

业务传播特点propagation是一个枚举类,其枚举内容如下:

//若存在当时业务,则参加当时业务,若不存在当时业务,则创立新的业务
REQUIRED(0),
//若存在当时业务,则参加当时业务,若不存在当时业务,则已非业务的办法运转
SUPPORTS(1),
//若存在当时业务,则参加当时业务,若不存在当时业务,则抛出反常
MANDATORY(2),
//始终以新业务的办法运转,若存在当时业务,则暂停(挂起)当时业务。
REQUIRES_NEW(3),
//以非业务的办法运转,若存在当时业务,则暂停(挂起)当时业务。
NOT_SUPPORTED(4),
//以非业务的办法运转,若存在当时业务,则抛出反常。
NEVER(5),
//暂时不支撑
NESTED(6),

Db.tx()代码示例:

Db.tx(() -> {
    //进行业务操作
    return true;
});

tx()办法抛出反常,或许回来 false,或许回来 null,则回滚业务。只要正常回来 true 的时分,进行业务提交。

嵌套业务

示例代码:

Db.tx(() -> {
    //进行业务操作
    boolean success = Db.tx(() -> {
        //另一个业务的操作
        return true;
    });
    return true;
});

支撑无限极嵌套,默许情况下,嵌套业务直接的联系是:REQUIRED(若存在当时业务,则参加当时业务,若不存在当时业务,则创立新的业务)。

@Transactional

MyBatis-Flex 已支撑 Spring 结构的@Transactional,在运用 SpringBoot 的情况下,能够直接运用@Transactional进行业务管理。 同理,运用 Spring 的TransactionTemplate进行业务管理也是没问题的。

注意:若项目未运用 SpringBoot,只用到了 Spring,需求参考 MyBatis-Flex 的FlexTransactionAutoConfiguration进行业务装备,才能正常运用@Transactional注解。

特征

  • 1、支撑嵌套业务
  • 2、支撑多数据源

注意:在多数据源的情况下,所有数据源的数据库请求(Connection)会履行相同的 commit 或许 rollback,但并非原子操作。例如:

@Transactional
public void doSomething(){
    try{
        DataSourceKey.use("ds1");
        Db.updateBySql("update ....");
    }finally{
        DataSourceKey.clear()
    }
    try{
        DataSourceKey.use("ds2");
        Db.updateBySql("update ...");
    }finally{
        DataSourceKey.clear()
    }
    //抛出反常
    int x = 1/0;
}

在以上的比方中,两次Db.update(...)虽然是两个不同的数据源,但它们都在同一个业务@Transactional里,因而,当抛出反常的时分, 它们都会进行回滚(rollback)。

以上说到的并非原子操作,指的是:

假定在回滚的时分,恰好其间一个数据库出现了反常(比方 网络问题,数据库溃散),此时,可能只要一个数据库的数据正常回滚(rollback)。 但无论如何,MyBatis-Flex 都会确保在同一个@Transactional中的多个数据源,保持相同的 commit 或许 rollback 行为。

字段权限

字段权限,指的是在一张表中规划了许多字段,但是不同的用户(或许人物)查询,回来的字段成果是不一致的。 比方:tb_account 表中,有 user_name 和 password 字段,但是 password 字段只允许用户自己查询, 或许超级管理员查询,这种场景下,咱们会用到 字段权限 的功用。

@Table()注解中,有一个装备名为onSet,用于设置这张表的设置监听,这儿的设置监听指的是: 当咱们运用 sql 、调用某个办法去查询数据,得到的数据内容映射到 entity 实体,mybatis 经过 setter 办法去设置 entity 的值时的监听。

以下是示例:

step 1: 为实体类编写一个 set 监听器(SetListener

public class AccountOnSetListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        if (property.equals("password")){
            //去查询当时用户的权限
            boolean hasPasswordPermission = getPermission();
            //若没有权限,则把数据库查询到的 password 内容修改为 null
            if (!hasPasswordPermission){
                value = null;
            }
        }
        return value;
    }
}

step 2: 为实体类装备onSet监听

@Table(value = "tb_account", onSet = AccountOnSetListener.class)
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    private String password;
    //getter setter
}

字段加密

字段加密,指的是数据库在存入了明文内容,但是当咱们进行查询时,回来的内容为加密内容,而非明文内容。

以下是 MyBatis-Flex 字段加密示例:

step 1: 为实体类编写一个 set 监听器(SetListener

public class AccountOnSetListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        if (value != null){
            //对字段内容进行加密
            value = encrypt(value);
        }
        return value;
    }
}

step 2: 为实体类装备onSet监听

@Table(value = "tb_account", onSet = AccountOnSetListener.class)
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    private String password;
    //getter setter
}