java - 从父实体中删除时,Spring data JPA @PreRemove ConcurrentModificationException

标签 java spring-boot jpa spring-data-jpa

我有一个参与者可以注册类(class)的案例。

基本上我有以下实体配置(省略了 getter 和 setter 以及其他无用的属性):

@Entity
@Table(name = "course")
public class Course {

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
    private Set<Registration> registrations;

}

@Entity
@Table(name = "participant")
public class Participant {

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
    private Set<Registration> registrations;

}

@Entity
@Table(name = "registration")
public class Registration {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "course_id")
    private Course course;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "participant_id")
    private Participant participant;

    @PreRemove
    private void removeRegistrationFromHolderEntities() {
        course.getRegistrations().remove(this);
        participant.getRegistrations().remove(this);
    }

}

然后我可以从我的 View 模型中删除注册或类(class)(我还删除了不必要的东西):

@Command
public void deleteRegistration(Registration reg) {
    registrationMgr.delete(reg);
}

@Command
public void deleteCourse(Course crs) {
    courseMgr.delete(crs);
}

问题:

  • 如果我删除注册,我需要 @PreRemove函数,这样我就可以删除引用。如果没有这个,删除将被忽略(没有错误,只是被忽略)
  • 如果我删除类(class),我必须删除 @PreRemove功能否则我得到一个 ConcurrentModificationException (显然...)

我也无法删除 deleteRegistration 中的引用方法(而不是 @PreRemove ),因为参与者注册是延迟加载的(会引发 failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session 异常)。

这里最好的方法是什么?

我将 Java 11Spring Boot 1.0.4(以及 spring-boot-starter-data-jpa)结合使用。

编辑:

管理器/存储库或以这种方式定义(与 registrationparticipant 相同),因此它应该是事务性的(我的主类上没有 @EnableTransactionManagement 但不应该需要它,因为我不需要不要使用存储库之外的事务):

@Transactional
@Component("courseMgr")
public class CourseManager {

    @Autowired
    CourseRepository courseRepository;

    public void saveOrUpdate(Course course) {
        courseRepository.save(course);
    }

    public void delete(Course course) {
        courseRepository.delete(course);
    }
}

public interface CourseRepository extends CrudRepository<Course, Long> {
    ...
}

编辑2:

我想我找到了一个非常简单的解决方案:

我已经删除了 @PreRemove方法从实体,然后而不是像这样删除 deleteRegistration 中的引用方法(我尝试过但导致 failed to lazily initialize a collection of role 异常):

@Command
public void deleteRegistration(Registration reg) {
    reg.getCourse().getRegistrations().remove(reg);
    reg.getParticipant.getRegistrations().remove(reg);
    registrationMgr.delete(reg);
}

我只是将parents设置为null,我不在乎,因为它会被删除......

@Command
public void deleteRegistration(Registration reg) {
    reg.setCourse(null);
    reg.setParticipant(null);
    registrationMgr.delete(reg);
}

所以现在我也可以删除类(class)而不触发 ConcurrentModificationException@PreRemove .

EDIT3:我的错,上面的解决方案没有删除注册(仍然没有错误,但没有任何反应)。我以这个结束,它终于起作用了:

@Command
public void deleteRegistration(Registration reg) {
    // remove reference from course, else delete does nothing
    Course c = getRegistration().getCourse();
    c.getRegistrations().remove(getRegistration());
    courseMgr.saveOrUpdate(c);

    // delete registration from the database
    registrationMgr.delete(reg);
}

无需删除参与者的引用...

最佳答案

您的存储库设置不正确。您需要一个用于注册的复合 PK,并且您需要了解双向映射实际上仅用于查询。此外,CourseParticipate 中的双向映射存在挑战,因为通过 Registration 实体的 ManyToOne 关系是 默认情况下,FetchType.EAGER。有了所有 cascadefetch 注释,您就要求 JPA 提供复杂的组合,而您似乎还没有将其全部解决。从基础知识开始,一定要打印您的 SQL 语句,如果您想尝试从 JPA 中获得更多技巧,请从那里开始。

@Entity
@Data
public class Course {
    @Id
    private Integer id;
    private String name;
}

@Entity
@Data
public class Participant {
    @Id
    private Integer id;
    private String name;
}

@Entity
@Data
public class Registration {
    @EmbeddedId
    private RegistrationPK id;

    @ManyToOne
    @MapsId("participant_id")
    private Participant participant;

    @ManyToOne
    @MapsId("course_id")
    private Course course;
}

@Embeddable
@Data
public class RegistrationPK implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer course_id;
    private Integer participant_id;
}

是您的基本实体RegistrationRepository 需要额外的查询。

public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
    Set<Registration> findByCourse(Course c);
}

并在示例中使用所有这些:

@Override
public void run(String... args) throws Exception {
    create();
    Course c = courseRepo.getOne(1);
    Set<Registration> rs = read(c);
    System.out.println(rs);
    deleteCourse(c);
}

private void create() {
    Course c1 = new Course();
    c1.setId(1);
    c1.setName("c1");
    courseRepo.save(c1);

    Participant p1 = new Participant();
    p1.setId(1);
    p1.setName("p1");
    participantRepo.save(p1);

    Registration r1 = new Registration();
    r1.setId(new RegistrationPK());
    r1.setCourse(c1);
    r1.setParticipant(p1);
    registrationRepo.save(r1);
}

private Set<Registration> read(Course c) {
    return registrationRepo.findByCourse(c);
}

private void deleteCourse(Course c) {
    registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
    courseRepo.delete(c);
}

关于java - 从父实体中删除时,Spring data JPA @PreRemove ConcurrentModificationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55869659/

相关文章:

java - 无法签署 Mac OS X 应用程序

java - Spring LDAP 和 Spring Boot 配置

java - 努力存储生成的盐以进行 aes 加密/解密

java - 无法将字符串导出为 .xlsx 文件中的超链接 - Java 8 和 POI 3.17

spring-boot - Spring Boot 中 Thymeleaf 的几个模板位置

Spring数据JPA java.lang.IllegalArgumentException : Not an entity

hibernate - 如何使用JPA Criteria Builder编写查询(包括子查询和存在查询)

java - JPA/Hibernate 将动态列映射为对象列表

java - Axis .ConfigurationException : SAXException: Fatal Error: URI=null Line=56: XML document structures must start and end

Eclipse 中的 java 文件被奇怪的字符弄乱了