我完全不知道在 Spring 中級聯洗掉物體的正確方法。這是我專案中的一些物體類:
@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 方法:
@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 是它的一個常見問題,最后一種方法是我發現推薦的方法。但是,當然,最后一種方法有其自身的問題。我完全被難住了,非常感謝任何幫助!
uj5u.com熱心網友回復:
所以目標是在用戶被洗掉時觸發門票的級聯洗掉,并保持雙向映射的兩側同步。首先,我建議擺脫雙向映射并切換到單向映射。為什么?
- 您不必跟蹤關系的雙方
- 將物體序列化為 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,這可以防止休眠創建交集表。如果沒有此屬性,hibernate 會生成以下 SQL 進行onDelete呼叫:
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 創建了一個附加表,因為他不知道誰是關系的所有者。改為mappedBy:
Hibernate: delete from ticket where id=?
Hibernate: delete from ticket where id=?
Hibernate: delete from user where id=?
為簡潔起見,我沒有在物體和用戶物體中添加addTicket方法。removeTicket
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/463969.html
