Spring:无法理解级联删除实体

Spring: Trouble understanding cascade deleting entities

我完全不知道在 Spring 中级联删除实体的正确方法。这是我项目中的一些实体 classes:

@Entity
@Getter
@Setter
public class User {

    @OneToMany(cascade = CascadeType.ALL)
    private Set<Ticket> tickets;

}


@Entity
@Getter
@Setter
public class Ticket {

    @ManyToOne
    @JoinColumn
    private User user;

    @ManyToOne
    @JoinColumn
    private Screening screening;

}


@Entity
@Getter
@Setter
public class Screening {
 
    @OneToMany(cascade = CascadeType.ALL)
    private Set<Ticket> tickets;

}

我需要能够删除一个用户,它会级联删除与其关联的所有票证,但是当我这样做时,我得到一个异常,显示“无法添加或更新子行:一个外国键约束失败”。

我试过向票证实体添加@PreRemove 方法:

@Entity
@Getter
@Setter
public class Ticket {

    @ManyToOne
    @JoinColumn
    private User user;

    @ManyToOne
    @JoinColumn
    private Screening screening;

    @PreRemove
    public void preRemove() {
        user.getTickets().remove(this);
        setUser(null);
        screening.getTickets().remove(this);
        setScreening(null);
    }

}

但是当我这样做时,我得到了一个 ConcurrentModificationException。我还尝试将 onDelete 方法添加到票务服务 class:

@Service
@Transactional
public class TicketServiceImpl implements TicketService {

    private final UserService userService;

    public TicketServiceImpl(UserService userService) {
        this.userService = userService;
    }

    @Override
    protected void onDelete(Ticket ticket) {
        User user = ticket.getUser();
        user.getTickets.remove(ticket);
        ticket.setUser(null);
        Screening screening = ticket.getScreening();
        screening.getTickets().remove(ticket);
        ticket.setScreening(null);
    }

    @Override
    public void purchaseTicket(Long userId, TicketForm ticketForm) {
        // depends on UserService to fetch User by id
    }

}


@Service
@Transactional
public class UserServiceImpl implements UserService {

    private final TicketService ticketService;

    public UserServiceImpl(TicketService ticketService) {
        this.ticketService = ticketService;
    }

    @Override
    protected void onDelete(User user) {
        // I'm required to implement the cascade myself in order to 
        // have the onDelete method called for Tickets
        ticketService.deleteAll(user.getTickets());
    }

}

上述方法存在两个问题。第一个问题是,如果我想调用 onDelete 方法,现在我不得不自己实现级联,而不是让 Hibernate 来实现。其次,如果我希望能够删除用户并单独删除票证,现在这两个服务之间存在循环依赖关系。

我尝试四处寻找解决此问题的答案,但找不到答案。我认为 @PreRemove 方法肯定是执行此操作的“正确”方法,但从我发现的情况来看,ConcurrentModificationException 显然是它的一个常见问题,最后一种方法是我发现推荐的方法。但当然,最后一种方法有其自身的问题。我完全被难住了,非常感谢任何帮助!

所以目标是在删除用户时触发票据的级联删除,并保持双向映射的两侧同步。首先,我建议摆脱双向映射并切换到单向映射。 ?

  1. 您将不必跟踪关系的双方
  2. 将实体序列化为 json 时不会遇到无限递归问题

如前所述,在双向映射中,您必须跟踪关系的两边。做到这一点的方法是使用同步方法,比如你的onDelete。通常,这些方法直接放在映射的一侧。例如:

@Entity
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Ticket> tickets;
}
@Entity
@Getter
@Setter
public class Screening {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "screening", cascade = CascadeType.ALL)
    private Set<Ticket> tickets;

    public void removeTicket(Ticket ticket) {
        tickets.remove(ticket);
        ticket.setScreening(null);
    }
}

这样您就可以简化您的服务 类 并执行类似的操作:

@Service
public class UserServiceImpl implements UserService {
    private final EntityManager entityManager;

    public UserServiceImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Transactional
    @Override
    public void onDelete(User user) {
        user.getTickets().forEach(ticket -> {
            ticket.getScreening().removeTicket(ticket);
        });

        entityManager.remove(user);
    }
}

请注意,我还添加了 mappedBy,以防止休眠创建交集 table。如果没有此属性,hibernate 会为 onDelete 调用生成以下 SQL:

Hibernate: delete from user_tickets where user_id=?
Hibernate: delete from ticket where id=?
Hibernate: delete from ticket where id=?
Hibernate: delete from user where id=?

如你所见,hibernate 额外创建了一个table,因为他不知道谁是关系的所有者。用 mappedBy 代替:

Hibernate: delete from ticket where id=?
Hibernate: delete from ticket where id=?
Hibernate: delete from user where id=?

为简洁起见,我没有在两个实体中添加 addTicket 方法,在用户实体中添加 removeTicket 方法。