Why in new Transaction I couldn't read inner entity? - spring

I would like to open a new transaction and use all the changes which I have made in first transaction. But from second transaction I couldn't read inner Entity
class A {
#Autowirde
private B b;
#Transactional
public test() {
ProgramRole programRole = new ProgramRole();
programRoleRepo.save(programRole);
Program program = new Program();
program.setProgramRole(programRole);
programRepo.save(program); // Let say id is 1
Program p = programRepo.getOne(1);
p.getProgramRole() // Return 'programRole'
b.test();
}
}
#Component
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public void test() {
Program p = programRepo.getOne(1);
p.getProgramRole() // Return null. Why?
}
}

Hibernate holds the persistable state in memory, The process of synchronizing this state to the underlying DB is called flushing. When we use the save() method, the data associated with the save operation will not be flushed to the DB unless and until an explicit call to flush() or commit() method is made, In your case Transactional annotation will do that
as b.test method is calling from with in a transaction so your are getting null there as first transaction still in process(transaction will commit data when method execution competed).
For your use case, you need to use saveAndFlush instead of only save
ProgramRole programRole = new ProgramRole();
programRoleRepo.saveAndFlush(programRole);
Program program = new Program();
program.setProgramRole(programRole);
programRepo.saveAndFlush(program); /

Related

Hibernate LazyInitialization exception in console Spring Boot with an open session

I'm not sure if anyone has experienced this particular twist of the LazyInitialization issue.
I have a console Spring Boot application (so: no views - everything basically happens within a single execution method). I have the standard beans and bean repositories, and the typical lazy relationship in one of the beans, and it turns out that unless I specify #Transactional, any access to any lazy collection automatically fails even though the session stays the same, is available, and is open. In fact, I can happily do any session-based operation as long as I don't try to access a lazy collection.
Here's a more detailed example :
#Entity
class Path {
... `
#OneToMany(mappedBy = "path",fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#OrderBy("projectOrder") `
public List<Project> getProjects() {
return projects; `
}`
}
Now, the main method does something as simple as this:
class SpringTest {
... `
#Autowired
private PathRepository pathRepository;
void foo() {
Path path = pathRepository.findByNameKey("...");
System.out.println(path.getProjects()); // Boom <- Lazy Initialization exception}
}
Of course if I slap a #Transactional on top of the method or class it works, but the point is - why should I need that? No one is closing the session, so why is Hibernate complaining that thereĀ“s no session when there is one?
In fact, If I do:
void foo() {
System.out.println(entityManager.unwrap(Session.class));
System.out.println(entityManager.unwrap(Session.class).isOpen());
Path basic = pathRepository.findByNameKey("...");
System.out.println(entityManager.unwrap(Session.class));
System.out.println(entityManager.unwrap(Session.class).isOpen());
System.out.println(((AbstractPersistentCollection)basic.projects).getSession());
Path p1 = pathRepository.findByNameKey("....");
}
I get that the session object stays the same the whole time, it stays open the whole time, but the internal session property of the collection is never set to anything other than null, so of course when Hibernate tries to read that collection, in its withTemporarySessionIfNeeded method it immediately throws an exception
private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
SharedSessionContractImplementor tempSession = null;
if (this.session == null) {
if (this.allowLoadOutsideTransaction) {
tempSession = this.openTemporarySessionForLoading();
} else {
this.throwLazyInitializationException("could not initialize proxy - no Session");
}
So I guess my question would be - why is this happening? Why doesn't Hibernate store or access the session from which a bean was fetched so that it can load the lazy collection from it?
Digging a bit deeper, it turns out that the repository method executying the query does a
// method here is java.lang.Object org.hibernate.query.Query.getSingleResult()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
if (SharedEntityManagerCreator.queryTerminatingMethods.contains(method.getName())) {
...
EntityManagerFactoryUtils.closeEntityManager(this.entityManager); // <--- Why?!?!?
this.entityManager = null;
}
...
}
and the above closeEntityManager calls unsetSession on all collections:
SharedSessionContractImplementor session = this.getSession();
if (this.collectionEntries != null) {
IdentityMap.onEachKey(this.collectionEntries, (k) -> {
k.unsetSession(session);
});
}
But why?!
(Spring Boot version is 2.7.8)
So, after researching more it appears that this is standard behavior in Spring Boot - unless you use your own EntityManager, the one managed automatically by Spring is either attached to a #Transactional boundary, or opens and closes for each query.
Some relevant links:
Does Entity manager needs to be closed every query?
Do I have to close() every EntityManager?
In the end, I ended using a TransactionTemplate to wrap my code into a transaction without having to mark the whole class #Transactional.

spring boot how to handle fault tolerance in async method?

Suppose I have a caller to distribute work to multiple async tasks:
public class Caller{
public boolean run() {
for (int i = 0: i< 100; i++) {
worker.asyncFindOrCreate(entites[i]);
}
return true;
}
public class Worker{
#Autowired
Dao dao;
#Async
public E asyncFindOrCreate(User entity) {
return dao.findByName(entity.getName).elseGet(() -> dao.save(entity));
}
}
If we have 2 same entities:
with the synchronized method, the first one will be created and then the second one will be retrieved from the existing entity;
with async, the second entities might pass the findByName and go to save because the first entity hasn't been saved yet, which cause the save of the second entity throws unique identifier error.
Is there a way to add some fault tolerance mechanic to have some features like retry and skipAfterRetry, in particular for database operations.
In this special case you should convert your array to a map. Use the name property as a key, so there will be no duplicated entries.
However, if this method also can be called by multiple threads (ie. it's in a web-server) or there are multiple instances running it's still not fail-safe.
In generic, you should let the DB to check the uniqueness. There is no safest/easiest way to do that. Put the save method inside a try-catch block and check/handle the unique identifier exception.

Spring Data JPA: Find object after update is not always the updated object

We notice in our application that the find-method does not always return the latest object.
We have a repostiory like this (simplified):
public interface ObjRepo extends JpaRepository<Obj, Integer> {
#Secured({})
#RestResource(exported = false)
Obj findObjByObjToken(String ObjToken);
}
Simplified, we have a webservice doing something like this:
private static final Object lock = new Object();
private ModelAndView handle(HttpServletRequest request) {
synchronized (lock) {
String token = request.getParameter("token");
Obj obj = objRepo.findObjByObjToken(token);
obj.changeStuff();
objRepo.save(obj);
return ...;
}
}
The webservice is called multiple times at the same time; because of the synchronized lock, all calls will be handled serial.
I would expect the findObjByObjToken always to return the updated value, but very often it returns the old version.
We did some tests, and the break-off point seems to be around 600 ms.
How can we make sure the 'find' always returns the updated version?
(edit; we use Spring 1.3.3.RELEASE)

#cacheput is not updating the existing cache

I am working with Spring 4 and Hazelcast 3.2. I am trying to add a new record to existing cache with below code. somehow cache is not getting updated and at the same time I don't see any errors also. below is the code snippet for reference.
Note:- Cacheable is working fine, only cacheput is not working. Please throw light on this
#SuppressWarnings("unchecked")`enter code here`
#Transactional(readOnly = true, propagation = Propagation.REQUIRED)
#Cacheable(value="user-role-data")
public List<User> getUsersList() {
// Business Logic
List<User> users= criteriaQuery.list();
}
#SuppressWarnings("unchecked")
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
#CachePut(value = "user-role-data")
public User addUser(User user) {
return user;
}
I had the same issue and managed to solved it. The issue seemed to be tied to the transaction management.
Bascially updating the cache in the same method where you are creating or updating the new record does not work because the transaction was not committed. Here's how I solved it.
Service layer calls repo to insert user
Then go back to service layer
After the insert /update db call
In the service layer I called a refresh cache method
That returned the user data and this method has the cacheput annotation
After that it worked.
An alternative approach is you could use #CacheEvict(allEntries = true) on the method used to Save or Update or Delete the records. It will flush the existing cache.
Example:
#CacheEvict(allEntries = true)
public void saveOrUpdate(Person person)
{
personRepository.save(person);
}
A new cache will be formed with updated result the next time you call a #Cacheable method
Example:
#Cacheable // caches the result of getAllPersons() method
public List<Person> getAllPersons()
{
return personRepository.findAll();
}

DBUnit, Hibernate/JPA, DAO and flush

Goal
In order to test the create method of a DAO, I create an instance, insert it in database, flush the entity manager to update the database and then use dbunit to compare table using dataset.
Code
Here is the code, it uses Spring test, DBUnit and JPA (via Hibernate) :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"/WEB-INF/applicationContext-database.xml"})
public class MyEntityTest extends AbstractTransactionalJUnit4SpringContextTests {
#PersistenceContext
protected EntityManager em;
#Autowired
MyEntityDAO myEntityDAO;
#Test
public void createTest() {
// create the entity
MyEntity record = new MyEntity();
record.setData("test");
myEntityDAO.insertNew(record);
// flush to update the database
em.flush();
// get actual dataset from the connection
Session session = em.unwrap(Session.class);
Connection conn = SessionFactoryUtils.getDataSource(
session.getSessionFactory()).getConnection();
DatabaseConnection connection = new DatabaseConnection(conn);
ITable actualTable = connection.createTable("MY_ENTITY");
// get expected dataset
IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(getClass().getResource("dataset.xml"));
ITable expectedTable = expectedDataSet.getTable("MY_ENTITY");
// compare the dataset
Assertion.assertEquals(expectedTable, actualTable);
}
}
Problem
This code never ends, it seems to freeze (infinite loop ?) during this command:
ITable actualTable = connection.createTable("MY_ENTITY");
But if I comment the em.flush() block, the test ends (no freeze or infinite loop). In this case, the test fails because the database has not been updated after the insert.
Question
how can I test the create method of a DAO using a similar approach (compare dataset with dbunit) without having a freeze when calling dataset.getTable() ?
I found the solution. The problem was caused by the connection.
If I replace :
Session session = em.unwrap(Session.class);
Connection conn = SessionFactoryUtils.getDataSource(
session.getSessionFactory()).getConnection();
by
DataSource ds = (DataSource) applicationContext.getBean("dataSource");
Connection conn = DataSourceUtils.getConnection(ds);
All runs fine...
I don't understand why, so let me know in comments if you have any clue to help me understand that.

Resources