Let's take into consideration the following code snippet:
public class EmployeeServiceImpl implements EmployeeService
{
#PersistenceContext(unitName="EmployeeService")
EntityManager em;
public void assignEmployeeToProject(int empId, int projectId)
{
Project project = em.find(Project.class, projectId);
Employee employee = em.find(Employee.class, empId);
project.getEmployees().add(employee);
employee.getProjects().add(project);
}
}
please note that this example refers to Transaction Scoped,container managed Entity Manager.
from javacodegeeks:
By the end of 2nd line in the method both project and employee
instance are managed. At the end of the method call, the transaction
is committed and the managed instances of person and employee get
persisted. Another thing to keep in mind is that when the transaction
is over, the Persistence Context goes away.
I really cannot understand how does the entity manager knows the method is closed and implicitly commits the transaction...
Am I missing something here ?
Should we commit the transaction explicitly ?
Yes you are missing something:
Your service isn't just an instance of EmployeeServiceImpl but of a proxy class which wraps EmployeeServiceImpl and every public method in it. And when your method exits the wrapping method takes over and commits the transaction. If you debug your application and set a breakpoint in assignEmployeeToProject() you can see very easily what is happening in the stacktrace.
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 have an entity Customer and Spring data interface CustomerRepository shown below:
public interface CustomerRepository extends JpaRepository<Customer,Long> {
Customer findCustomerByName(String name);
}
I save Customer object in the database and then update one field like this:
customerRepository.save(new Customer("John", "Smith"));
Customer john = customerRepository.findCustomerByName("John");
john.setSurname("Barton");
customerRepository.flush();
customerRepository.findAll().forEach(System.out::println);
I don't understand why it prints: Customer(id=1, name=John, surname=Smith).
As far as I know, Hibernate uses dirty checking mechanism to update entities in persistent state. So changed surname should be propagated to the database during end of transaction (but it does not - even if I separate this code into two #Transactional methods). Am I doing something wrong? Do I really need to save object manually after each change? Why surname field is not updated in the database?
#RunWith(SpringRunner.class)
#SpringBootTest
public class CustomerRepoTest {
#Autowired
private CustomerRepository customerRepository;
#Test
//NOTE: No #Transactional
public void testSaveFails() throws Exception {
customerRepository.save(new Customer("John", "Smith"));
Customer john = customerRepository.findCustomerByName("John");
john.setSurname("Barton");
customerRepository.flush();
customerRepository.findAll().forEach(System.out::println);
}
#Test
#Transactional
public void testSaveWorks() throws Exception {
customerRepository.save(new Customer("John", "Smith"));
Customer john = customerRepository.findCustomerByName("John");
john.setSurname("Barton");
//customerRepository.flush(); Flush is not necessary
customerRepository.findAll().forEach(System.out::println);
}
}
To explain:
Hibernate keeps a cache of objects it loaded during a transaction.
When a find method is executed, the id of a newly loaded object is compared to the id of the object in this cache, if found, the cached version is used.
This is why the version with #Transactional works.
Also, it explains why flush is not necessary - it only forces the values to be written before the transaction ends.
If you miss the #Transactional (assuming auto-commit on the underlying transaction, which is most likely the case):
You save the entity in one transaction
Reload it with findCustomerByName, but it immediately gets detached
you modify the detached entity - no save
you reload the entries in another transaction, and you don't see your update
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.
I have a service class the injects two JpaRepository classes: organizationRepository and stateRepository. In the service method I need to perform two transactions:
#Override
#Transactional
public Status createOrganization(#ResponseBody Organization organization) throws Exception {
Organization savedOrg = organizationRepository.save(organization);
int id = savedOrg.getOrgId();
State state = new State();
state.setOrgId(id);
state.setCode("MD");
State savedState = stateRepository.save(state);
.
.
.
This code isn't working and throwing a transaction error on my server. I also tried a saveAndFlush on the organizationRepository before trying to call the subsequent save for stateRepository. I realize I could also set the propagation properties but that didn't fix it either. The first save transaction always executes, but the second keeps failing. What can I do to solve?
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.