JPA - охват транзакции несколькими вызовами метода JpaRepository - PullRequest
0 голосов
/ 16 декабря 2018

Я использую 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 открывать новые транзакции для каждого (предопределенного) метода, используемого из интерфейса репозитория??Или, если это невозможно, как «заставить» менеджера транзакций повторно использовать транзакцию, открытую в сервисе, чтобы сделать возможным полный откат?

1 Ответ

0 голосов
/ 26 июля 2019

Привет Я нашел эту документацию, которая выглядит, поможет вам:

https://www.logicbig.com/tutorials/spring-framework/spring-data/transactions.html

Следующий пример взят с предыдущего веб-сайта:

@Configuration
**@ComponentScan
@EnableTransactionManagement**
public class AppConfig {
 ....
}

Тогда мыможно использовать такие транзакции:

@Service
public class MyExampleBean{

**@Transactional**

public void saveChanges() {
    **repo.save(..);
    repo.deleteById(..);**
    .....
}

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...