There is an inconsistency when using Hibernate 5 via multiple APIs.

SpringSessionContext is constructed by Hibernate, therefore this.sessionFactory is always a plain SessionFactoryImpl instance. There is no way to change that, that I can see.

Spring Data JPA repositories and JpaTransactionManager use the EntityManagerFactory interface instead, which is usually a LocalContainerEntityManagerFactoryBean proxy.

If you try to use both (e.g. to fetch an entity and then use LobHelper) then they cannot see each others' sessions via TransactionSynchronizationManager, because they're keying by different objects. You then end up with two of them, and exceptions because one has no active transaction. SpringSessionContext is essentially non-functional.

The correct SessionFactoryImpl instance can be obtained via entityManagerFactory.unwrap(SessionFactoryImpl.class), but it's not clear how to know whether that should happen. Perhaps just instanceof SessionFactory?

Some background of my specific use-case

Comment From: rstoyanchev

@spring-projects/spring-data could someone please comment on whether this is a supported scenario, or if there are alternatives or improvements to be made?

Comment From: OrangeDog

tl;dr: If the purpose of SpringSessionContext is to allow sharing session context between Hibernate and Spring Data, then it doesn't work,

Comment From: schauder

I'm currently confused what the problem is supposed to be. Despite what the OP says on the Stackoverflow question they seem to run code in multiple threads. Which at least at first glance would explain the problem.

I'd at the very least would need a complete reproducer to give a more helpful answer.

Comment From: OrangeDog

The SO question has a complete reproduction. The multiple threads are not part of the problem, but I wasn't sure at the time. This should be sufficient to reproduce in a Spring Boot application with Hibernate and Spring Data JPA repositories:

@Autowired private MyRepository repository;
@Autowired private SessionFactory sessionFactory;

@Transactional
public void test(Object id) {
    MyEntity entity = repository.findById(id).orElseThrow();
    entity.setBlobField(sessionFactory.getCurrentSession().getLobHelper().createBlob(new byte[]{});
}

It will fail because getCurrentSession() returns a new session instead of the one already being used, due to the mismatched keys in TransactionSynchronizationManager as explained above.

Comment From: OrangeDog

@spring-projects/spring-data does my reduced example clarify things?

Comment From: odrotbohm

I lean towards agreeing that it would be nice if this worked. That said, the more JPA-idiomatic way would be to just inject the EntityManager and use its ….unwrap(…) method to get access to Hibernates Session. Is there any reason you don't do that instead?

Comment From: OrangeDog

@odrotbohm that is what I'm doing. It doesn't work because TransactionSynchronizationManager doesn't do that.

That is the bug I'm reporting. If you use the EntityManagerFactory/SessionFactory (they are the same thing in Hibernate5) then you get a new Session instead of the one Spring already created, because TransactionSynchronizationManager used the wrong key.

Comment From: odrotbohm

If you do, you do not lay that out in the code examples you show. The SO question is using a HibernateTransactionManager pulling the SessionFactory out of the EMF. What I am suggesting to is to avoid any reference to Hibernate's Session… in the transaction infrastructure, inject an EntityManager in your client code instead of a SessionFactory and rather obtain the Session(not the factory!) from the EM.

You dealing with the factory instances directly is likely to cause separate instances created at some point. I propose leaving the instance creation to the container entirely and only consuming the transaction-bound instances directly.

Comment From: OrangeDog

obtain the Session (not the factory!) from the EM.

Ah I see what you mean. This does work:

@Autowired private MyRepository repository;
@PersistenceContext private EntityManager entityManager;

@Transactional
public void test(Object id) {
    MyEntity entity = repository.findById(id).orElseThrow();
    entity.setBlobField(entityManager.unwrap(Session.class).getLobHelper().createBlob(new byte[]{});
}

You dealing with the factory instances directly is likely to cause separate instances created at some point.

The point of SpringSessionContext and TransactionSynchronizationManager is to make that work, but it doesn't.

It's good to find a workaround, but it should still be fixed (or the class deprecated/removed). I think unwrapping in the TransactionSynchronizationManager should do it.

Comment From: jhoeller

As indicator above, entityManager.unwrap(Session.class) is the correct approach when using JPA.

SpringSessionContext rather originates from Spring's native Hibernate 5 support and was never meant to be used with JPA to begin with, as indicated by its presence in the orm.hibernate5 package. While you can go down to native Hibernate API from given EntityManager(Factory) handlers, the SessionFactory.getCurrentSession() mechanism was only ever meant to be used with native Hibernate bootstrapping and not with JPA resource management even from Hibernate's own side. With JPA, the primary resource is always the EntityManager itself, and you should only ever go down to the Hibernate Session interface from there.

Note that native Hibernate 5 support and the entire orm.hibernate5 package are being phased out in favor of general JPA usage with Hibernate 6.x. Only HibernateJpaDialect and HibernateJpaVendorAdapter remain for strategic Hibernate support in Spring.