Apply Spring JPA Specification to multiple repositories and queries
up vote
2
down vote
favorite
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?
java spring-boot spring-data-jpa spring-aop
add a comment |
up vote
2
down vote
favorite
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?
java spring-boot spring-data-jpa spring-aop
2
Sharinguser
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.
– Eugen Covaci
Nov 20 at 15:34
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
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?
java spring-boot spring-data-jpa spring-aop
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?
java spring-boot spring-data-jpa spring-aop
java spring-boot spring-data-jpa spring-aop
edited Dec 11 at 9:49
BDL
14.5k63341
14.5k63341
asked Nov 20 at 14:52
RafaelCT
285
285
2
Sharinguser
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.
– Eugen Covaci
Nov 20 at 15:34
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51
add a comment |
2
Sharinguser
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.
– Eugen Covaci
Nov 20 at 15:34
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51
2
2
Sharing
user
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.– Eugen Covaci
Nov 20 at 15:34
Sharing
user
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.– Eugen Covaci
Nov 20 at 15:34
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51
add a comment |
2 Answers
2
active
oldest
votes
up vote
2
down vote
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
public class UserAspect {
@Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll()
methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification)
method. Notice how I get the HttpServletRequest
instance.
Of course, it's a starting point not an out of the box solution.
I think Rafael said that the method to be called wasfindAll(Specification sp, Pageagle p)
, not justfindAll(Specification sp)
like in your suggested solution.
– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to interceptfindAll(Pageable)
, but your pointcut interceptsfindAll()
. So there are at least two bugs in your code. No offense meant.
– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods werefindAll(Specification sp)
andfindAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.
– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
|
show 2 more comments
up vote
1
down vote
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository
, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository
) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T>
while the method you want to call instead returns List<T>
. The calling method surely expects the former and not the latter, unless you always use Iterable<T>
which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable
. Then your pointcut/advice pair looks like this:
@Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say
Foo
, the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
@Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository
method calls already via target()
parameter binding using the advice signature. The second within()
should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor
- but only if you do not need the JpaRepository
in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution()
with within()
(a matter of taste):
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable)
signature:
@Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
Unless I'm missing something, you do suppose all repositories implementsExampleRepository
.
– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His ownExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.
– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.ExampleRepository
is the repository interface forExample
entity only.
– Eugen Covaci
Dec 11 at 7:51
For another entity, let's sayFoo
the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
|
show 2 more comments
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53395648%2fapply-spring-jpa-specification-to-multiple-repositories-and-queries%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
public class UserAspect {
@Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll()
methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification)
method. Notice how I get the HttpServletRequest
instance.
Of course, it's a starting point not an out of the box solution.
I think Rafael said that the method to be called wasfindAll(Specification sp, Pageagle p)
, not justfindAll(Specification sp)
like in your suggested solution.
– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to interceptfindAll(Pageable)
, but your pointcut interceptsfindAll()
. So there are at least two bugs in your code. No offense meant.
– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods werefindAll(Specification sp)
andfindAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.
– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
|
show 2 more comments
up vote
2
down vote
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
public class UserAspect {
@Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll()
methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification)
method. Notice how I get the HttpServletRequest
instance.
Of course, it's a starting point not an out of the box solution.
I think Rafael said that the method to be called wasfindAll(Specification sp, Pageagle p)
, not justfindAll(Specification sp)
like in your suggested solution.
– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to interceptfindAll(Pageable)
, but your pointcut interceptsfindAll()
. So there are at least two bugs in your code. No offense meant.
– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods werefindAll(Specification sp)
andfindAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.
– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
|
show 2 more comments
up vote
2
down vote
up vote
2
down vote
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
public class UserAspect {
@Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll()
methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification)
method. Notice how I get the HttpServletRequest
instance.
Of course, it's a starting point not an out of the box solution.
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
public class UserAspect {
@Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll()
methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification)
method. Notice how I get the HttpServletRequest
instance.
Of course, it's a starting point not an out of the box solution.
answered Nov 20 at 16:18
Eugen Covaci
1,18026
1,18026
I think Rafael said that the method to be called wasfindAll(Specification sp, Pageagle p)
, not justfindAll(Specification sp)
like in your suggested solution.
– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to interceptfindAll(Pageable)
, but your pointcut interceptsfindAll()
. So there are at least two bugs in your code. No offense meant.
– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods werefindAll(Specification sp)
andfindAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.
– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
|
show 2 more comments
I think Rafael said that the method to be called wasfindAll(Specification sp, Pageagle p)
, not justfindAll(Specification sp)
like in your suggested solution.
– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to interceptfindAll(Pageable)
, but your pointcut interceptsfindAll()
. So there are at least two bugs in your code. No offense meant.
– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods werefindAll(Specification sp)
andfindAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.
– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
I think Rafael said that the method to be called was
findAll(Specification sp, Pageagle p)
, not just findAll(Specification sp)
like in your suggested solution.– kriegaex
Dec 11 at 0:57
I think Rafael said that the method to be called was
findAll(Specification sp, Pageagle p)
, not just findAll(Specification sp)
like in your suggested solution.– kriegaex
Dec 11 at 0:57
Furthermore the OP wants to intercept
findAll(Pageable)
, but your pointcut intercepts findAll()
. So there are at least two bugs in your code. No offense meant.– kriegaex
Dec 11 at 2:21
Furthermore the OP wants to intercept
findAll(Pageable)
, but your pointcut intercepts findAll()
. So there are at least two bugs in your code. No offense meant.– kriegaex
Dec 11 at 2:21
@kriegaex When I wrote the answer, the OP methods were
findAll(Specification sp)
and findAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.– Eugen Covaci
Dec 11 at 7:20
@kriegaex When I wrote the answer, the OP methods were
findAll(Specification sp)
and findAll()
then the question was edited later. As I said, this is not a solution, it's more like a starting point.– Eugen Covaci
Dec 11 at 7:20
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
I see. Yeah, moving targets from OPs changing their minds/questions are difficult to trace here when just seeing the current status of questions and answers.
– kriegaex
Dec 11 at 7:22
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
@kriegaex A feature like editing history would be helpful.
– Eugen Covaci
Dec 11 at 7:24
|
show 2 more comments
up vote
1
down vote
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository
, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository
) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T>
while the method you want to call instead returns List<T>
. The calling method surely expects the former and not the latter, unless you always use Iterable<T>
which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable
. Then your pointcut/advice pair looks like this:
@Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say
Foo
, the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
@Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository
method calls already via target()
parameter binding using the advice signature. The second within()
should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor
- but only if you do not need the JpaRepository
in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution()
with within()
(a matter of taste):
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable)
signature:
@Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
Unless I'm missing something, you do suppose all repositories implementsExampleRepository
.
– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His ownExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.
– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.ExampleRepository
is the repository interface forExample
entity only.
– Eugen Covaci
Dec 11 at 7:51
For another entity, let's sayFoo
the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
|
show 2 more comments
up vote
1
down vote
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository
, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository
) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T>
while the method you want to call instead returns List<T>
. The calling method surely expects the former and not the latter, unless you always use Iterable<T>
which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable
. Then your pointcut/advice pair looks like this:
@Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say
Foo
, the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
@Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository
method calls already via target()
parameter binding using the advice signature. The second within()
should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor
- but only if you do not need the JpaRepository
in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution()
with within()
(a matter of taste):
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable)
signature:
@Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
Unless I'm missing something, you do suppose all repositories implementsExampleRepository
.
– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His ownExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.
– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.ExampleRepository
is the repository interface forExample
entity only.
– Eugen Covaci
Dec 11 at 7:51
For another entity, let's sayFoo
the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
|
show 2 more comments
up vote
1
down vote
up vote
1
down vote
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository
, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository
) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T>
while the method you want to call instead returns List<T>
. The calling method surely expects the former and not the latter, unless you always use Iterable<T>
which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable
. Then your pointcut/advice pair looks like this:
@Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say
Foo
, the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
@Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository
method calls already via target()
parameter binding using the advice signature. The second within()
should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor
- but only if you do not need the JpaRepository
in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution()
with within()
(a matter of taste):
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable)
signature:
@Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository
, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository
) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T>
while the method you want to call instead returns List<T>
. The calling method surely expects the former and not the latter, unless you always use Iterable<T>
which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable
. Then your pointcut/advice pair looks like this:
@Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say
Foo
, the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
@Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository
method calls already via target()
parameter binding using the advice signature. The second within()
should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor
- but only if you do not need the JpaRepository
in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution()
with within()
(a matter of taste):
@Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable)
signature:
@Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
edited Dec 12 at 1:32
answered Dec 11 at 2:21
kriegaex
30.5k36398
30.5k36398
Unless I'm missing something, you do suppose all repositories implementsExampleRepository
.
– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His ownExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.
– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.ExampleRepository
is the repository interface forExample
entity only.
– Eugen Covaci
Dec 11 at 7:51
For another entity, let's sayFoo
the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
|
show 2 more comments
Unless I'm missing something, you do suppose all repositories implementsExampleRepository
.
– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His ownExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.
– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.ExampleRepository
is the repository interface forExample
entity only.
– Eugen Covaci
Dec 11 at 7:51
For another entity, let's sayFoo
the repository would bepublic interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
Unless I'm missing something, you do suppose all repositories implements
ExampleRepository
.– Eugen Covaci
Dec 11 at 7:38
Unless I'm missing something, you do suppose all repositories implements
ExampleRepository
.– Eugen Covaci
Dec 11 at 7:38
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His own
ExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.– kriegaex
Dec 11 at 7:47
Of course I do because this is what the OP's question clearly implies. Otherwise the trick to intercept a method call from one interface and then call a method from another interface would not even work. His own
ExampleRepository
extends both of them, which is the reason why what he wants to do theoretically makes sense in the first place.– kriegaex
Dec 11 at 7:47
That's not like Spring Data works.
ExampleRepository
is the repository interface for Example
entity only.– Eugen Covaci
Dec 11 at 7:51
That's not like Spring Data works.
ExampleRepository
is the repository interface for Example
entity only.– Eugen Covaci
Dec 11 at 7:51
For another entity, let's say
Foo
the repository would be public interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
For another entity, let's say
Foo
the repository would be public interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
– Eugen Covaci
Dec 11 at 7:53
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
Interesting (implicit) question. As I said in my answer's first sentence: I am not a Spring or Java EE user. Anyway, please see my answer update and tell me if this would do for you.
– kriegaex
Dec 11 at 9:00
|
show 2 more comments
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53395648%2fapply-spring-jpa-specification-to-multiple-repositories-and-queries%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
Sharing
user
field between those 2 advices is clearly a bad idea since you are in a multithreaded environment.– Eugen Covaci
Nov 20 at 15:34
@EugenCovaci yes, you are right. I wrote this code as a primary test of AOP, but will definitely take this into account when implementing the real solution. Thanks!
– RafaelCT
Nov 20 at 16:51