Данные Spring - «ОШИБКА: текущая транзакция прервана»: как откатить / зафиксировать транзакцию, когда перехватывается DataIntegrityViolationException? - PullRequest
0 голосов
/ 29 октября 2018

Если у меня есть несколько экземпляров для вставки или обновления в БД, например:

@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() перед каждой вставкой / обновлением? Мой подход ленивого выбора не сработает ни при каких обстоятельствах?

...