Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add way to enable Session filter for Hibernate 5+ [SPR-16518] #21061

Open
spring-projects-issues opened this issue Feb 20, 2018 · 10 comments
Open

Add way to enable Session filter for Hibernate 5+ [SPR-16518] #21061

spring-projects-issues opened this issue Feb 20, 2018 · 10 comments
Labels
in: data status: waiting-for-triage

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Feb 20, 2018

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

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 20, 2018

Juergen Hoeller commented

Oliver Drotbohm, what's your take on this from a Spring Data perspective?

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Aug 30, 2018

Dominik Bartholdi commented

Juergen Hoeller is there any chance to make this easier?

@spring-projects-issues spring-projects-issues added status: waiting-for-triage in: data type: enhancement and removed type: enhancement labels Jan 11, 2019
@bugy
Copy link

@bugy bugy commented Jun 4, 2020

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?

@andrei-ivanov
Copy link

@andrei-ivanov andrei-ivanov commented Jun 4, 2020

See #25125

@jofatmofn
Copy link

@jofatmofn jofatmofn commented Jun 11, 2020

@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.

@bugy
Copy link

@bugy bugy commented Jun 12, 2020

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.

@jofatmofn
Copy link

@jofatmofn jofatmofn commented Jun 13, 2020

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.

  • In case of AOP, the advice on repositories (enabling the filter and setting tenant id) runs before the advice on RestController method which identifies the tenant id.
  • In case of post-processing transactional EntityManager instances, transactionManager runs at the start of the Spring Boot. Again the tenant identification happens only at individual RestController methods.

@bugy
Copy link

@bugy bugy commented Jun 13, 2020

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.

@jofatmofn
Copy link

@jofatmofn jofatmofn commented Jun 15, 2020

@M-Devloo
Copy link

@M-Devloo M-Devloo commented Oct 5, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data status: waiting-for-triage
Projects
None yet
Development

No branches or pull requests

5 participants