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 显然是它的一个常见问题,最后一种方法是我发现推荐的方法。但当然,最后一种方法有其自身的问题。我完全被难住了,非常感谢任何帮助!
所以目标是在删除用户时触发票据的级联删除,并保持双向映射的两侧同步。首先,我建议摆脱双向映射并切换到单向映射。 ?
- 您将不必跟踪关系的双方
- 将实体序列化为 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
方法。
我完全不知道在 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 显然是它的一个常见问题,最后一种方法是我发现推荐的方法。但当然,最后一种方法有其自身的问题。我完全被难住了,非常感谢任何帮助!
所以目标是在删除用户时触发票据的级联删除,并保持双向映射的两侧同步。首先,我建议摆脱双向映射并切换到单向映射。
- 您将不必跟踪关系的双方
- 将实体序列化为 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
方法。