1

I'm trying to enable a hibernate filter through spring EntityManager by tyring to pointcut a service implementation method annotated with custom annotation @TenantAware and add @Around advise to that method. I want to enable custom filter which adds a differentiator where tenant_id = :tenantId on all entities that extend a BaseEntity. Hence I created the custom annotation and using it on @Transactional methods where it is required. It is intercepting the method successfully but the variable values when I log them are showing up empty and neither is the filter being set.

The project is a spring-boot 2 application and I'm using spring aop for creating the aspect. I'm using Hibernate 5 as the JPA implementation provider.

Load time weaving of the SimpleJpaRepository.class is not possible since it does not expose a noarg constructor.

This is my TenantFilterAdvisor class.

package org.foo.bar.advisors;

@Aspect
@Slf4j
@Component
public class TenantFilterAdvisor {

    @PersistenceContext
    private EntityManager entityManager;

    public TenantFilterAdvisor() {
        log.debug("###########################################################################");
        log.debug("###################### Tenant Advisor Filter Started ######################");
        log.debug("###########################################################################");
    }

    @Pointcut(value = "@annotation(org.foo.bar.TenantAware)")
    public void methodAnnotatedWithTenantAware() {
    }

    @Pointcut(value = "execution(public * * (..))")
    public void allPublicMethods() {

    }

    @Around(value = "methodAnnotatedWithTenantAware() && allPublicMethods()")
    public Object enableTenantFilter(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        log.debug("###########################################################################");
        log.debug("###################### Before enabling tenant filter ######################");
        log.debug("###########################################################################");

        if (null != entityManager) {

            log.debug("Tenant filter name: ", "tenantFilter");
            log.debug("Tenant filter property: ", "tenantId");
            log.debug("Setting tenant id to: ", new Long(10));

            Session session = entityManager.unwrap(Session.class);
            Filter filter = session.enableFilter("tenantFilter");
            filter.setParameter("tenantId", new Long(10));

        }


        Object result = proceedingJoinPoint.proceed();

        // Code to disable the hibernate filter goes here.
        log.debug("###########################################################################");
        log.debug("###################### After disabling tenant filter ######################");
        log.debug("###########################################################################");

        return result;

    }

}

The relevant part of service interface and implementation class is

public interface InventoryService {
    Inventory getInventoryById(Long id);
}
@Service
public class InventoryServiceImpl implements InventoryService {

    @Autowired
    private InventoryRepository repo;

    @Override
    @Transactional
    @TenantAware
    public Inventory getInventoryById(Long id) {
       LOG.debug("getInventoryById() called  with: id = {}", id);
        final Optional<Inventory> inventoryOp = repo.findById(id);

        if (inventoryOp.isPresent()) {
            return inventoryOp.get();
        } else {
            throw new InventoryNotFoundException(String.format(MESSAGE_INVENTORY_NOT_FOUND_FOR_ID, id));
        }
    }
}

The repository interface is

@Repository
@Transactional(readOnly = true)
public interface InventoryRepository extends BaseRepository<Inventory, Long> {  
}

The BaseRepository interface extends JpaRepository.

And the aspect configuration class is

@Configuration
@ComponentScan(basePackages = {"org.foo.bar.advisors"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AOPConfig {
}

And finally the relevant MappedSuperClass which is inherited by other classes has the filter defined as

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@FilterDef(
        name = "tenantFilter",
        parameters = @ParamDef(name = "tenantId", type = "long")
)
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public abstract class BaseTransactionalEntity extends BaseEntity {

    @Column(name = "tenant_id", nullable = false)
    private Long tenantId;

}

Here is the cutom annotation class if you need the detail

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface TenantAware {
}

I need the hibernate filter to be enabled in session and disabled after the proceeding join point completes the execution. But it is not so. What am I missing?

5
  • Ofcourse they aren't logger. You are missing the {} placeholder in your log message. Commented Apr 4, 2019 at 10:14
  • Thanks, I think I get that. But what about the filter not being enabled on the entity manager? Commented Apr 4, 2019 at 11:04
  • 1
    How are you obtaining the data. YOu only show the service but not the actual implementation using an entitymanager or repository. Commented Apr 4, 2019 at 11:16
  • I have a repository interface that extends spring JpaRepository interface. I'm relying on spring SimpleJpaRepository to provide the implementation. Otherwise I'll have to implement my own repository implementation ground up which beats the purpose. If needed I can post the repository interface here but it's an empty interface as I said. Commented Apr 5, 2019 at 12:25
  • 1
    findById calls entityManager.find and filters don't work in that case. They only work in case of queries. Commented Apr 6, 2019 at 6:23

2 Answers 2

1

As explained in the Hibernate Reference Guide filters only apply to entity queries not to direct fetching. In your code you are doing a direct fetch through findById which translates to entityManager.find and is thus a direct fetch.

You could override the Spring JPA repository and reimplement the findById to be an entity query instead of a direct fetch, to workaround this issue.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for pointing this out! I wish there was an easy way to do something so trivial. Now I guess I'll have to provide my own implementation for all the methods just to apply that filter.
0

An alternative (and proven to be working) way without AOP is using TransactionManagerCustomizers:

@Configuration
public class HibernateFilterConfig {
    @Bean
    @ConditionalOnMissingBean
    public PlatformTransactionManager transactionManager(
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        JpaTransactionManager transactionManager = new JpaTransactionManager() {
            @Override
            @NonNull
            protected EntityManager createEntityManagerForTransaction() {
                final EntityManager entityManager = super.createEntityManagerForTransaction();
                Session session = entityManager.unwrap(Session.class);
                session.enableFilter("tenantFilter").setParameter("tenantId", new Long(10));
                return entityManager;
            }
        };
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));

        return transactionManager;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.