Spring Boot 中的 Hibernate JPA 插入退出实体

问题描述 投票:0回答:1

我在更新应用程序中的课程实体时遇到问题,其中涉及学生和课程实体之间的多对多关系。当我更新课程信息时,操作成功,没有任何问题。但是,当我尝试向课程中添加新学生时,Hibernate 尝试再次插入学生,导致 SQL 错误:

SQL错误:0,SQL状态:23505 错误:重复的键值违反了唯一约束“student_lessons_pkey”

您能否提供帮助来解决此问题?以下是相关更新函数供参考和实体。

@Override
    @Transactional
    public LessonResponse updateLesson(LessonRequest lessonRequest) {
        Long lessonId = lessonRequest.getId();

        if(lessonId == null)
            throw new EntityNotFoundException("Lesson id is required");
        Lesson lesson = lessonRepository.findById(lessonId)
                .orElseThrow(() -> new EntityNotFoundException("Lesson not found"));
        // Update lesson properties
        updateLessonProperties(lesson, lessonRequest);

        // Update students associated with the lesson
        updateLessonStudents(lesson, lessonRequest.getStudents());


        log.info("Lesson: {}", lesson);
        lessonRepository.save(lesson);
        return Mapper.toLessonResponse(lesson);


    }
    private void updateLessonProperties(Lesson lesson, LessonRequest lessonRequest) {
        // Update lesson properties from the request
        lesson.setModul(lessonRequest.getModul());
        lesson.setStartAt(lessonRequest.getStartAt());
        lesson.setUnits(lessonRequest.getUnits());
        lesson.setLessonType(lessonRequest.getLessonType());
        lesson.setDescription(lessonRequest.getDescription());

        // Optionally update the teacher if it has changed
        if (!Objects.equals(lesson.getTeacher().getId(), lessonRequest.getTeacher().getId())) {
            Teacher teacher = teacherRepository.findById(lessonRequest.getTeacher().getId())
                    .orElseThrow(() -> new EntityNotFoundException("Teacher not found"));
            lesson.setTeacher(teacher);
        }
    }
    private void updateLessonStudents(Lesson lesson, List<Student> updatedStudents) {
        List<Long> updatedStudentIds = updatedStudents.stream().map(User::getId).toList();
        log.info("updatedStudentsU: {}", updatedStudentIds);

        // Fetch the existing students associated with the lesson
        List<Long> existingStudentIds = lesson.getStudents().stream().map(User::getId).toList();
        log.info("existingStudents: {}", existingStudentIds);

        existingStudentIds.stream()
                .filter(existingStudentId -> !updatedStudentIds.contains(existingStudentId))
                .forEach(existingStudentId -> {
                    log.info("Removing student with ID: {}", existingStudentId);
                    Student student = studentRepository.findById(existingStudentId)
                            .orElseThrow(() -> new InternalError("Error removing student from lesson!"));
                    lesson.removeStudent(student);
                    student.removeLesson(lesson);

                });
        // Add new students from the updated list
        updatedStudents.stream()
                .filter(student -> !existingStudentIds.contains(student.getId()))
                .forEach(student -> {
                    log.info("Add student with ID: {}", student.getId());
                    Student existingStudent = studentRepository.findById(student.getId())
                            .orElseThrow(() -> new EntityNotFoundException("Student not found for lesson!"));
                    log.info("Student: {}", existingStudent);
                    lesson.addStudent(existingStudent);
                });
    }

这是我的课程实体


@Entity
@Data
@Builder
@NoArgsConstructor(force = true)
@AllArgsConstructor
@Table(name = "lessons")
@SoftDelete
public class Lesson {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private ModuleType modul;

    @Column(nullable = false)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    private LocalDateTime startAt;

    @Column(nullable = false)
    private double units;


    @Enumerated(EnumType.STRING)
    @Column(length = 50, nullable = false) // Adjust the length as needed
    private LessonType  lessonType;
    @Column(length = 512, nullable = false)
    @NonNull
    private String description;

    @ManyToMany
    @JoinTable(
            name = "student_lessons",
            joinColumns = @JoinColumn(name = "lesson_id"),
            inverseJoinColumns = @JoinColumn(name = "student_id")
    )
    @SoftDelete
    @Column(nullable = false)
    @Builder.Default
    private List<Student> students = new ArrayList<>();
    @ManyToOne
    @JoinColumn(name = "teacher_id", nullable = false)
    private Teacher teacher;
    private LocalDateTime lastUpdatedTimestamp;
    private String lastUpdatedBy;
    // Method to add a student to the lesson
    public void addStudent(Student student) {
        this.students.add(student);
        student.getLessons().add(this);
    }

    // Method to remove a student from the lesson
    public void removeStudent(Student student) {
        this.students.remove(student);
        student.getLessons().remove(this);
    }

}

这是我的学生实体

@Entity
@Table(name = "students")
public class Student extends User {


    private String level;
    private boolean portalAccess;

    @ManyToMany(fetch = FetchType.LAZY
            ,mappedBy = "students")
    @Builder.Default
    private List<Lesson> lessons = new ArrayList<>();

    @OneToMany(mappedBy = "student")
    @Builder.Default
    private List<Contract> contracts = new ArrayList<>();


    @ManyToMany(mappedBy = "students")
    @Builder.Default
    private List<Teacher> teachers = new ArrayList<>();
    public void addTeacher(Teacher teacher) {
        teachers.add(teacher);
        teacher.getStudents().add(this);
    }
    public void removeTeacher(Teacher teacher) {
        teachers.remove(teacher);
        teacher.getStudents().remove(this);
    }
    public void addLesson(Lesson lesson) {
        lessons.add(lesson);
        lesson.getStudents().add(this);
    }
    public void removeLesson(Lesson lesson) {
        lessons.remove(lesson);
        lesson.getStudents().remove(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        Student student = (Student) o;
        return Objects.equals(level, student.level) && Objects.equals(lessons, student.lessons) && Objects.equals(contracts, student.contracts) && Objects.equals(teachers, student.teachers);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), level, lessons, contracts, teachers);
    }
}

这是日志信息

INFO 17540 --- [school_system] [nio-8080-exec-2] c.s.service.impl.LessonServiceImpl       : updatedStudentsU: [25, 75]
2024-02-17T12:15:03.041+01:00  INFO 17540 --- [school_system] [nio-8080-exec-2] c.s.service.impl.LessonServiceImpl       : existingStudents: [25]
2024-02-17T12:15:03.043+01:00  INFO 17540 --- [school_system] [nio-8080-exec-2] c.s.service.impl.LessonServiceImpl       : Add student with ID: 75
2024-02-17T12:15:03.072+01:00  INFO 17540 --- [school_system] [nio-8080-exec-2] c.s.service.impl.LessonServiceImpl       : Student: User(id=75, title=HERR, firstName=Fayre, lastName=Dudney, phoneNumber=+358 (355) 234-9484, comment=null, [email protected], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, verified=false, hasProvidedAllInfo=true, lastUpdatedTimestamp=2024-02-01T21:05:55.710775, lastUpdatedBy=null, deleted=false, roles=[Role(id=2, name=ROLE_TEACHER, description=)], address=Address(id=75, street=Quincy, hausnummer=748, city=Kristinestad, state=null, country=Finland, postal=41270, lastUpdatedTimestamp=2024-02-01T21:06:35.662254, lastUpdatedBy=null))
2024-02-17T12:15:03.073+01:00  INFO 17540 --- [school_system] [nio-8080-exec-2] c.s.service.impl.LessonServiceImpl       : Lesson: Lesson(id=7, modul=DEUTSCH, startAt=2023-11-14T10:30, units=2.0, lessonType=PRESENTS_SCHOOL, description=gsd hrt hgre erh, students=[User(id=25, title=HERR, firstName=Stefan, lastName=Polhill, phoneNumber=+86 (937) 393-8812, comment=null, [email protected], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, verified=false, hasProvidedAllInfo=true, lastUpdatedTimestamp=2024-02-01T21:05:55.710775, lastUpdatedBy=null, deleted=false, roles=[Role(id=2, name=ROLE_TEACHER, description=)], address=Address(id=25, street=Atwood, hausnummer=9614, city=Axili, state=null, country=China, postal=0, lastUpdatedTimestamp=2024-02-01T21:06:35.662254, lastUpdatedBy=null)), User(id=75, title=HERR, firstName=Fayre, lastName=Dudney, phoneNumber=+358 (355) 234-9484, comment=null, [email protected], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, verified=false, hasProvidedAllInfo=true, lastUpdatedTimestamp=2024-02-01T21:05:55.710775, lastUpdatedBy=null, deleted=false, roles=[Role(id=2, name=ROLE_TEACHER, description=)], address=Address(id=75, street=Quincy, hausnummer=748, city=Kristinestad, state=null, country=Finland, postal=41270, lastUpdatedTimestamp=2024-02-01T21:06:35.662254, lastUpdatedBy=null))], teacher=Teacher(qualifications=Waelchi-Waelchi, education=Cremin Group, students=[]), lastUpdatedTimestamp=2024-02-03T14:10:30.160628, lastUpdatedBy=null)
2024-02-17T12:15:03.128+01:00  WARN 17540 --- [school_system] [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23505
2024-02-17T12:15:03.128+01:00 ERROR 17540 --- [school_system] [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: duplicate key value violates unique constraint "student_lessons_pkey"
  Detail: Key (student_id, lesson_id)=(25, 7) already exists.
2024-02-17T12:15:03.156+01:00 ERROR 17540 --- [school_system] [nio-8080-exec-2] c.s.exception.CustomExceptionHandler     : could not execute statement [ERROR: duplicate key value violates unique constraint "student_lessons_pkey"
  Detail: Key (student_id, lesson_id)=(25, 7) already exists.] [insert into student_lessons (lesson_id,student_id,deleted) values (?,?,false)]; SQL [insert into student_lessons (lesson_id,student_id,deleted) values (?,?,false)]; constraint [student_lessons_pkey]

org.springframework.dao.DataIntegrityViolationException: could not execute statement [ERROR: duplicate key value violates unique constraint "student_lessons_pkey"
  Detail: Key (student_id, lesson_id)=(25, 7) already exists.] [insert into student_lessons (lesson_id,student_id,deleted) values (?,?,false)]; SQL [insert into student_lessons (lesson_id,student_id,deleted) values (?,?,false)]; constraint [student_lessons_pkey]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:290) ~[spring-orm-6.1.3.jar:6.1.3]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:241) ~[spring-orm-6.1.3.jar:6.1.3]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:565) ~[spring-orm-6.1.3.jar:6.1.3]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:669) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:419) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.3.jar:6.1.3]
    at com.school_system.service.impl.LessonServiceImpl$$SpringCGLIB$$0.updateLesson(<generated>) ~[classes/:na]
    at com.school_system.controller.LessonController.updateLesson(LessonController.java:42) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.3.jar:6.1.3]

我尝试更改 ManyToMany CasCadeType 和 FetchType 我已经添加了这个方法,但它没有帮助 addStudent(Student s)、removeStudent(Student s)、addLesson(Lesson l) 和 removeLesson(Lesson l)

spring hibernate spring-data-jpa duplicates boot
1个回答
0
投票

您定义了唯一的键约束。以下对(student_id、lesson_id)构成表中的唯一键。

根据定义:UNIQUE 约束确保列中的所有值都不同。

您似乎正在尝试插入值 (25, 7),这违反了定义的唯一约束。

尝试插入不违反唯一约束的数据,它应该可以工作。

© www.soinside.com 2019 - 2024. All rights reserved.