前文《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, )
复制代码
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
五、请关注我的公众号
评论