Dominik Bartholdi opened SPR-16518 and commented
in the context of #5615, _setFilterName_ and _setFilterNames_ methods have been added to _HibernateAccessor_ to support enabling Hibernate Filters for Hibernate 3. Unfortunate there seems no equivalent way to enable Hibernate Filters when using Spring Data Repositories with Hibernate 5.
Sure, I can create a pointcut that matches all repositories:
@Pointcut("execution(* org.springframework.data.repository.Repository+.*(..))")
public void activateTenantFilter() throws Throwable {
Optional<Long> tenantId = TenantUtil.getCurrentTenantId();
tenantId.ifPresent(id -> {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("TENANT_FILTER");
filter.setParameter("tenantId", id);
});
}
But this means the aspect is executed way to many times - e.g. if I have a service calling multiple repository methods.
Another way would be to define a custom annotation (e.g. @EnableTenantFilter) and annotate service methods with it to enable the filter based on a Pointcut specific for this Annotation and do the same as in above, but this could lead to the same issue as above.
A nicer way would be to define a Pointcut for _org.hibernate.SessionBuilder.openSession_, but as _SessionBuilder_ is not exposed as a Bean, but this requires Load Time Weaving :(
@AfterReturning(pointcut = "execution(* org.hibernate.SessionBuilder.openSession(..))", returning = "session")
public void forceFilter(JoinPoint joinPoint, Object session) {
...
Filter filter = session.enableFilter("TENANT_FILTER");
filter.setParameter("tenantId", id);
}
Also DATACMNS-293 would provide a way to implement such a filter (even independent of Hibernate), but it does not seem to be merged anytime soon :(
A way to ease this, would be some kind of a listener that is called whenever a new Hibernate Session is created and allows me to enable the Filter based on my criteria at that point. An important point is that I need to be able to pass parameters to the filter, otherwise it does not make a lot of sense.
Maybe I have missed something and this already exists...
No further details from SPR-16518
Juergen Hoeller commented
Oliver Drotbohm, what's your take on this from a Spring Data perspective?
Dominik Bartholdi commented
Juergen Hoeller is there any chance to make this easier?
Hi, I'd like to note that AOP approach (the first example from Dominik) with repositories doesn't work for spring.jpa.open-in-view = false (which is recommended).
So it seems that the only option is aspectj load time weaving. And this is very cumbersome for spring boot applications.
Are there any other ways to intercept sessions and enable filters?
See #25125
@bugy, Looked at your code in #25125. Sorry for the naive question - Could you please tell me how to use this in my application. Which class to put your code and what additional changes do I need to make to enable the filter for all the repository methods of selective repositories? Thanks.
Hi @jofatmofn, this is just a normal spring @Configuration bean creation, i.e. you just put this method in a class like that and it will work:
@Configuration
public class HibernateFilterConfig {
@Bean
@ConditionalOnMissingBean
public PlatformTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
// ... code from #25125
}
}
Your entities should be annotated with proper field annotations:
@FilterDef(name = MY_FILTER, parameters = @ParamDef(name = MY_PARAM, type = "string"))
@Filters(
@Filter(name = MY_FILTER, condition = MyEntity.FILTERED_FIELD + " = :" + MY_PARAM)
)
And I think that's it (if I didn't forget anything).
Unfortunately, this is not customizable by a repository. But usually, repositories correspond to some particular entities, so you have to add filters only to those entities, which you are interested in. I think it's fine to always set a filter in the HibernateFilterConfig. If entities don't use it, it will be simply ignored
If you really want to customize based on a repository, I believe you have to go AOP way, with load time weaving and java agent.
Hi @bugy, Thanks for the quick response.
My objective is to achieve Multi-tenancy. I think I am badly missing a key point - Are these solutions suitable for shared database, shared schema scenario? I don't intend to hijack this thread, if it is not appropriate. Could you please confirm this point alone. Thanks.
Hi @jofatmofn, I'm also using it for multitenancy, with a shared DB and schema.
For AOP: it depends on where you add the advice. If you add them on repository methods (see the first example in this ticket), then it should work, since repository methods are invoked within you rest controller.
For transaction customizer: for me, it's invoked just before the query goes to a database, i.e. definitely after RestController code. But it can depend on spring.jpa.open-in-view = false config (which I have, and which is not default one). I believe, that with open-in-view = true, transaction is open before RestController is invoked. But in this case, you can use the first example from this thread, I guess.
Thanks @bugy, I have raised a SO question for my issue at https://stackoverflow.com/questions/62387223/why-createentitymanager-is-called-before-my-rest-controller-method.
Hi @jofatmofn, I have implemented and documented this in a standalone Spring Boot application except the TenantId is applied through a received JWT token. But the same concept can be applied with your tenantId.
https://github.com/M-Devloo/Spring-boot-auth0-discriminator-multitenancy
This project is built to tackle the discriminator based multi tenancy problem.
Most helpful comment
Hi @jofatmofn, I have implemented and documented this in a standalone Spring Boot application except the TenantId is applied through a received JWT token. But the same concept can be applied with your tenantId.
https://github.com/M-Devloo/Spring-boot-auth0-discriminator-multitenancy
This project is built to tackle the discriminator based multi tenancy problem.