Я использую SpringBoot 2.x с SpringData-JPA для доступа к базе данных через CrudRepository.
По сути, я хотел бы вызвать методы CrudRepository для обновления или сохранения данных.В одном случае использования я хотел бы удалить более старые записи из базы данных (для краткости этого примера предположим: удалить все записи из таблицы) перед тем, как вставить новый элемент.Если по какой-либо причине сохранение нового элемента завершается неудачно, операция удаления должна быть отменена.
Однако, как представляется, основная проблема заключается в том, что новые транзакции открываются для каждого метода, вызываемого из CrudRepository.Хотя транзакция была открыта методом из вызывающей службы.Я не смог заставить методы репозитория использовать существующую транзакцию.
Getting transaction for [org.example.jpatrans.ChairUpdaterService.updateChairs]
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Я пробовал использовать другое Распространение.(ТРЕБУЕТСЯ, ПОДДЕРЖИВАЕТСЯ, ОБЯЗАТЕЛЬНО) на разных методах (сервис / репозиторий) безрезультатно.Изменение метода @Transactional
аннотация на @Transactional(propagation = Propagation.NESTED)
звучало так, что это бы просто помогло, но это не помогло.
JpaDialect does not support savepoints - check your JPA provider's capabilities
Можно ли добиться ожидаемого поведения, не с помощьюEntityManager напрямую?
Я также хотел бы избежать необходимости использовать собственные запросы.Я что-то упустил из виду?
Для демонстрации я создал очень сжатый пример.Полный пример можно найти по адресу https://gitlab.com/cyc1ingsir/stackoverlow_jpa_transactions
Вот основные (еще более упрощенные) детали:
Сначала я определил очень простую сущность:
@Entity
@Table(name = "chair")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Chair {
// Not auto generating the id is on purpose
// for later testing with non unique keys
@Id
private int id;
@Column(name = "legs", nullable = false)
private Integer legs;
}
Соединение с базой данных осуществляется через CrudRepository:
@Repository
public interface ChairRepository extends CrudRepository<Chair, Integer> {
}
Это вызывается из другого компонента (основные методы здесь updateChairs
и doUpdate
):
@Slf4j
@Service
@AllArgsConstructor
@Transactional
public class ChairUpdater {
ChairRepository repository;
/*
* Initialize the data store with some
* sample data
*/
public void initializeChairs() {
repository.deleteAll();
Chair chair4 = new Chair(1, 4);
Chair chair3 = new Chair(2, 3);
repository.save(chair4);
repository.save(chair3);
}
public void addChair(int id, Integer legCount) {
repository.save(new Chair(id, legCount));
}
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
@Transactional
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
}
Цель, которую я хочу достичь, демонстрируется этими двумя тестовыми примерами:
@Slf4j
@RunWith(SpringRunner.class)
@DataJpaTest
@Import(ChairUpdater.class)
public class ChairUpdaterTest {
private static final int COUNT_AFTER_ROLLBACK = 3;
@Autowired
private ChairUpdater updater;
@Autowired
private ChairRepository repository;
@Before
public void setup() {
updater.initializeChairs();
}
@Test
public void positiveTest() throws UpdatingException {
updater.updateChairs(3, 10);
}
@Test
public void testRollingBack() {
// Trying to update with an invalid element
// to force rollback
try {
updater.updateChairs(3, null);
} catch (Exception e) {
LOGGER.info("Rolled back?", e);
}
// Adding a valid element after the rollback
// should succeed
updater.addChair(4, 10);
assertEquals(COUNT_AFTER_ROLLBACK, repository.findAll().spliterator().getExactSizeIfKnown());
}
}
Обновление:
Кажется, что это работает, если репозиторий не расширен из CrudRepository или JpaRepository, а изпростой репозиторий, явно определяющий все необходимые методы.Для меня это кажется обходным путем, а не решением проблемы.
Кажется, что вопрос сводится к следующему: возможно ли запретить SimpleJpaRepository открывать новые транзакции для каждого (предопределенного) метода, используемого из интерфейса репозитория??Или, если это невозможно, как «заставить» менеджера транзакций повторно использовать транзакцию, открытую в сервисе, чтобы сделать возможным полный откат?