Если у меня есть несколько экземпляров для вставки или обновления в БД, например:
@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
является суррогатным идентификатором и будет сгенерирован перед сохранением. И userId
уникален в БД.
Чтобы узнать больше об интерфейсе Persistable<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, у нас нет от EntityManager
до rollback()
.
Что теперь делать? Должен ли я делать руководство findByUserId()
перед каждой вставкой / обновлением? Мой подход ленивого выбора не сработает ни при каких обстоятельствах?