8

I have a Spring Framework 4 application that uses Hibernate 4.3.8 as the JPA provider. I want to use Hibernate filters, and therefore I need to enable them. I want to do this globally in the application, which I am trying to do with Spring AOP. The idea is that I can write an aspect that enables filters every time a session is created/fetched, like in this and this question.

I have added the spring-aop and aspectjweaver dependencies to my project (using Maven). I have added the following aspect.

@Aspect
@Component
public class EnableHibernateFilters {
    @Pointcut("execution(* org.hibernate.SessionFactory.getCurrentSession(..))")
    protected void sessionBeingFetched() {

    }

    @AfterReturning(pointcut = "sessionBeingFetched()", returning = "object")
    public void enableFilters(JoinPoint joinPoint, Object object) {
        System.out.println("!!! Enabling filters !!!"); // Never printed

        Session session = (Session) object;
        session.enableFilter("myFilter");
    }
}

My problem is that the above advice (enableFilters) is never invoked; neither the text is printed, nor is my filter enabled. I have verified that my aspect is detected and that AOP works in my project by changing the pointcut to one of my own classes. I have also tried to change the pointcut to execution(* org.hibernate.SessionFactory.openSession(..)), but with no result.

I suspect that this is caused by how I set up Hibernate, because I don't configure a SessionFactory explicitly; rather, I set up an EntityManagerFactory. Here is my configuration.

@Configuration
@EnableTransactionManagement
public class PersistenceConfig {
    @Bean
    public DataSource dataSource() throws NamingException {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/postgres"); // JNDI lookup
    }

    @Bean
    public EntityManagerFactory entityManagerFactory() throws SQLException, NamingException {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        vendorAdapter.setDatabase(Database.POSTGRESQL);
        vendorAdapter.setShowSql(true);
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(...);
        factory.setDataSource(this.dataSource());
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public JpaTransactionManager transactionManager() throws SQLException, NamingException {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(this.entityManagerFactory());

        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

Basically I am not sure which pointcut to use with the above configuration. I have tried to mess around with LocalContainerEntityManagerFactoryBean.setLoadTimeWeaver(), but I couldn't figure it out. I don't know if I even need to configure that anyways.

Essentially, my AOP setup works for my own custom classes. I guess either the problem is that weaving is not configured with Hibernate or something (I am very unfamiliar with this part of it all) or that the session is not obtained through the SessionFactory.getCurrentSession() method due to my setup. I tried to verify that my advice even worked with Hibernate by changing my pointcut to execution(* org.hibernate.Hibernate.isInitialized(..)) and manually invoking Hibernate.isInitialized(null) in my code, but this did not trigger the advice either, so this might be the problem. I tried what was suggested in this post to enable Hibernate weaving, but I couldn't get it to make any difference.

I also tried to set my pointcut to execution(* org.springframework.orm.hibernate4.SessionHolder.getSession(..)) and execution(* org.springframework.orm.jpa.vendor.HibernateJpaDialect.getSession(..)), but also without any luck.

So, I am not sure where to go next. How can I get a hold of Hibernate's Session object from my advice such that I can enable Hibernate filters? Thank you in advance!

EDIT: Just in case, I do have @EnableAspectJAutoProxy present in my configuration:

@Configuration
@ComponentScan(basePackages = { ... })
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // ...
}
Community
  • 1
  • 1
ba0708
  • 10,180
  • 13
  • 67
  • 99
  • Maybe a `@ComponentScan("org.hibernate")` helps? – Ruben Oct 04 '15 at 14:59
  • @Ruben No, that doesn't make a difference. – ba0708 Oct 04 '15 at 15:14
  • You are intercepting the `SessioNFactory` but are using plain JPA (with hibernate as an implementor). That is obviously never going to kick off, there is no `SessionFactory` in your config and thus your aspect will match nothing. Write the point cut for the `EntityManagerFactory.getEntityManager` instead. – M. Deinum Oct 08 '15 at 09:56
  • @M.Deinum That makes sense. I tried it out, but couldn't get it to work. If I have `@EnableAspectJAutoProxy(proxyTargetClass = true)` defined, I get the following exception: `java.lang.IllegalArgumentException: Cannot subclass final class`. If I remove the annotation, then my project compiles, but the pointcut does not work. I tried various pointcuts directly on `LocalContainerEntityManagerFactoryBean`, but so far no luck. – ba0708 Oct 08 '15 at 16:27
  • And your `AppConfig` and `PersistenceConfig` are loaded in the same context? AOP only works for beans defined in the same application context. – M. Deinum Oct 08 '15 at 18:09
  • To the best of my knowledge, yes. Here is my `web.xml` and `AppConfig`: http://pastebin.com/Df1Rg0LU – ba0708 Oct 09 '15 at 09:43
  • Why do you need `proxyTargetClass` anyway? You shouldn't need that. Also I would suggest removing `afterPropertiesSet()` and `getObject()` from your configuration class and let Spring handle that. – M. Deinum Oct 14 '15 at 18:38

3 Answers3

1

Maybe it's just the fact that you're declaring the pointcut by using the org.hibernate.SessionFactory interface as the execution argument...

@Pointcut("execution(* org.hibernate.SessionFactory.getCurrentSession(..))")

The proper way is to define the pointcut's execution as implementations of that interface and the notation for that is just a bit different, see the + sign

@Pointcut("execution(* org.hibernate.SessionFactory+.getCurrentSession(..))")

Also an alternative notation...

@Pointcut("within(org.hibernate.SessionFactory+) && execution(* getCurrentSession(..))")

You should also have a look at aspectj-cheat-sheet

Regarding java.lang.IllegalArgumentException: Cannot subclass final class. The class that is targeted is the concrete implementation of org.hibernate.SessionFactory namely org.hibernate.internal.SessionFactoryImpl which happens to be final, public final class SessionFactoryImpl.

A proxyTargetClass = true configuration according to the documentation

Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to standard Java interface-based proxies.

But since the class you're trying to subclass is final there's a bit of a problem according to the Java Language Specification

It is a compile-time error if the name of a final class appears in the extends clause (§8.1.4) of another class declaration; this implies that a final class cannot have any subclasses.

Filip
  • 1,214
  • 10
  • 19
  • Thank you for your answer! Unfortunately, I tried the pointcuts that you mentioned, but they did not work for me. – ba0708 Oct 09 '15 at 14:24
  • Have you tried using a `LocalEntityManagerFactoryBean` instead of `LocalContainerEntityManagerFactoryBean `? I'm no expert in this field but I know that it's the first one that produces an application-managed EntityManagerFactory and not a container-managed EntityManagerFactory which is produced by the latter... And we really want Spring to manage this so that aop works :) – Filip Oct 09 '15 at 15:08
  • Now I understand that aop is cool and all that but you should really have a look at the following possible solutions involving session management as well: [org.springframework.orm.hibernate4.support.OpenSessionInterceptor](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/hibernate4/support/OpenSessionInterceptor.html) or [org.hibernate.SessionEventListener](https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/SessionEventListener.html) – Filip Oct 09 '15 at 15:12
  • Or even better, have a look at this article abound using Hibernate's SPI [spring-managed-event-listeners-with-jpa](https://deepintojee.wordpress.com/2012/02/05/spring-managed-event-listeners-with-jpa/). I bet this may present a much more decent alternative to this aop contraption :) – Filip Oct 09 '15 at 15:24
  • I think this should present itself as an alternative also [OpenSessionInViewFilter](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.html) – Filip Oct 10 '15 at 16:41
  • I took a look at your proposals. Hibernate's SPI does seem like a good solution, but I took a look at the [available event types](https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/event/spi/EventType.html), and I couldn't find an appropriate one to use. In theory, I guess I could use the `INIT_COLLECTION ` event, but I don't want to enable a filter every time that is called (potentially many times). Any ideas here? I don't think the OpenSessionInViewFilter is applicable here, as it just extends the lifetime of the session to the duration of the request. – ba0708 Oct 12 '15 at 11:19
  • Regarding `OpenSessionInterceptor`, how did you imagine that this could be used? I understand how the interceptor works, but I am not 100% sure how to apply it to my use case. The interceptor itself does not give me any control of the session, so I guess that I would have to use an advice and make a pointcut to this interceptor in order to manipulate the session? I feel like I almost figured it out with the [help of the documentation](http://tinyurl.com/p37w6hf), but I am still missing some pieces in order to apply it to my project. Any help here would be much appreciated! – ba0708 Oct 12 '15 at 12:02
  • For Hibernate 4 Spring provides access to 'org.hibernate.SessionFactory' by `org.springframework.orm.hibernate4.LocalSessionFactoryBuilder` and this is a Spring managed bean and any aop pointcut will obviously apply. I just don't know if it's versatile enough for you to achieve exactly what you're looking for – Filip Oct 12 '15 at 12:14
  • Sorry, this is the not-so-deprecated factory bean that creates a Hibernate session factory... `org.springframework.orm.hibernate4.LocalSessionFactoryBean` you can then mess around with – Filip Oct 12 '15 at 12:41
1

Your aspect class seems good.

Add @Filter and @FilterDef on your entities :

  • @Filter: Adds filter to an entity or a target entity.
  • @FilterDef: Defines filter definition name and parameters to set values while enabling filter.

Example :

@Entity
@Table(name="myTable", schema="mySchema")
@FilterDef(name="myFilter", parameters=@ParamDef(name="myAttribute", type="integer"))
@Filter(name="myFilter", condition=":myAttribute <= attribute")
public class MyEntity implements Serializable {
    ...
    @Column(name="attribute")
    private Integer attribute;
    ...
}

In your configuration, the filter is enabled, but has no parameters.

Example to test:

session.enableFilter("myFilter").setParameter("myAttribute", Integer.valueOf(2));

Of course, you can set as many parameters as you need in @FilterDef annotation.

Hoping it helps you. Regards, André.

André Blaszczyk
  • 750
  • 4
  • 17
  • I have already declared the filter within my entity, but the problem is that the filter is never enabled on the session. This is supposed to happen within my aspect, but the filter is never enabled because that code is not executed. My filter does not need a parameter, so I have not added any. – ba0708 Oct 17 '15 at 17:08
0

Was facing the exact issue. I was able to resolve it by using EntityManager and getting the session information using its unwrap method rather than to get the session information using pointcut around sessionFactory's getCurrentSession call.

@PersistenceContext
private EntityManager entityManager;

@Around("myPointcut")
public Object enableGlobalFilter(ProceedingJoinPoint pjp) throws Throwable {
            Session session = entityManager.unwrap(Session.class);
            Filter filter = session.enableFilter("Published_Entity");
            filter.setParameter("is_publishedParam", true);
            Object obj  = pjp.proceed();
            session.disableFilter("Published_Entity");
            return obj;
 }

Hope this helps.

Crane
  • 121
  • 2
  • 9