I have the following situation:
My project contains multiple entities, each one with its respective controller, service and JPA repository. All of these entities are associated with a specific company by a "companyUuid" property.
Every incoming request in my controllers will have a "user" header, which will give me the details of the User making that request, including which company he is associated with.
I need to retrieve the company associated with the user from the header and filter every subsequent query by this company, which would be essentially like adding WHERE companyUuid = ...
to each query.
What I did as a solution was a generic function for creating the Specification object:
public class CompanySpecification {
public static <T> Specification<T> fromCompany(String companyUuid) {
return (e, cq, cb) -> cb.equal(e.get("companyUuid"), companyUuid);
}}
Implemented repository as follows:
public interface ExampleRepository extends JpaRepository<Example, Long>, JpaSpecificationExecutor<Example> { }
Changed the "find" calls to include the specification:
exampleRepository.findAll(CompanySpecification.fromCompany(companyUuid), pageRequest);
Of course, this requires adding @RequestHeader
to the controller functions to get the user in the header.
Although this solution works absolutely fine, it would require a lot of copy-pasting and code repetition to get it done for all routes of my @RestControllers
.
Therefore, the question is: how can I do this in an elegant and clean way for all my controllers?
I have researched this quite a bit now and I came across the following conclusions:
- Spring JPA and Hibernate don't seem to provide a way of dynamically using a Specification to restrict all queries (reference: Automatically Add criteria on each Spring Jpa Repository call)
- Spring MVC
HandlerInterceptor
would maybe help for getting the User out of the header in each request, but it doesn't seem to fit overall since I don't use views in this project (it's just a back-end) and it can't do anything about my repository queries - Spring AOP seemed like a great option to me and I gave it a go. My intention was to keep all repository calls as they were, and add the Specification to the repository call. I created the following
@Aspect
:
@Aspect
@Component
public class UserAspect {
@Autowired(required=true)
private HttpServletRequest request;
private String user;
@Around("execution(* com.example.repository.*Repository.*(..))")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object[] arguments = jp.getArgs();
Signature signature = jp.getSignature();
List<Object> newArgs = new ArrayList<>();
newArgs.add(CompanySpecification.fromCompany(user));
return jp.proceed(newArgs.toArray());
}
@Before("execution(* com.example.controller.*Controller.*(..))")
public void getUser() {
user = request.getHeader("user");
}
}
This would have worked perfectly, since it would require almost no modifications at all to controllers, services and repositories. Although, I had a problem with the function signature. Since I am calling findAll(Pageable p)
in my Service, the signature of the function is already defined in my advice, and I can't change to the alternative version findAll(Specification sp, Pageagle p)
from inside the advice.
What do you think would be the best approach in this situation?