问题描述
数据导入,因为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;
}
不小心中奖了,code
、key
、value
这三个都是mysql
里的关键字或保留字。
解决过程中也算总结出了一点经验。机器人会报如下的错:
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 ...");
}
但不允许在几个方法中重复相同的回调
@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());
}
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
学生有三个属性,id
、name
、code
。
@Test
public void saveTest() {
Student student = new Student();
student.setName("zhangsan");
studentRepository.save(student);
}
直接运行。
this is prePersist
this is postPersist
PostPersist回调业务逻辑
@PostPersist
public void postPersist() {
System.out.println("this is postPersist");
this.code = String.valueOf(this.id);
}
再运行:
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
中修改的实体属性不会反映到数据表。
this is prePersist
this is postPersist
this is postLoad
this is postLoad
this is preUpdate
this is postUpdate
结果为null
。很遗憾,@PostUpdate
中修改的实体属性不会反映到数据表。
新发现的问题
最开始以为都好使,然后在项目中应用,但遇到了问题。
初始化时,精确度类别的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);
}
code
存储失败,未反映到数据表。
总结
- 可以通过监听器在实体的各个状态执行相关回调方法。
-
@PostPersist
的回调方法中如果修改了实体属性,如果主键有值生成策略,会自动调用一次update
方法,将数据反映到数据表。 - 如果主键不添加策略,不会反映到数据表。
-
@PostUpdate
的回调方法中如果修改了实体属性,不会反映到数据表。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。