Latest Spring Boot with JPA and Hibernate: I'm struggling to understand the relationship between transactions, the persistence context and the hibernate session and I can't easily avoid the dreaded no session lazy initialization problem.
I update a set of objects in one transaction and then I want to loop through those objects processing them each in a separate transaction - seems straightforward.
public void control() {
List<> entities = getEntitiesToProcess();
for (Entity entity : entities) {
processEntity(entity.getId());
}
}
#Transactional(value=TxType.REQUIRES_NEW)
public List<Entity> getEntitiesToProcess() {
List<Entity> entities = entityRepository.findAll();
for (Entity entity : entities) {
// Update a few properties
}
return entities;
}
#Transactional(value=TxType.REQUIRES_NEW)
public void processEntity(String id) {
Entity entity = entityRepository.getOne(id);
entity.getLazyInitialisedListOfObjects(); // throws LazyInitializationException: could not initialize proxy - no Session
}
However, I get a problem because (I think) the same hibernate session is being used for both transactions. When I call entityRepository.getOne(id) in the 2nd transaction, I can see in the debugger that I am returned exactly the same object that was returned by findAll() in the 1st transaction without a DB access. If I understand this correctly, it's the hibernate cache doing this? If I then call a method on my object that requires a lazy evaluation, I get a "no session" error. I thought the cache and the session were linked so that's my first confusion.
If I drop all the #Transactional annotations or if I put a #Transactional on the control method it all runs fine, but the database commit isn't done until the control method completes which is obviously not what I want.
So, I have a few questions:
How can I make the hibernate session align with my transaction scope?
What is a good pattern for doing the separation transactions in a loop with JPA and declarative transaction management?
I want to retain the declarative style (i.e. no xml), and don't want to do anything Hibernate specific.
Any help appreciated!
Thanks
Marcus
Spring creates a proxy around your service class, which means #Transactional annotations are only applied when annotated methods are called through the proxy (where you have injected this service).
You are calling getEntitiesToProcess() and processEntity() from within control(), which means those calls are not going through proxy but instead have the transactional scope of the control() method (if you aren't also calling control() from another method in the same class).
In order for #Transactional to apply, you need to do something like this
#Autowired
private ApplicationContext applicationContext;
public void control() {
MyService myService = applicationContext.getBean(MyService.class);
List<> entities = myService.getEntitiesToProcess();
for (Entity entity : entities) {
myService.processEntity(entity.getId());
}
}
Related
I am writing an application with Spring 5 and Hibernate. There's a service that receives an entity in a different state than currently persisted. It performs some processing and saves the entity to database (using Spring CrudRepository).
public void saveEntity(Entity entity) {
ProcessingStatus processingStatus = doSomeProcessing(entity);
if (processingStatus == ProcessingStatus.SUCCESS) {
entity.setProcessingStatus(ProcessingStatus.SUCCESS);
repository.save(entity);
} else {
Entity originalEntity = repository.findById(entity.getId());
originalEntity.setProcessingStatus(ProcessingStatus.FAILURE);
repository.save(originalEntity);
}
}
So if processing was successful we're just marking entity with success status and save it. Otherwise any changes that comes with new version of entity shouldn't be applied. So original entity from repository is retrieved, its status is changed and then it is saved.
The problem is that line
Entity originalEntity = repository.findById(entity.getId());
actually retrieves already modified object (probably cached by Hibernate?), not the original one from database. So originalEntity has the same set of attributes as entity (received as method argument). What would be the best way to retrieve original object in such case?
I suppose this saveEntity method is annotated with #Transactional.
For this reason, also if not explicitly stored to with repository.save(entity), you have your update object because you are in the same hibernate session.
You can detach your modified entity and then you will read the clean one (WARNING! if your entity has not been already persisted you will get a null)
To detach and Entity you have to inject PersistenceContext in your repository
#Repository
public class EntityRepository {
#PersistenceContext
private EntityManager entityManager;
public void detachOrderItem(Object object) {
entityManager.detach(object);
}
}
And then you have to call detach before findById
} else {
entityRepository.detach(entity);
Entity originalEntity = repository.findById(entity.getId());
originalEntity.setProcessingStatus(ProcessingStatus.FAILURE);
repository.save(originalEntity);
}
Another simpler solution could be to run public void saveEntity(Entity entity) out of a Transaction (only nested method could live a a transaction)
UPD 1: Upon further research I think the following information may be useful:
I obtain datasource through JNDI lookup on WildFly 9.0.2, then 'wrap' it into in instance of HikariDataSource (e. g. return new HikariDataSource(jndiDSLookup(dsName))).
the transaction manager that ends up being used is JTATransactionManager.
I do not configure the transaction manager in any way.
ORIGINAL QUESTION:
I am experiencing an issue with JPA/Hibernate and (maybe) Spring-Boot where DB changes introduced in a transactional method of one class called from a transactional method of another class are committed even though the changes in the caller method are rolled back (as they should be).
Here are my transactional services
StuffService:
#Service
#Transactional(rollbackFor = IOException.class)
public class StuffService {
#Inject private BarService barService;
#Inject private StuffRepository stuffRepository;
public Stuff updateStuff(Stuff stuff) {
try {
if (null != barService.doBar(stuff)) {
stuff.setSomething(SOMETHING);
stuff.setSomethingElse(SOMETHING_ELSE);
return stuffRepository.save(stuff);
}
} catch (FirstCustomException e) {
logger.error("Blah", e);
throw new SecondCustomException(e.getMessage());
}
throw new SecondCustomException("Blah 2");
}
// other methods
}
and BarService:
#Service
#Transactional
public class BarService {
#Inject private EntityARepository entityARepository;
#Inject private EntityBRepository entityBRepository;
/*
* updates existing entity A and persists new entity B.
*/
public EntityA doBar(Stuff stuff) throws FirstCustomException {
EntityA a = entityARepository.findOne(/* some criteria */);
a.setSomething(SOMETHING);
EntityB b = new EntityB();
b.setSomething(SOMETHING);
b.setSomethingElse(SOMETHING_ELSE);
entityBRepository.save(b);
return entityARepository.save(a);
}
// other methods
}
EntityARepository and EntityBRepository are very similar Spring-Boot repositories defined like this:
public interface EntityARepository extends JpaRepository<EntityA, Long>{
EntityA findOne(/* some criteria */);
}
FirstCustomException extends Throwable
SecondCustomException extends RuntimeException
Stuff entity is versioned, and every once in a while it is concurrently updated by StuffService.updateStuff(). In that case changes to one of the stuff instances are rolled back, as expected, but everything that happens in the barService.doBar() ends up being committed.
This puzzles me quite a lot since transaction propagation on both methods should be REQUIRED (the default one) and both methods belong to different classes, hence #Transactional should apply for both.
I did see Transaction is not completely rolled back after server throws OptimisticLockException1
But it did not really answer my question.
Can anyone please give me an idea of what's going on?
Thank you.
This isn't a 'nested' transaction - these services are operating in completely independent transactions. If you want the rollback of one to affect the other, you need to have them take part in the same transaction rather than start its own.
Or if your issue is that there is a problem with the version of 'stuff' passed into the doBar method and you want it verified, you will need to do something with the stuff instance that would cause an optimistic lock check, and so result in an exception if it is stale. see EntityManager.lock
My Spring application is layered as Bean, Service and DAO. All the #Transactional annotations are in service layer.
This is the pseudo code in one particular scenario.
UserBean.java
saveUser() {
userService.manageUser();
}
UserServiceImpl.java
#Transactional
public void manageUser() {
userDAO.createUser();
userDAO.updateParentUser();
}
UserDAOImpl.java
createUser() {
//insert user record in database
}
updateParentUser() {
//update parent user's database record
}
In my save user test case, the update parent user operation can fail in some cases due to primary key violation which is kind of expected.
As the #Transactional annotation is implemented in service class, this violation exception will be notified in bean class only.
What is the option to get this PK violation notification in my service class?
[Then I can handle it from there in a different business process.]
If I add a new method in service class and call manageUser() from there the #Transactional annotation will not work properly. This is due to the limitation/property of AOP. Spring expects external call to #Transactional methods.
The create/update won't be committed until you return from the #Transactional method. If the create/update is flushed to the database before that then you may get the exception within the method, but in your case it's not being flushed until the commit.
You can force the create/update to be flushed before the commit. You don't say whether you're using Hibernate or JPA, but session.flush() or entityManager.flush() should do the trick.
Use programmatic transaction management and handle exceptions in try catch block
Introduce a delegate class and do manageUser in a transaction there:
#Transactional(REQUIRED)
public void manageUser() {
try{
delegate.manageUser();
} catch (Exception ex ) {
//handle
}
}
And in delegate class
#Transactional(REQUIRES_NEW)
public void manageUser() {
}
Instead of Spring proxy based AOP I moved to AspectJ approach. This gives me the flexibility to make my manageUser() method call from another method of same service class and I can handle the exception there.
My project is Web app with Spring and Hibernate. I need to perform some routine operation on the data returned by my DAO layer and transaction is committed and session is closed. I try to use aspects with annotations like this:
#AfterReturning(pointcut = "execution(* com.dao..*.*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result){
processResult(result);
}
or using
#Around("execution(* com.dao..*.*(..))")
but in either event I got Hibernate exceptions like this:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
Off course everything works if I just apply that same operation in my service:
public void serviceMethod(Object param) {
Object result = dao.getObject(param);
processResult(result);
}
'Cause I need to use processResult against every DAO method's result I am reluctant to use the latter approach and would be grateful for some advice on how to use either aspects or some other alternative way on doing so....
I am upgrading a working project from Spring2+Hibernate3 to Spring3+Hibernate4. Since HibernateTemplate and HibernateDAOSupport have been retired, I did the following
Before (simplified)
public List<Object> loadTable(final Class<?> cls)
{
Session s = getSession(); // was calling the old Spring getSession
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
closeSession(s);
return objects;
}
Now (simplified)
#Transactional(propagation=Propagation.REQUIRED)
public List<Object> loadTable(final Class<?> cls)
{
Session s = sessionFactory.getCurrentSession();
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
return objects;
}
I also added the transaction annotation declaration to Spring XML and removed this from Hibernate properties
"hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext"
The #Transactional annotation seems to have worked as I see this in the stacktrace
at com.database.spring.DatabaseDAOImpl$$EnhancerByCGLIB$$7d20ef95.loadTable(<generated>)
During initialization, the changes outlined above seem to work for a few calls to the loadTable function but when it gets around to loading an entity with a parent, I get the "collection with cascade="all-delete-orphan" was no longer referenced" error. Since I have not touched any other code that sets/gets parents or children and am only trying to fix the DAO method, and the query is only doing a sql SELECT, can anyone see why the code got broken?
The problem seems similar to Spring transaction management breaks hibernate cascade
This is unlikely problem of Spring, but rather issue with your entity handling / definition. When you are using deleteOrphans on a relation, the underlying PersistentSet MUST NOT be removed from the entity itself. You are allowed only to modify the set instance itself. So if you are trying to do anything clever within your entity setters, that is the cause.
Also as far as I remember there are some issues when you have deleteOrphans on both sides of the relation and/or load/manipulate both sides within one session.
Btw. I don't think "hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext" is necessary. In our project, this is the only configuration we have:
#Bean
public LocalSessionFactoryBuilder sessionFactoryBuilder() {
return ((LocalSessionFactoryBuilder) new LocalSessionFactoryBuilder(
dataSourceConfig.dataSource()).scanPackages(ENTITY_PACKAGES).
setProperty("hibernate.id.new_generator_mappings", "true").
setProperty("hibernate.dialect", dataSourceConfig.dialect()).
setProperty("javax.persistence.validation.mode", "none"));
}
#Bean
public SessionFactory sessionFactory() {
return sessionFactoryBuilder().buildSessionFactory();
}
The issue was with Session Management. The same block of transactional code was being called by other modules that were doing their own session handling. To add to our woes, some of the calling modules were Spring beans while others were written in direct Hibernate API style. This disorganization was sufficient work to keep us away from moving up to Hibernate 4 immediately.
Moral of the lesson (how do you like that English?): Use a consistent DAO implementation across the entire project and stick to a clearly defined session and transaction management strategy.