You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:45:57