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

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.