6

I'm working on a project where I want to use a very specific transaction propagation strategy. The database has two sets of tables, active and archive. Each set of tables is implemented with its own Entities and Interfaces that extend CrudRepository<T, ID>. The goal is to have a set of entities inserted into the active tables and to have all the data in the active tables inserted into the archive tables in a single transaction. The entities in the tables are not the same, and will have different table structures.

Given two separate repositories similar to the form

public interface FooRepository extends CrudRepository<Foo, Integer>
public interface FooArchiveRepository extends CrudRepository<FooArchive, Integer>

and an implementation similar to

@Autowired FooRepository fooRepo;
@Autowired FooArchiveRepository fooArchiveRepo;
@Autowired BarService barService;
List<Foo> newData = barService.doThing();
fooRepo.saveAll(newData);
// fooData is a list of FooArchive from earlier
fooArchiveRepo.saveAll(fooData);

The goal is to have fooRepo.saveAll(newData) and fooArchiveRepo.saveAll(fooData) be guaranteed to execute in a single database transaction. Spring's default transaction propagation of Required has different Transactional methods execute in the same physical transaction - does that apply to all Transactional methods within the Application Context, or only on a per-entity basis?

Leo
  • 283
  • 4
  • 10
  • Even though they have different table structures, consider a `before update trigger` (assuming the db supports it). This simplifies the client and reduces complexity when multiple types of clients can update. – Andrew S Oct 02 '18 at 16:59

1 Answers1

7

wrap the save calls into a new method and annotate the method with @Transactional

@Transactional
public void saveAll(){
  fooRepo.saveAll(newData);
  // fooData is a list of FooArchive from earlier
  fooArchiveRepo.saveAll(fooData);
}

by default (PROPAGATION_REQUIRED) it will execute the inner transactions into the same transaction. Please see https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/images/tx_prop_required.png

Adina Rolea
  • 2,031
  • 1
  • 7
  • 16
  • Seems obvious now that I read it, thanks! Do you know how I could verify or test the transaction propagation? – Leo Oct 02 '18 at 17:56
  • 1
    You can create a SpringBootTest and autowire the service with the method saveAll(). Make your second save fail, by adding a constraint (ex. you can add @NotNull on a field and try to save it with null value to force the fail). After that, you can assert that the data from the first save was not updated. – Adina Rolea Oct 02 '18 at 18:37
  • 1
    What is the performance impact on having multiple transactions – pannu Jul 02 '19 at 10:33
  • For this case, it wasn't a question of performance impact, but transaction atomicity. The goal was that if the transaction to add data to one table failed, the other table should not have data added. If both sets of table interactions happen in a single commit, then that goal is reached. – Leo Dec 13 '19 at 18:14
  • I'm not getting the same behavior that you mentioned. I've separated to a saveAll() as you mentioned. But in the console I've noticed that I got a different transaction for each repo save. Any thoughts? – Mauricio Toledo Apr 19 '22 at 14:13
  • This might have changed in the years since original posting, but when I try to implement this, I get this warning: @Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime which sort of defeats the purpose of this approach. In [this][1] accepted answer, the point is made to avoid self-invoking public methods, but the `@Transactional` does require `public`. [1]: https://stackoverflow.com/questions/23931698/spring-transactional-annotation-self-invocation – onouv Jul 07 '23 at 08:45