写点什么

EBean ORM 框架介绍 -2. 字段加密、更新日志和历史记录

发布于: 2021 年 06 月 07 日
EBean ORM 框架介绍-2.字段加密、更新日志和历史记录

前文《Ebean ORM框架介绍-1.增强注解》介绍了一些特性注解,本文继续介绍一些注解的高级功能

一、@Encrypted 字段加密

使用@Encrypted注解简单实现对数据库字段进行加密解密,以达到保护重要数据的作用,如下 phone 字段


1. 使用数据库加密

public class User extends BaseModel {
@DbComment("the name") private String name;
@Encrypted private String phone;
private Integer loginCount = 0;}
复制代码


只需要设置@Encrypted就可以使用对指定字段进行加密解密,此设置对应用程序是完全透明的

2. 使用应用程序加密

public class User extends BaseModel {
@DbComment("the name") private String name;
@Encrypted private String phone;
private Integer loginCount = 0;
@Encrypted(dbEncryption=false) String description;}
复制代码

(1) 注解设置

@Encrypted(dbEncryption=false)

(2) 自定义加密程序

public class BasicEncryptKeyManager implements EncryptKeyManager {
@Override public EncryptKey getEncryptKey(String tableName, String columnName) { return new BasicEncryptKey(tableName, columnName); }}
public class BasicEncryptKey implements EncryptKey {
private String tableName; private String columnName; private String key = "0123456";
public BasicEncryptKey(String tableName, String columnName){ this.tableName = tableName; this.columnName = columnName; }
@Override public String getStringValue() { return tableName.concat(columnName).concat(key); }}
复制代码

(3) 配置注解程序

ebean:  encryptKeyManager: fun.barryhome.ebean.encrypt.BasicEncryptKeyManager
复制代码


application.yaml 中设置

3. 性能分析

10:29:06.167 [main] DEBUG io.ebean.SQL - txn[1001] select t0.id, t0.name, CONVERT(AES_DECRYPT(t0.phone,?) USING UTF8) _e_t0_phone, t0.login_count, t0.description, t0.version, t0.when_created, t0.when_modified from user t0 where CONVERT(AES_DECRYPT(t0.phone,?) USING UTF8) = ? and t0.description = ?; --bind(****,130000000000,****)
复制代码


从日志中的 SQL 执行语句可以看出,使用了加密的字段在 SQL 上会做函数转换,如果做为查询条件的话可能会有较大的性能开销,故谨慎使用

二、 @ChangeLog 更新日志

使用@ChangeLog注解将数据更新日志记录到日志中,主要用于日志查询

1. 设置注解

@ChangeLogpublic class User extends BaseModel {  ...}
复制代码

2. 日志配置

这里必须是 logback.xml


<?xml version="1.0" encoding="UTF-8"?><configuration scan="true">
<appender name="CHANGE_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>log/changeLog.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>log/changeLog.log.%d{yyyy-MM-dd}</FileNamePattern> <MaxHistory>90</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{HH:mm:ss.SSS} %msg%n</pattern> </encoder> </appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{80}) - %msg%n</pattern> </encoder> </appender>
<logger name="io.ebean.ChangeLog" level="INFO" additivity="false"> <appender-ref ref="CHANGE_LOG"/> <appender-ref ref="STDOUT"/> </logger>
<!--默认使用console和file--> <root level="INFO" > <appender-ref ref="CHANGE_LOG" /> <appender-ref ref="STDOUT" /> </root></configuration>
复制代码

3. 日志结果

15:01:07.975 [ebean-db1] INFO  io.ebean.ChangeLog - {"ts":1622962867966,"change":"U","type":"User","id":"1","data":{"version":24,"whenModified":"2021-06-06T07:01:07.936Z","description":"Sun Jun 06 15:01:07 CST 2021"},"oldData":{"version":23,"whenModified":"2021-06-06T06:02:49.253Z","description":"Sun Jun 06 14:02:49 CST 2021"}}
复制代码

三、@History 历史记录

使用@history注解可以实现在数据库表中记录所有数据的 update 和 del 操作的前后快照

1. 设置

@History@Table(name = "customer")public class Customer extends BaseModel {
public static final CustomerFinder find = new CustomerFinder();
private String name;
@HistoryExclude private Integer age;
}
复制代码


Entity 上设置**@History**,不需要记录的字段可设置**@HistoryExclude**

2. 数据结构变化


设置注解后数据库结构会发生一些变化


  • customer 表会增加"sys_period_start"和"sys_period_end"两个字段,以及"customer_history_del"和"customer_history_upd"两个触发器

  • 增加"customer_history"历史数据表

  • 增加"customer_with_history"视图

3. 数据变化

1)新增记录变化

@Testpublic void create() {  Customer customer = Customer.builder()    .name("abc")    .age(0)    .build();  customer.save();}
复制代码



源表中 sys_period_start 字段更新为最后更新时间,由于时区原因,比当前时间小 8 小时

2)修改记录变化

@Testpublic void update() {  Customer customer = DB.find(Customer.class, 1L);  customer.setName(UUID.randomUUID().toString());  customer.setAge(customer.getAge() + 1);  customer.update();}
复制代码



源表中原字段已更新, sys_period_start 字段更新为最后更新时间



历史表增加一行数据,记录了原始数据


  • sys_period_start 为新增时的时间

  • sys_period_end 为更新后的时间

  • 两个时间形成本条记录的保持时间



多次更新后会发现规律,后一条记录的 start 时间就是前一条记录的 end 时间,也就是当前记录数据的保持时间

4. 历史记录查询

@Testpublic void query() {  Timestamp date = Timestamp.valueOf("2021-06-07 04:20:00");  Customer customer = Customer.find.query().asOf(date).findOne();  System.err.println(customer);}
复制代码


13:58:44.485 [main] DEBUG io.ebean.SQL - txn[1001] select t0.id, t0.name, t0.age, t0.version, t0.when_created, t0.when_modified from customer_with_history t0 where (t0.sys_period_start <= ? and (t0.sys_period_end is null or t0.sys_period_end > ?)); --bind(asOf 2021-06-07 06:20:00.0, )
复制代码


  • date 可为历史任意一个时间,根据前面的时间规律,可以查询到唯一一条记录,如果前于新增时间则回返为空,如果晚于最后更新时间则返回为最新的记录

  • customer_with_history 为自动创建的视图

5. 历史数据版本比较

@Testpublic void queryList() {    List<Version<Customer>> customerVersions = Customer.find.query()            .where().idEq(1L)            .findVersions();
for (Version<Customer> customerVersion : customerVersions) { Customer bean = customerVersion.getBean(); Map<String, ValuePair> diff = customerVersion.getDiff(); Timestamp effectiveStart = customerVersion.getStart(); Timestamp effectiveEnd = customerVersion.getEnd();
System.err.println(diff); }}
复制代码


{name=a6d6ca0e-c266-4efd-95c5-9a47e94706d2,711d365c-9a40-44ad-be6a-535879c81c12, age=4,null, version=5,4, whenModified=2021-06-07T05:49:06.570Z,2021-06-07T04:29:48.570Z}{name=711d365c-9a40-44ad-be6a-535879c81c12,3b1da201-e600-4d94-ba86-89679edfde4e, version=4,3, whenModified=2021-06-07T04:29:48.570Z,2021-06-07T04:29:23.179Z}{name=3b1da201-e600-4d94-ba86-89679edfde4e,520874da-816c-482f-a3b9-d94be47477ba, version=3,2, whenModified=2021-06-07T04:29:23.179Z,2021-06-07T04:19:45.068Z}{name=520874da-816c-482f-a3b9-d94be47477ba,abc, version=2,1, whenModified=2021-06-07T04:19:45.068Z,2021-06-07T04:18:58.203Z}
复制代码


每次输出都可以看出前后两个值的变化情况

6. 注意事项

  • 事务性,可以保证更新内容是一致性的

  • History 是通过数据库的触发器实现的,故直接修改数据库也可以产生历史数据

  • 相对于 @ChangeLog 来讲,@History 给数据库带来了额外的存储成本和性能开销

  • 对于表结构的修改,原有的触发器和 history 表不会自动更新,结构同步将带来一些麻烦,可使用 ebean 提供的 db 迁移来解决这一问题

四、综述

Ebean 还有很多 JPA 没有的高级功能,如草稿、复合查询、多数据支持、多租户等等功能,后续期待更新。


文中代码由于篇幅原因有一定省略并不是完整逻辑,如有兴趣请 Fork 源代码 https://gitee.com/hypier/barry-ebean/tree/master/ebean-section-2

五、请关注我的公众号


用户头像

还未添加个人签名 2019.07.10 加入

还未添加个人简介

评论

发布
暂无评论
EBean ORM 框架介绍-2.字段加密、更新日志和历史记录