Using JPA with a Hibernate (5.4.16) implentation and LocalContainerEntityManagerFactoryBean
with JpaTransactionManager
(5.2.6) I'm trying to use a Hibernate Filter, which implies access to the newly created EntityManager
, to enable it.
I kept looking through the code but I can't seem to find a way to customize the EntityManager
.
I think something similar to AbstractJpaVendorAdapter#postProcessEntityManagerFactory
perhaps would be nice, if possible.
Comment From: jhoeller
When exactly would you like to enable this filter? Globally for the entire application configuration? Or per transaction but decoupled from actual data access? Within a transaction boundary, you could also set it programmatically on an injected EntityManager
before performing data access operations.
Comment From: andrei-ivanov
Hmm, The workaround that I currently have is a load time woven aspect:
@Aspect
public class EntityManagerAspect {
private boolean enabled = false;
@EventListener(ContextRefreshedEvent.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public void enable() {
enabled = true;
}
@AfterReturning(pointcut = "execution(* javax.persistence.EntityManagerFactory+.createEntityManager(..))", returning = "entityManager")
public void enableMultiTenantsFilter(EntityManager entityManager) {
if (enabled) {
// custom class obtained from a JAX-RS SecurityContext ThreadLocal
UserPrincipal principal = getUserPrincipal();
Filter filter = entityManager.unwrap(Session.class).enableFilter(TenantEntity.TENANTS_FILTER);
filter.setParameter(TenantEntity.TENANTS_FILTER_PARAM, tenant);
}
}
}
So, each time a new EntityManager
instance gets created, the filter gets enabled and queries get it applied (except native ones).
Comment From: jhoeller
For pre-/post-processing a transactional EntityManager
, you could use JpaDialect.begin/cleanupTransaction
... but admittedly that's not really meant for application-level concerns.
Based on your case case, a general JpaVendorAdapter.postProcessEntityManager
method would indeed make sense. I'll see what we can do for 5.2.7 there.
Comment From: jhoeller
It turns out that this is not totally straightforward in terms of capturing all relevant createEntityManager
calls, including our differentiation between transactional resources and application-level EntityManager
handles, requiring not only an addition to JpaVendorAdapter
but also a revision of the JpaTransactionManager
-EntityManagerFactoryInfo
interaction. I'll rather schedule this for 5.3 M1 instead, due in June as well.
Comment From: jhoeller
For post-processing transactional EntityManager
instances, you could also override the existing JpaTransactionManager.createEntityManagerForTransaction()
method, calling super
and then applying the filter to it. This would cover common access within a Spring-managed transaction.
Comment From: andrei-ivanov
I guess that's a possibility (that I didn't see). Now it would also be nice to play nice with Spring Boot, which I'm currently using 😀
@Bean
@ConditionalOnMissingBean
public PlatformTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
In this case, it doesn't seem that I would be losing that much by creating a sub-class though, I don't use any PlatformTransactionManagerCustomizer
Comment From: jhoeller
Good point about the convenience of customizing a pre-built instance there...
In addition to the JpaVendorAdapter
callback, I'm tempted to add a setEntityManagerCustomizer
method to LocalContainerEntityManagerFactoryBean
, typically to be used with a lambda expression against a given EntityManager, applied whenever JpaVendorAdapter.postProcessEntityManager
is being called as well. Would that help for your scenario? We could also add such a lambda-based customizer to JpaTransactionManager
, locally applied to transactional EntityManagers
only, allowing for differentiation between all EntityManagers
and just transactional EntityManagers
.
Comment From: andrei-ivanov
Yes, seems like a good idea, I just have to be able to inject them somehow when they are instantiated by Boot. Not sure for my specific case if I would need both of them, but I assume others might.
Comment From: jhoeller
I went with a convenient setEntityManagerInitializer(Consumer<EntityManager>)
method on JpaTransactionManager
as well as LocalContainerEntityManagerFactoryBean
(based on the newly introduced JpaVendorAdapter.postProcessEntityManager
and EntityManagerFactoryInfo.createNativeEntityManager
SPI underneath), and also a corresponding setSessionInitializer(Consumer<Session>)
on HibernateTransactionManager
(here just on the transaction manager since we have no common hook for all Session opening calls).
Comment From: andrei-ivanov
Thank you for the incredible fast turnaround 🙂
Now, to get this used with Spring Boot, I guess I should create a ticket for that project, right?
Comment From: jhoeller
This should work fine with Boot's existing TransactionManagerCustomizers
, or with overridden entity manager factory or transaction manager definitions, or even with @Autowired LocalContainerEntityManagerFactoryBean
/ @Autowired JpaTransactionManager
injection and a call to setEntityManagerInitializer
(before any actual JPA interactions come in). So I don't think it needs explicit support in Boot; that said, there is always room for additional convenience in Boot if you have something specific in mind there.
Comment From: andrei-ivanov
That's good news, but then will this be backported to 5.2? I see it sill has an assigned milestone of 5.2.7.
Comment From: jhoeller
I changed the milestone to 5.3 M1 a few days ago... I'm afraid this is not the kind of change we do in a patch release, due to the revision of the SPI contracts. For the time being, the best you can do in 5.2.x is to override JpaTransactionManager.createEntityManagerForTransaction
, as discussed above.
Comment From: bugy
After reading the discussion here, I came up with this solution for now:
@Bean
@ConditionalOnMissingBean
public PlatformTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
JpaTransactionManager transactionManager = new JpaTransactionManager() {
@Override
protected EntityManager createEntityManagerForTransaction() {
final EntityManager entityManager = super.createEntityManagerForTransaction();
Session session = entityManager.unwrap(Session.class);
session.enableFilter(MY_FILTER).setParameter(MY_PARAM, myValue);
return entityManager;
}
};
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
Is it the best one to do with spring 5.2 (actually 5.1 for us)?
PS thanks @andrei-ivanov for pointing me to this issue!
Comment From: oloaP
Thank you for that new way of customizing entity manager @jhoeller, it is very useful indeed to enable hibernate filters by default. I'm trying to do the same thing in a JTA context. I'm was not able to find out how Hibernate handles entity manager initialization in this case. Do you have any idea if some sort of equivalent mecanism could be implemented in Spring JtaTransactionManager ?