Почему @Transactional не откатывает один коммит, если другой не удался? - PullRequest
1 голос
/ 02 октября 2019

Итак, у меня есть класс:

@Service
public class MyService {

  @Autowired
  private RepositoryA repoA;

  @Autowired
  private RepositoryB repoB;

  @Transactional
  public void storeEntity(SomeEntity e) {
    repoA.save(e);

    OtherEntity o = doSomethingWithEntity(e);

    repoB.save(o);
  }
}

Мой метод storeEntity делает два сохранения в два разных источника данных. Я ожидаю, что в случае сбоя сохранения в repoB или doSomethingWithEntity произойдет откат repoA.save(e).

Я хочу написать небольшие тесты, обеспечивающие такое поведение:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceForTransactionTest {
  @Autowired
  private MyService subject;

  @Autowired
  private RepositoryA repoA;

  @MockBean
  private RepositoryB repoB;

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    subject.storeEntity(e);

    // then
    assertThat(repoA.count()).isEqualTo(0);
  }
}

Это не будет работать, потому что исключение выдается и не проходит тест. Когда я окружаю вызов try / catch, мое утверждение не выполняется с сообщением, что repoA имеет 1 запись. Как с этим справиться?

Я тоже пробовал это:

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    try {
      subject.storeEntity(e);
    } catch (Exception e) {
      // some output here
    }
    // then
    assertThat(repoA.count()).isEqualTo(0);
  }

Утверждение не удалось. Я пробовал также это:

  @Test
  public void repoBShouldNotHaveEntries() {
    // given
    when(repoB.save(any())).thenThrow(new IllegalStateException("Something wrong with db"));
    assertThat(repoB.count()).isEqualTo(0);

    // when
    SomeEntity e = ...
    subject.storeEntity(e);
  }

  @After
  public void tearDown() {
    // then
    assertThat(repoA.count()).isEqualTo(0);
  }
}

Также не удается. 1 запись найдена, но я ожидаю, что @Transactional должен быть откат.

1 Ответ

1 голос
/ 03 октября 2019

Когда вы управляете транзакциями через Spring, вы фактически используете абстракцию. Для одного источника данных (обычно называемого локальной транзакцией ресурса) запуск / принятие / откат транзакций будет работать как положено, но если используются два разных источника данных, вам потребуется менеджер транзакций, способный выполнять распределенные транзакции, такие как Bitronix или Atomikos Essentials (в мире открытого исходного кода). В среде сервера приложений EE эта возможность предоставляется самим сервером. Здесь - очень старая статья, которую стоит прочитать (в ней используется одна база данных и один брокер обмена сообщениями, участвующий в распределенной транзакции, но концепция та же самая - ключ - несколько ресурсов). Для примера конфигурации для Bitrionix и Spring проверьте this out.

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