I am trying to understand the behavior of transaction propagation using SpringJTA - JPA - Hibernate.
Essentially I am trying to update an entity. To do so I have written a test method where I fetch an object using entity manager (em) find method ( so now this object is manged object). Update the attributes of the fetched object. And then optionally make a call to service layer(service layer propagation=required) which is calling em.merge
Now I have three variations here :
Test method has no transactional annotation. Update the attributes
of the fetched object and make no call to service layer.
1.1. Result level 1 cache doesn't gets updated and no update to DB.
Test method has no transactional annotation. Update the attributes of the fetched object. Call the service layer.
2.1. Result level 1 cache and DB gets updated.
Test method has Transnational annotation which could be any of the following. Please see the table below for Propagation value at the test method and the outcome of a service call.
(service layer propagation=required)
So to read the above table, the row 1 says if the Test method has transaction propagation= REQUIRED and if a service layer call is made then the result is update to Level 1 cache but not to the DB
Below is my test case
#Test
public void testUpdateCategory() {
//Get the object via entity manager
Category rootAChild1 = categoryService.find(TestCaseConstants.CategoryConstant.rootAChild1PK);
assertNotNull(rootAChild1);
rootAChild1.setName(TestCaseConstants.CategoryConstant.rootAChild1 + "_updated");
// OPTIONALLY call update
categoryService.update(rootAChild1);
//Get the object via entity manager. I believe this time object is fetched from L1 cache. As DB doesn't get updated but test case passes
Category rootAChild1Updated = categoryService.find(TestCaseConstants.CategoryConstant.rootAChild1PK);
assertNotNull(rootAChild1Updated);
assertEquals(TestCaseConstants.CategoryConstant.rootAChild1 + "_updated", rootAChild1Updated.getName());
List<Category> categories = rootAChild1Updated.getCategories();
assertNotNull(categories);
assertEquals(TestCaseConstants.CategoryConstant.rootAChild1_Child1,categories.get(0).getName());
}
Service Layer
#Service
public class CategoryServiceImpl implements CategoryService {
#Transactional
#Override
public void update(Category category) {
categoryDao.update(category);
}
}
DAO
#Repository
public class CategoryDaoImpl {
#Override
public void update(Category category) {
em.merge(category);
}
}
Question
Can someone please explain why does REQUIRED, REQUIRES_NEW, and NESTED doesn't lead to insertion in the DB?
And why absence of transaction annotation on Test case lead to insertion in the DB as presented in my three variations?
Thanks
The effect you're seeing for REQUIRED, NESTED, and REQUIRES_NEW is due to the fact that you're checking for updates too early
(I'm assuming here that you check for db changes at the same moment when the test method reaches the assertions, or that you roll the test method transaction back somehow after executing the test)
Simply enough, your assertions are still within the context created by the #Transactional annotation in the test method. Consequently, the implicit flush to the db has not been invoked yet.
In the other three cases, the #Transactional annotation on the test method does not start a transaction for the service method to join. As a result, the transaction only spans the execution of the service method, and the flush occurs before your assertions are tested.
Related
I have a problem with accessing data inside a running transaction when the data came from another (supposedly closed) transaction. I have three classes like below, with an entity (called MyEntity) which also has another entity connected via Hibernate mapping called "OtherEntity" which has lazy loading set to true. Notice how I have two transactions:
One to load a list of entities
And a new transaction for each new item
However, this fails inside the loop with "No session" even though I have an active transaction inside the method (TransactionSynchronizationManager.isActualTransactionActive is true).
I don't really understand the problem. Seems to me the object which is used by the second transaction(s) "belong" to the first one even though the first transaction was supposed to finish? Maybe its a race condition?
#Service
class ServiceA {
#Autowired
private ServiceB serviceB;
#Autowired
private ServiceC serviceC;
public void test() {
List<MyEntity> allEntities = serviceC.loadAllEntities(); //First transaction ran, getting a list of entities, but due to lazy loading we havent loaded all the data
for(MyEntity i : allEntities) {
serviceB.doOnEach(i); //On each element a new transaction should start
}
}
}
#Service
class ServiceB {
#Transactional
public void doOnEach(MyEntity entity) {
System.out.println(TransactionSynchronizationManager.isActualTransactionActive()); //true, therefore we have an active transaction here
OtherEntity other = entity.getSomeOtherEntity(); //Want to load the "lazy loaded" entity here
//"No Session" exception is thrown here
}
}
#Service
class ServiceC {
#Autowired
private MyRepository myRepository;
#Transactional
public List<MyEntity> loadAllEntities() {
return myRepository.findAll();
}
}
A solution would be to re-load the "MyEntity" instance inside the "doOnEach" method, but that seems to me like a sub-optimal solution, especially on big lists. Why would I reload all the data which is already supposed to be there?
Any help is appreciated.
Obviously the real code is a lot more complicated than this but I have to have these kind of separate transactions for business reasons, so please no "solutions" which re-write the core logic of this. I just want to understand whats going on here.
After the call to loadAllEntities() finishes the Spring proxy commits the transaction and closes the associated Hibernate Session. This means you cannot have Hibernate transparently load the non-loaded lazy associations anymore without explicitly telling it to do so.
If for some reason you really want your associated entities to be loaded lazily the two options you have is either use Hibernate.initialize(entity.getSomeOtherEntity()) in your doOnEach() method or set the spring.jpa.open-in-view property to true to have the OpenSessionInViewInterceptor do it for you.
Otherwise it's a good idea to load them together with the parent entity either via JOIN FETCH in your repository query or via an Entity Graph.
References:
https://www.baeldung.com/spring-open-session-in-view
https://www.baeldung.com/hibernate-initialize-proxy-exception
To clarify further:
Spring creates a transaction and opens a new Session (A) before entering the loadAllEntities() method and commits/closes them upon returning. When you call entity.getSomeOtherEntity() the original Session (A) that loaded entity is gone (i.e. entity is detached) but instead there's a new Session (B) which was created upon entering the doOnEach() transactional method. Obviously Session (B) doesn't know anything about entity and its relations and at the same time the Hibernate proxy of someOtherEntity inside entity references the original Session (A) and doesn't know anything about Session (B). To make the Hibernate proxy of someOtherEntity actually use the current active Session (B) you can call Hibernate.initialize().
I am new to Java and Spring. I am learning spring jdbc connectivity using JdbcTemplate. I wrote the below code.
Controller
service.importInsuranceEstimates(insuranceEstimates);
Service class
public void importInsuranceEstimates(List<String[]> insuranceEstimates) {
for (String[] insuranceEstimate: insuranceEstimates) {
repository.insertInsuranceEstimate(insuranceEstimate);
}
}
Repository class
public void insertInsuranceEstimate(String[] insuranceEstimate) {
jdbcTemplate.update("insert into some_table values (?, ?, ?)", insuranceEstimate[0], insuranceEstimate[1], insuranceEstimate[2]);
}
Assume that after inserting few records, the next insert statement failed. In this case, I would like the previously inserted records to be rolled back.
So I decorated the repository method with #Transactional(propagation = Propagation.REQUIRED). But still I don't see the previous records being rolled back if the insert failed.
Then I understood that the rollback is not done because each insert is done in its own transaction and committed before the repository is returned.
So then I decorated the service method also with the same annotation #Transactional(propagation = Propagation.REQUIRED). But no success. The records are still not being rolled back.
Then, I understood that I have to insert all the records under the same transaction. So I changed my repository signature to
public void importInsuranceEstimates(List<String[]> insuranceEstimates)
then service class
repository.importInsuranceEstimates(insuranceEstimates);
In the repository class I am using batchUpdate instead of using the regular update.
What I understood is
1. queries related to a single transaction must be run/executed under a single transaction.
2. annotation based rollback is not possible using JdbcTemplate. We have to get the connection and play with setAutoCommit(boolean) method.
Are my observations right?
Also, in some cases one would like to make multiple insert/update/delete db calls for different tables from service layer. How to make multiple db calls from service layer under the same transaction. Is it even possible?
For example I want to write a code to transfer money from an account to another. So I have to make two db calls, one to debit the send and one to credit the receiver. In this case I would write something like below
Service class
repository.debitSender(id, amount);
repository.creditReceiver(id, amount);
Since I cannot run these two method calls under the same transaction, I have to modify my service class to
repository.transferMoney(senderId, receiverId, amount)
and do the two updates under the same transaction in the repository like below
public void transferMoney(String senderId, String receiverId, double amount) {
jdbcTemplate.getConnection().setAutoCommit(false);
// update query to debit the sender
// update query to credit the receiver
jdbcTemplate.getConnection().setAutoCommit(true);
}
What if I do not want to use transferMoney method and instead split the method into two - debitSender and creditReceiver and call these two methods from the service class under the same transaction with JdbcTemplate?
I have an List which contains say 4 DTOs. I am performing some processes on each of the DTOs present in my list. If suppose for one the DTO, any exception comes then all the transactions are rolled back (even if the process is success for other 3 DTOs).
My code looks like this :
#Transactional
public void processEvent(List<MyObject> myList){
myList.forEach(dto -> process(dto));
}
public void process(MyObject dto){
//some code which calls another class marked as #Transactional
// and save the data processed to database
}
I want to perform these processes for each DTO on a sepearte thread such that exception encountered in one thread does not rollbacks transaction for all the DTOs.
Also is there a way to process these DTOs one by one on different threads so that data consistency is maintained ?
Simply move the transactional to the method called with the dto, plus I am not sure if it is needed a transaction for dto. This looks as a controller which should not have any transactional annotaions. In the service once you change the dto to entity and are ready to save it you may put the anotation. Furthermore if you are simply calling the repository's save method you do not need to be in transaction as save method has the annotation in the repository.
public void processEvent(List<MyObject> myList){
myList.forEach(dto -> process(dto));
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void process(MyObject dto){
//some code which calls another class marked as #Transactional
// and save the data processed to database
}
And one last advice do not put #Transactional on classes, except if they have the readOnly parameter set to true. Then you can put #Transactional on the methods that perform any CRUD operations.
consider entity as user, it having some fields. here i am using jpa callback functions to update user information of last update information. in test class I want to write junit method to evaluate whether these call back methods are working or not/ not only for functionality testing and code coverage purpose also. but if I follow below approach i am getting same time everytime, can anyone help on this.
#Entity
public class User {
// user setter and getter methods
#preUpdate
public void preUpdateFunction() {
this.lastUpdateDate = new Date();
}
#prePersist
public void prePersistFunction() {
// setting some user properties
preUpdateFunction();
}
}
// please ignore this configuration and annotations setup, I tested my class spring configuration working perfectly there is no issue with spring configuration.
#SpringConfiguration
#JpaTransactional
public class TestClass {
#Autowired
UserDao userDao; // userDao implements JPA Repository
// I am worrying about this functionality only
#Test
public void saveUpdateTest() {
User user = userDao.save(new User(constructor arguments));
user = userDao.findOne(user.getId());
user.setName("Hello"); // here updating user object with existing property
User updatedUser = userDao.save(user);
assertEquals(user.getLastUpdateDate().getTime(), updatedUser.getLastUpdateDate().getTime());
// assertion is failing, everytime i am getting same Time for two values. even I added //Thread.sleep(1000) between save and update operations, still i am getting same values.
}
}
Short answer
You need to call saveAndFlush
User updatedUser = userDao.saveAndFlush(user);
Long answer
From JPA spec (JSR 338 JPA 2.1):
The PrePersist and PreRemove callback methods are invoked for a given entity before the
respective EntityManager persist and remove operations for that entity are executed.
The PreUpdate and PostUpdate callbacks occur before and after the database update operations to entity data respectively. These database operations may occur at the time the entity state is updated or
they may occur at the time state is flushed to the database (which may be at the end of the transaction).
#PrePersist is invoked when entityManager persist operation is executed. Tricky part is the execution is usually delayed until next flush operation or transaction commit (hibernate default config). Generally, it depends on flush configuration and on jpa implementation.
Same also applies to #PreUpdate. In addition, JPA spec says it more specifically, it might occur when entity state is updated or when flushed to DB.
Related links
https://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf
https://thorben-janssen.com/spring-data-jpa-save-saveandflush-and-saveall
I Have a method using #Transactional annotation, and inside this method, i have one that persists one entity, and the next ones uses this persisted entity that is not yet persisted, because the method using #Transactional dont finished.
What is the best approach to do this? I Think about REQUIRED_NEW, but when this is a new transactional, if the external transaction fails, it will not fail all.
Thanks !!
Paulo
#Override
#Transactional
public Catalog updateCatalog(CatalogPrice catalog, Long id) {
CatalogEntity catalogEntity = CatalogEntity.findSingle(id);
Catalog catalog = catalogHand.updateCatalogPrice(catalog);
catalogEntity.sendToQueue(catalog);
return catalog;
}
Within the same transaction boundary - any changes you made (CREATE or UPDATE) will be visible. I believe you need to call flush() method between the method calls.
// Create code
entityManager.flush(); // If you use JPA, or it will be session.flush() for hibernate
// Update code goes here
Persistence context is flushed only when you call flush() explicitly or when you search for the entity or when the transaction commits. Only in these cases the changes you made will be available.