PO Serialization
看起来非常顺利,那我们试着通过 @RestController
将得到的实体传给前端吧:
序列化:就是将对象转化成字节序列的过程。
反序列化:就是讲字节序列转化成对象的过程。
@RestController
@RequiredArgsConstructor
public class studentController {
private final StudentRepository studentRepository;
private final TuitionRepository tuitionRepository;
@GetMapping("/all/student")
public List<Student> getAllStudent() {
return studentRepository.findAllBy(Student.class);
}
@GetMapping("/all/tuition")
public List<Tuition> getAllStudent() {
return tuitionRepository.findAllBy(Tuition.class);
}
}
不好了,前端请求 API 时后端抛出了 java.lang.StackOverflowError
!
这个异常其实是 Jackson 抛出的。由于 Student
里有 Tuition
,Tuition
里有 Student
,这样无限套娃,Jackson 的序列化就无限地递归处理下去直到崩溃了。
要处理这个问题也好办,有两种方法:
方法一
@JsonBackReference
@JsonManagedReference
在 owning side 使用
@JsonBackReference
,在 non-owning side 使用@JsonManagedReference
注解。@Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @JsonManagedReference @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.LAZY) private Tuition tuition; /* Getters and setters */ }
@Entity @Table(name = "tuition") public class Tuition { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private Double fee; @JsonBackReference @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "student_id") private Student student; /* Getters and setters */ }
序列化结果:
/all/student
[ { "id": 1, "name": "张三", "tuition": { "id": 2, "fee": 1145.14 } } ]
我们发现,方法一序列化
Student
时,成功将其内部的tuition
序列化,并且内部tuition
的student
域被忽略,消除了循环引用。/all/tuition
[ { "id": 2, "fee": 1145.14 } ]
很遗憾,
tuition
的其他域虽然被成功序列化,其student
域无法被实例化。
总结:使用
@JsonBackReference
@JsonManagedReference
确实可以解决循环引用的问题,但是序列化只有 non-owning side 会序列化内部的引用,并且随着数据库关系变得复杂,这种解决方法渐渐变得无法维护,思考谁是 owning side 容易把人绕晕。方法二
@JsonIdentityInfo
方法二非常简单,只需要无脑地对所有实体加上
@JsonIdentityInfo
注解即可。@Entity @Table(name = "student") @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "@id") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private Tuition tuition; /* Getters and setters */ }
@Entity @Table(name = "tuition") @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "@id") public class Tuition { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private Double fee; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "student_id") private Student student; /* Getters and setters */ }
序列化结果:
/all/student
[ { "@id": "13389f64-688b-4162-b030-1039133c15f6", "id": 1, "name": "张三", "tuition": { "@id": "9b90d0bb-d267-4111-a262-bfca07e43f13", "id": 2, "fee": 1145.14, "student": "13389f64-688b-4162-b030-1039133c15f6" } } ]
/all/tuition
[ { "@id": "5fc601fc-6eae-489b-a225-fb205f7c2d0c", "id": 2, "fee": 1145.14, "student": { "@id": "adc4d249-de70-4070-ae33-dce1f1384574", "id": 1, "name": "张三", "tuition": "5fc601fc-6eae-489b-a225-fb205f7c2d0c" } } ]
可以看到,无论是 owning side 还是 non-owning side 序列化,都能完整地展现全部信息。并且,序列化得到的 JSON 中,不仅包含了原来的域,还新包含了
@id
域。每一次序列化,Jackson 用不同的 UUID 标记每个对象,这样就可以在遇到已经序列化的对象时停止序列化,而将那个域的值设置为引用的 UUID。方法三
为什么要直接向前端返回 PO 呢?
我们应该创建合适的 VO,将 PO 在 service 层转化为 VO 再返回给前端,这样不就好了。
最后更新于