Issue
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.
Solution
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;
});
Answered By - Vadym Martsun
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)