Home > OS >  Spring @Transactional not working when Hibernate object with lazy loading coming from another transa
Spring @Transactional not working when Hibernate object with lazy loading coming from another transa

Time:05-18

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.

CodePudding user response:

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:


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().

  • Related