前文《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. 设置注解
@ChangeLog
public 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)新增记录变化
@Test
public void create() {
Customer customer = Customer.builder()
.name("abc")
.age(0)
.build();
customer.save();
}
源表中sys_period_start字段更新为最后更新时间,由于时区原因,比当前时间小8小时
2)修改记录变化
@Test
public 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. 历史记录查询
@Test
public 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. 历史数据版本比较
@Test
public 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