Entity 监听器

问题描述

数据导入,因为ID业务无关性,在多系统中,需要添加code进行对象唯一性标识。

某些实体,其code为国家规定,需要用户输入,某些国家未规定代码的实体,我们需要为其设置一个不重复的代码作为标识。

最初

为了保证每次运行系统,该代码不重复,所以新建一张code表用于存储不同的实体的code编号到多少了,以保证不重复。

为了让每个人都能看懂,用了Code作为实体名,key作为相应的实体名(建了一个枚举,防止打错),value作为已经编码到的号码。

实体省略getter、setter方法。

@ApiModel("代码实体")
@Entity
@Table(name = "resource_code")
public class Code {

    @Id
    @GeneratedValue
    private Long id;

    @ApiModelProperty("键")
    @Column(name = "code_key", unique = true)
    private String key;

    @ApiModelProperty("值")
    @Column(name = "code_value")
    private Integer value = 0;
}

不小心中奖了,codekeyvalue这三个都是mysql里的关键字或保留字。

clipboard.png

解决过程中也算总结出了一点经验。机器人会报如下的错:

Unsuccessful: create table resource_code
You have an error in your SQL syntax;

建表失败,你的SQL有语法错误,因为ORM映射是Hibernate为我们实现的,经过了全世界开发者的测试,肯定不会错,那为什么语法错误呢?极有可能使用了数据库中的关键字或保留字。

更好的方案

潘老师又提出了更好的方案:

id由数据库唯一分配,可以在当前实体有id之后,将id再存储一份给code

@PostPersist        // 实体新建以后执行
public void postPersist() {
    if (this.code == null || this.code.equals("")) {
        this.code = this.id.toString();
    }
}

需要在持久化之后执行,我们就需要学习一下Entity监听器。

监听器

实体监听器不需要实现任何特定的接口。可以将回调注解应用到特定事件中需要被通知的任何方法。
可以在单个方法中合并几个回调,但不允许在几个方法中重复相同的回调。

——《Hibernate实战 第12章 12.3.4 实体监听器与回调》

回调

可以在单个方法中合并几个回调

@PrePersist
@PostPersist
public void callback() {
    System.out.println("running ...");
}

clipboard.png

但不允许在几个方法中重复相同的回调

@PrePersist
public void callback() {
    System.out.println("running ...");
}

@PrePersist
public void callback2() {
    System.out.println("running ...");
}
You can only annotate one callback method with javax.persistence.PrePersist in bean class

控制台会报错:在bean里,你只能使用PrePersist注解一个回调方法。

回调注解

回调注解描述
@PostLoadExecuted after an entity has been loaded into the current persistence context or an entity has been refreshed.
@PrePersistExecuted before the entity manager persist operation is actually executed or cascaded. This call is synchronous with the persist operation.
@PostPersistExecuted after the entity manager persist operation is actually executed or cascaded. This call is invoked after the database INSERT is executed.
@PreUpdateExecuted before the database UPDATE operation.
@PostUpdateExecuted after the database UPDATE operation.
@PreRemoveExecuted before the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.
@PostRemoveExecuted after the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.

测试

@Test
public void saveTest() {
    Student student = new Student();
    student.setName("zhangsan");
    studentRepository.save(student);
    Student newStudent = studentRepository.findOne(student.getId());
    newStudent.setName("newZhangsan");
    studentRepository.save(newStudent);
    studentRepository.delete(student.getId());
}

clipboard.png

this is prePersist
this is postPersist
this is postLoad
this is postLoad
this is preUpdate
this is postUpdate
this is postLoad
this is preRemove
this is postRemove

执行过程应该与各位预期一致。

@PostPersist

学生有三个属性,idnamecode

@Test
public void saveTest() {
    Student student = new Student();
    student.setName("zhangsan");
    studentRepository.save(student);
}

直接运行。

clipboard.png

clipboard.png

this is prePersist
this is postPersist

PostPersist回调业务逻辑

@PostPersist
public void postPersist() {
    System.out.println("this is postPersist");
    this.code = String.valueOf(this.id);
}

再运行:

clipboard.png

clipboard.png

this is prePersist
this is postPersist
this is preUpdate
this is postUpdate

与上次相比,多了两次update的执行。

所以,我们总结出,@PostPersist在数据库语句插入之后执行,如果我们在@PostPersist的回调方法中修改了对象的状态,自动地调用一次更新方法,将修改后的状态持久化。

@PostUpdate

既然@PostPersist都能自动将数据持久化,我们再来试试@PostUpdate

@PostPersist
public void postPersist() {
    System.out.println("this is postPersist");
}
@PostUpdate
public void postUpdate() {
    System.out.println("this is postUpdate");
    this.code = String.valueOf(this.id + 1);
}
@Test
public void saveTest() {
    Student student = new Student();
    student.setName("zhangsan");
    studentRepository.save(student);
    Student newStudent = studentRepository.findOne(student.getId());
    newStudent.setName("newZhangsan");
    studentRepository.save(newStudent);
}
  • 如果code的值为id + 1的话,说明自动将@PostUpdate的数据改动也持久化。
  • 如果为null,说明没有自动持久化,@PostUpdate中修改的实体属性不会反映到数据表。

clipboard.png

clipboard.png

this is prePersist
this is postPersist
this is postLoad
this is postLoad
this is preUpdate
this is postUpdate

结果为null。很遗憾,@PostUpdate中修改的实体属性不会反映到数据表。

新发现的问题

最开始以为都好使,然后在项目中应用,但遇到了问题。

clipboard.png

clipboard.png

初始化时,精确度类别的code存上了,但是用途的code没存上。

后来发现用途实体主键不是自增的。

@Entity
public class Student {

    @Id
    private Long id;

    @PostPersist
    public void postPersist() {
        System.out.println("this is postPersist");
        this.code = "123";
    }
}

去掉主键值生成策略,进行测试。

@Test
public void saveTest() {
    Student student = new Student();
    student.setId(1L);
    student.setName("zhangsan");
    studentRepository.save(student);
}

clipboard.png

code存储失败,未反映到数据表。

总结

  1. 可以通过监听器在实体的各个状态执行相关回调方法。
  2. @PostPersist的回调方法中如果修改了实体属性,如果主键有值生成策略,会自动调用一次update方法,将数据反映到数据表。
  3. 如果主键不添加策略,不会反映到数据表。
  4. @PostUpdate的回调方法中如果修改了实体属性,不会反映到数据表。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值