Vadim
Vadim

Reputation: 31

How to use Hibernate @Filter without @Transactional?

I am using Hibernate @Filter with Spring Data to add specific "where" clause for every query in my project. The problem is that it works as long as I use @Transactional annotation for my 'findAll' method. Is there any way to avoid using @Transactional? Why is it important here?

Here is the filter config:

 @FilterDef(name = "testFilter",
            parameters = @ParamDef(name = "company_id", type = "long"),
            defaultCondition = "company_id=:companyId")
    @Filter(name = "testFilter")
    @EntityListeners(EmployeeListener.class)
    @NoArgsConstructor
    public class Employee {//basic fields}

Repository:

@Repository
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, UUID> {
}

Aspect that enables the filter:

@Aspect
@Component
public class TestAspect {

    private final SecurityAspect securityAspect;
    private final Logger logger;

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    public TestAspect(SecurityAspect securityAspect, Logger logger) {
        this.securityAspect = securityAspect;
        this.logger = logger;
    }
    

    @Around("execution(* org.springframework.data.repository.CrudRepository+.findAll(..))")
    public Object aroundFindAllTenantAware(ProceedingJoinPoint joinPoint) {
        Session session = this.entityManager.unwrap(Session.class);
        session.enableFilter("testFilter")
                .setParameter("companyId", this.securityAspect.getCompanyId());
        Object retValue = null;
        try {
            retValue = joinPoint.proceed();
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
        } finally {
            session.disableFilter("testFilter");
        }
        return retValue;

    }
}

And, finally, my service:

@Service
@Transactional
public class EmployeeApiServiceImpl {

private final EmployeeRepository repository;
    
    @Autowired
    public EmployeeApiServiceImpl(EmployeeRepository repository) {
this.employeeRepository = employeeRepository; }
                                
   
    @Override
    public Response listProfiles(SecurityContext securityContext) { 
        List<Employee> employees = repository.findAll();
        return Response.ok().entity(employees).build();
    }

Without @Transactional annotation, it does not work. When I am debugging the aspect I can see that the filter was enabled but the query did not change. When I put the annotation, everything works fine. But I don't get why it is happening. This annotation is not supposed to be on read methods, plus in every tutorial everything works without it, but not in my case.

Upvotes: 2

Views: 1034

Answers (2)

Vadim
Vadim

Reputation: 31

After some research I found a solution - using TransactionTemplate class. So I just need to put my code from aspect into method 'execute' and it will do the same as @Transactional annotation. But in this case, I don't have to annotate each method in each service as Transactional. These articles helped me a lot:

https://www.baeldung.com/spring-programmatic-transaction-management https://www.baeldung.com/transaction-configuration-with-jpa-and-spring

transactionTemplate.execute(status -> {
Session session = null;
        Object result = null;
        try {
            session = entityManager.unwrap(Session.class);
            session.enableFilter(Constants.FILTER_NAME)
                    .setParameter(Constants.PARAMETER_NAME, this.securityAspect.getCompanyId());
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
        } finally {
            if (session != null) {
                session.disableFilter(Constants.FILTER_NAME);
            }
        }
        return result;
});

Upvotes: 1

Christian Beikov
Christian Beikov

Reputation: 16430

The problem is the EntityManager proxy that is injected by Spring which only supports certain operations without a transaction. You'll need a custom proxy implementation if you want support for this. See org.springframework.orm.jpa.SharedEntityManagerCreator.SharedEntityManagerInvocationHandler#invoke

Upvotes: 0

Related Questions