Hibernate/JPA单向@ManyToMany关系CRUD序列化异常排查
问题:Teacher关联Timeslot序列化时重复对象变为ID而非完整实体
已实现的实体类
Teacher类
@Getter @Setter @ToString @Entity @Builder @AllArgsConstructor @NoArgsConstructor public class Teacher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false, updatable = false) private Long id; private String name; @ManyToMany(cascade = CascadeType.MERGE) @JoinTable( name = "teacher_timeslot", joinColumns = @JoinColumn(name = "teacher_id"), inverseJoinColumns = @JoinColumn(name = "timeslot_id") ) @ToString.Exclude private Set<Timeslot> timeslots = new HashSet<>(); //教师偏好时间段 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; Teacher teacher = (Teacher) o; return id != null && Objects.equals(id, teacher.id); } @Override public int hashCode() { return getClass().hashCode(); } }
Timeslot类
//忽略JsonIdentity,因为其他类(具体是Timetable)需要它 @JsonIdentityInfo(scope = Timeslot.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") @Getter @Setter @ToString //@RequiredArgsConstructor @Entity @Builder @AllArgsConstructor @NoArgsConstructor public class Timeslot{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false, updatable = false) @PlanningId private Long id; private DayOfWeek dayOfWeek; private LocalTime startTime; private LocalTime endTime; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; Timeslot timeslot = (Timeslot) o; return id != null && Objects.equals(id, timeslot.id); } @Override public int hashCode() { return getClass().hashCode(); } }
操作使用的JSON载荷
保存Timeslot
{"id":3,"dayOfWeek":"MONDAY","startTime":"14:00:00","endTime":"16:00:00"}
查询Timeslots返回结果
[ { "id": 1, "dayOfWeek": "MONDAY", "startTime": "10:00:00", "endTime": "12:00:00" }, { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" }, { "id": 3, "dayOfWeek": "MONDAY", "startTime": "14:00:00", "endTime": "16:00:00" } ]
保存/查询Teacher的载荷
{ "id": 1, "name": "Ben", "timeslots": [ { "id": 1, "dayOfWeek": "MONDAY", "startTime": "10:00:00", "endTime": "12:00:00" }, { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" } ] }
添加教师Alex的载荷
{ "id": 2, "name": "Alex", "timeslots": [ { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" }, { "id": 3, "dayOfWeek": "MONDAY", "startTime": "14:00:00", "endTime": "16:00:00" } ] }
问题现象
调用getAllTeachers()接口返回结果中,Alex的timeslots列表里id为2的Timeslot被序列化为数字2,而非完整对象:
[ { "id": 1, "name": "Ben", "timeslots": [ { "id": 1, "dayOfWeek": "MONDAY", "startTime": "10:00:00", "endTime": "12:00:00" }, { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" } ] }, { "id": 2, "name": "Alex", "timeslots": [ 2, // 问题所在 { "id": 3, "dayOfWeek": "MONDAY", "startTime": "14:00:00", "endTime": "16:00:00" } ] } ]
期望返回结果
[ { "id": 1, "name": "Ben", "timeslots": [ { "id": 1, "dayOfWeek": "MONDAY", "startTime": "10:00:00", "endTime": "12:00:00" }, { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" } ] }, { "id": 2, "name": "Alex", "timeslots": [ { "id": 2, "dayOfWeek": "MONDAY", "startTime": "12:00:00", "endTime": "14:00:00" }, { "id": 3, "dayOfWeek": "MONDAY", "startTime": "14:00:00", "endTime": "16:00:00" } ] } ]
已尝试但无效的方案
- 移除@ManyToMany的cascade属性并添加FetchType.Eager
- 移除@ManyToMany的cascade和fetch属性
- 在Service的创建与查询方法上添加jakarta @Transactional注解
Teacher Service实现
@Service @RequiredArgsConstructor @Slf4j public class TeacherService { private final TeacherRepo teacherRepo; public List<Teacher> getAllTeachers() { return teacherRepo.findAllByOrderByIdAsc(); } public Teacher createTeacher(Teacher teacher) { return teacherRepo.save(teacher); } //update and delete methods }
需求
解决序列化异常问题,同时确保CRUD功能正常,能够实现如下关联逻辑:
Timeslot wednesday12to14 = new Timeslot(nextTimeslotId++, DayOfWeek.WEDNESDAY, LocalTime.of(12, 0), LocalTime.of(14, 0)); Teacher ben = new Teacher(nextTeacherId++, "Ben", List.of(tuesday12to14, tuesday14to16, tuesday16to18, tuesday18to20, tuesday20to22, wednesday12to14, wednesday14to16, wednesday18to20, wednesday20to22));
内容的提问来源于stack exchange,提问作者Diallo Francis Patrick
相关产品推荐
相关产品推荐

