Использование транзакций при объединении RepositoryRestResource с другим хранилищем - PullRequest
0 голосов
/ 29 июня 2018

У меня есть простой JpaRepository, помеченный @RepositoryRestResource:

@RepositoryRestResource
public interface ItemRepository extends JpaRepository<Item, UUID> { }

Всякий раз, когда что-то меняется в базе данных, я хочу обновить файл. Я делаю это, используя RepositoryEventHandler:

@Component
@RepositoryEventHandler
public class ItemRepositoryEventHandler {

    @HandleAfterCreate
    @HandleAfterSave
    @HandleAfterDelete
    public void itemChanged(Item item) {
        writeToFile();
    }
}

Что я хочу сделать, если при записи содержимого в файл произошла ошибка, то необходимо выполнить откат базы данных.

Я пытался добавить аннотацию @Transactional к ItemRepository, но это не сработало. Отладка показала, что RepositoryRestResource выполняет три шага: отправка событий BeforeXXX, сохранение в базе данных, затем отправка событий AfterXXX. Он использует транзакцию только на этапе сохранения, а не одну из всех трех.

Таким образом, я не вижу способа использовать транзакцию для всей операции, и единственная альтернатива, которую я вижу, - это не использовать @RepositoryRestResource, а реализовать веб-слой вручную, а затем использовать службу, которая использует транзакцию в обоих репозиториях. Есть ли более простой способ?

1 Ответ

0 голосов
/ 02 июля 2018

Один из подходов - реализовать вашу бизнес-логику с помощью настраиваемого контроллера и службы. Но этот способ нейтрализует «преимущества» Spring Data REST.

Другой вариант (на мой взгляд, это более естественно для SDR) - использовать опубликованные события из совокупных корней . В этом случае вы должны расширить свои сущности с AbstractAggregateRoot и реализовать метод, который будет публиковать некоторые «события». Затем вы сможете обработать это событие (с помощью @EventListener) в той же транзакции в процессе сохранения вашей сущности. Например:

@Entity
public class Order extends AbstractAggregateRoot {
    //...

    public void registerItems(List<Item> items) {
        this.registerEvent(new RegisterItemsEvent(this, items));
    }
}
@Getter
@RequiredArgsConstructor
public class RegisterItemsEvent {
    private final Order order;
    private final List<Item> items;
}
@RequiredArgsConstructor
@Component
public class EventHandler {

    private final ItemRepo itemRepo;

    @EventListener
    @Transactional(propagation = MANDATORY)
    public void handleRegisterItemsEvent(RegisterItemsEvent e) {
        Order order = e.getOrder();
        List<Item> items = e.getItems();

        // update items with order - skipped...

        itemRepo.saveAll(items);
    }
}

Пример использования:

@Component
@RepositoryEventHandler
public class OrderEventHandler {

    @BeforeCreate
    public void handleOrderCreate(Order order) {

        // prepare a List of items - skipped...

        order.registerItems(items);        
    }
}

Когда SDR сохраняет Order, тогда он генерирует RegisterItemsEvent, который обрабатывается handleRegisterItemsEvent методом вашего EventHandler, который сохраняет подготовленные элементы в той же транзакции (мы используем параметр propagation = MANDATORY аннотации @Transaction чтобы убедиться, что транзакция присутствует).

Дополнительная информация: Публикация событий домена из совокупных корней

ОБНОВЛЕНО

Что касается вашей конкретной задачи, вы можете создать класс ItemChangedEvent:

@Getter
@RequiredArgsConstructor
public class ItemChangedEvent {
    private final Item item;
}

Реализация метода markAsChanged в Item сущности:

@Entity
public class Item extends AbstractAggregateRoot {
    //...

    public void markAsChanged() {
        this.registerEvent(new ItemChangedEvent(this));
    }
}

Когда item изменяется, вы помечаете его как «измененный»:

@Component
@RepositoryEventHandler
public class ItemRepositoryEventHandler {

    @BeforeCreate
    @BeforeSave
    @BeforeDelete
    public void itemChanged(Item item) {
        item.markAsChanged();
    }
}

И записать его в файл в обработчике ItemChangedEvent в той же транзакции:

@Component
public class EventHandler {

    @EventListener
    @Transactional(propagation = MANDATORY)
    public void handleItemChangedEvent(ItemChangedEvent e) {
        Item item = e.getItem();
        writeToFile(item);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...