Enterprise Java

Automatic save of managed JPA entities outside of transaction

Repositories and transactions in Spring go hand in hand. All database access in Spring should be run inside a transaction, and you typically have @Transactional somewhere to enforce this. However, this is not always necessary. For example, when using Spring Data your repositories use SimpleJPARepository for CRUD functionality. The SimpleJPARepository uses @Transactional so when you perform CRUD operations, transactions are already handled for you. This can give a wrong impression that you do not need to annotate your own classes with @Transactional, because this is only true if you know what you are doing.

Consider the following Spring Data based timeseries example for managing car rentals:

public CarRentalEntry createNewRental(Car car) {
  CarRentalEntry latestEntry = carRentalRepository.findByCarId(car.getId());
  latestCarRentalEntry.setEndDate(LocalDate.now());
  
  CarRentalEntry newEntry = new CarRentalEntry();
  newEntry.setCarId(car.getId())
  newEntry.setStartDate(LocalDate.now());
  newEntry.setEndDate(null);
  carRentalRepository.save(newEntry);
}

In the example above, the latest car rental entry for a particular car is fetched through the repository and ended. Then a new car rental entry is created and saved. This will work without @Transactional because carRentalRepository is a SimpleJPARepository which handles the transactions. Now consider the following where the saving is done before changing the end date of latestEntry:

public CarRentalEntry createNewRental(Car car) { 
   CarRentalEntry newEntry = new CarRentalEntry();
   newEntry.setCarId(car.getId())
   newEntry.setStartDate(LocalDate.now());
   newEntry.setEndDate(null);
   carRentalRepository.save(newEntry);
   
   CarRentalEntry latestEntry = carRentalRepository.findByCarId(car.getId());
   latestCarRentalEntry.setEndDate(LocalDate.now());
 }

Functionally, the method is exactly the same, but in this example only the save will be performed. Modification of latestEntry will not be saved to the database because there is no transaction! To make this approach work createNewRental() must be annotated with @Transactional. Any changes on a JPA managed entity are only automatically saved if they happen inside a transaction which is normal JPA behaviour. So the question is WHY did the first approach not require a transaction.

Actually it did. When latestEntry was fetches through the repository, it was added to the persistanceContext (a.k.a. Level 1 Cache) of JPAs entityManager. When the save() method was called, it flushed the persistanceContext upon transaction commit, which in turn had the side effect of also persisting the modified latestEntry. In the second example, persistanceContext did not have latestEntry upon calling save(). Because there is no transaction that commits when the methods completes, the changes are not flushed. By adding @Transactional, the persistanceContext is again flushed and the modification is written to the database. Note that the second example would also have worked without @Transactional by calling carRentalRepository.flush() because it too runs under a @Transactional.

The bottom line is that you should have control of your own transactions because as this case shows it is easy to make mistakes.

Lastly a tip when debugging Hibernate and managed entity issues. Good candidate classes to place a break point are:

  • org.springframework.orm.jpa.JpaTransactionManager
  • org.hibernate.jpa.internal.TransactionImpl.commit() persistence context that is to be flushed is typically found in TransactionImpl.entityManager.session.persistenceContext
Published on Java Code Geeks with permission by Qadeer Khan, partner at our JCG program. See the original article here: Automatic save of managed JPA entities outside of transaction

Opinions expressed by Java Code Geeks contributors are their own.

Qadeer Khan

Qadeer is a software engineer living in Oslo, Norway and has experience from large scale IT projects. Some of his interests are in creating quality code, performance tuning and machine learning. He is driven by opportunities of doing great work with great people.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Artem
Artem
1 month ago

Be careful people, that’s all wrong. Even variable names are broken. Session works differently and doesn’t persist anything without a transaction. Try yourself. And read this https://www.codepedia.org/jhadesdev/how-does-spring-transactional-really-work/

Back to top button