Hibernate JPA in Spring Boot insert exiting Entity

62 Views Asked by At

I'm encountering an issue with updating the Lesson entity in my application, which involves a Many-to-Many relationship between Student and Lesson entities. When I update the Lesson information, the operation succeeds without any problems. However, when I attempt to add a new Student to the Lesson, Hibernate tries to insert the student again, resulting in a SQL error:

SQL Error: 0, SQLState: 23505 ERROR: duplicate key value violates unique constraint "student_lessons_pkey"

Could you please provide assistance with resolving this issue? Below are the relevant update functions for reference and Entities.

@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);
                });
    }

This is my Lesson Entity


@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);
    }

}

And this is my Student Entity

@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);
    }
}

And here is the Logs Info

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]

I have tried to change the ManyToMany CasCadeType and the FetchType I have add this methods and it dose not helped addStudent(Student s), removeStudent(Student s), addLesson(Lesson l) and removeLesson(Lesson l)

DB table for studnet_lessons: enter image description here

EDIT:: I have solved the Problem by deleting all associated Studnets and reinsert them after that

        lesson.getStudents().clear();
        Lesson lesson1 =lessonRepository.saveAndFlush(lesson);
        lesson1.setStudents(lesson.getStudents());
        lessonRepository.save(lesson);
        return Mapper.toLessonResponse(lesson);
1

There are 1 best solutions below

10
Hadi Kattan On

You have a unique key constraint defined. The following pair (student_id, lesson_id) forms a unique key in your table.

By definition: The UNIQUE constraint ensures that all values in a column are different.

It seems that you're trying to insert the values (25, 7) and that violates the unique constraint defined.

Try inserting data that doesn't violate the unique constraint, it should work.