如果我有一些实例要在数据库中插入或更新,例如:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EqualsAndHashCode
@Table(name="user_purchases")
public class UserPurchase implements Serializable, Persistable<String> {
@Id
@Column(name="id")
@NotNull
private String id; // an UUID
@Column(name="user_id")
@NotNull
private String userId; // in user_info: "sub"
/**
* Seconds since epoch: when the last purchase happened.
*/
@Column(name="last_date")
private Date lastBuyDate;
@Transient
private boolean isNewObject;
// Add UUID before persisting
@PrePersist
public void prepareForInsert() {
this.id = UUID.randomUUID().toString();
}
@Override
public boolean isNew() {
return isNewObject;
}
// This part for Persistable is not required, because getId(), by accident,
// is the getter for the "id" field and returns a String.
//@Override
//public getId() {
// return id;
//}
}
我们知道id
是代理ID,将在持久化之前生成。 userId
在数据库中是唯一的。
要了解有关界面Persistable<ID>
的更多信息,请选中this answer。
现在,当我们有一个不带id的实例时,userId
可能会在数据库中复制或无法复制,并且无法判断我们是否在数据库中持久化或更新。
我想在每一个持久/更新之前保存全表扫描,因此我尝试在DateIntegrationViolationException
的第一次尝试之后捕获repository.save(entity)
。
@Transactional(rollbackFor = DataIntegrityViolationException.class)
public UserPurchase saveUserPurchase(UserPurchase purchase) throws RuntimeException {
UserPurchase saved = null;
try {
saved = repository.saveAndFlush(purchase);
log.debug("UserPurchase was saved/updated with id {}, last buy time: {}", saved.getId(),
DateTimeUtil.formatDateWithMilliPart(saved.getLastBuyDate(), false));
} catch (DataIntegrityViolationException e) {
log.info("Cannot save due to duplication. Rolling back..."); // we don't distinguish userId and id duplication here.
UserPurchase oldPurchase = repository.findByUserId(purchase.getUserId()); // <--------- here we cannot proceed
if (oldPurchase != null) {
purchase.setId(oldPurchase.getId()); // set the existent ID to do updating
purchase.setNewObject(false); // for Persistable<String>
saved = repository.saveAndFlush(purchase); // now should be updating
} else {
log.error("Cannot find ID by user id");
}
}
return saved;
}
这给了我以下错误:
ERROR: current transaction is aborted, commands ignored until end of transaction
因为我在一项事务中做了两件事,所以事务应该回滚。
好的,所以我抛出了异常,并尝试执行外部更新操作(因为当Spring看到抛出异常时,它会自动回滚,否则我会读):
@Transactional(rollbackFor = DataIntegrityViolationException.class)
public UserPurchase saveUserPurchase(UserPurchase purchase) throws RuntimeException {
UserPurchase saved = null;
try {
saved = repository.saveAndFlush(purchase);
log.debug("UserPurchase was saved/updated with id {}, last buy time: {}", saved.getId(),
DateTimeUtil.formatDateWithMilliPart(saved.getLastBuyDate(), false));
} catch (DataIntegrityViolationException e) {
log.info("Cannot save due to duplication. Rolling back..."); // we don't distinguish userId and id duplication here.
throw e; // or throw new RuntimeException(e); is the same
}
return saved;
}
在我打电话给save()
的地方:
try {
userPurchaseService.saveUserPurchase(purchase);
} catch (DataIntegrityViolationException e) {
log.info("Transaction rolled back, updating...");
// ... 1. select 2. getId() 3.setId() and save again
}
但是,它再次失败。
现在,对于Spring Data,我们没有EntityManager
至rollback()
。
现在该怎么办?每次插入/更新之前,我都必须做一本手册findByUserId()
吗?我的惰性选择方法在任何情况下都行不通?