Reputation: 31
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
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
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