从父实体中删除时,Spring数据JPA @PreRemove ConcurrentModificationException

时间:2019-04-26 14:24:38

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

我有一种情况,参与者可以注册 课程

基本上,我具有以下实体配置(省略了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);
    }

}

然后我可以从我的视图模型中删除注册或课程(我也删除了不必要的内容):

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

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

问题:

  • 如果删除注册,则需要@PreRemove函数,以便删除引用。没有这个,删除将被忽略(没有错误,只是被忽略)
  • 如果我删除课程,则必须删除@PreRemove函数,否则我会得到ConcurrentModificationException(显然...)

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

这里最好的方法是什么?

我在 Spring Boot 1.0.4 (和 spring-boot-starter-data-jpa )中使用了 Java 11

编辑

管理者/存储库或以这种方式定义(与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> {
    ...
}

EDIT2

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

我从实体中删除了@PreRemove方法,然后在deleteRegistration方法中删除了这样的引用(我曾尝试过但导致failed to lazily initialize a collection of role异常):< / p>

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

我只是将父母设为null,我不在乎,因为它将被删除...

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

因此,现在我也可以删除课程而无需触发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);
}

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

2 个答案:

答案 0 :(得分:1)

您的存储库设置不正确。您需要Registration的复合PK,并且需要了解双向映射实际上仅用于查询。此外,CourseParticipate中的双向映射提出了挑战,因为默认情况下,通过ManyToOne实体的Registration关系是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;
}

是您的基本EntitiesRegistrationRepository需要一个附加查询。

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

答案 1 :(得分:0)

好的解决方案非常简单。

我确实需要从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);
}

诀窍在于,在尝试删除注册之前,我还必须保存课程实体。

这有效:

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

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

@PreRemove可以完成工作,但是那样一来,我现在还可以删除课程而无需触发ConcurrentModificationException