2

问题描述

数据导入,因为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注解一个回调方法。

回调注解

回调注解 描述
@PostLoad Executed after an entity has been loaded into the current persistence context or an entity has been refreshed.
@PrePersist Executed before the entity manager persist operation is actually executed or cascaded. This call is synchronous with the persist operation.
@PostPersist Executed after the entity manager persist operation is actually executed or cascaded. This call is invoked after the database INSERT is executed.
@PreUpdate Executed before the database UPDATE operation.
@PostUpdate Executed after the database UPDATE operation.
@PreRemove Executed before the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.
@PostRemove Executed 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的回调方法中如果修改了实体属性,不会反映到数据表。

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。


引用和评论

0 条评论