-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Specifications applies conditions in the wrong order [DATAJPA-959] #1309
Comments
Oliver Drotbohm commented A |
Triqui Galletas commented The problem is not when using the AND to add a condition, it's when using it to modify the query (adding a join or a fetch). Fetch child = getFetchOrJoin(root, "child"); // this throws an exception when the join is not found
cb.equal(child.get("height"), 768); // otherwise it would throw a NulPointerException here
child = root.fetch("child", JoinType.LEFT); // These two lines should be executed before the lines above
cb.equal(child.get("width"), 1024); // only way to do it is move the specifications with join/fetch after any other specification so they are executed before. And I think |
Triqui Galletas commented I added a comment some days ago. I don't know whether that's enough or I have to use the feedback button (I just saw it today). So I write this just in case. Please, ignore this if it wasn't needed |
Oliver Drotbohm commented I think I understood your scenario but that doesn't change anything regarding the requirement for commutativity. I can't actually follow your "The problem is not when using the AND to add a condition…". That's what the ticket is about, isn't it? That the order of which specification is added to which changes the result, right? If you're using a canonical specification only, the problem can't really appear. So again. You need to implement your specifications in a commutative way |
Triqui Galletas commented I understand your point, and I agree with you. But there are two things I think you are not considering properly (please, take no offence, my english is not so fluent and it might sound rude even though that's not my intention). First, if you need to add joins and some other stuff which does not "return" a predicate, the only way I know to do it is like this: Specifications<Object> spec = Specifications.where(new Specification<Object>() {
@Override
public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true);
root.join("test", JoinType.LEFT);
return null;
}
}); Maybe there is a better way and that would solve all my problems.
Second. If you call root.getJoins() from inside toPredicate() it will never work unless any specification with a call to root.join() is added after it. I've tried to put an example as short as possible. Still it's a bit long, but please have a look at it and let me know what would be the right way to write it (I can't just put everything into one specification because they come from different places): public Specification<Object> b(final Integer status) {
Specifications<Object> spec = Specifications.where(new Specification<Object>() {
@Override
public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true);
root.join("test", JoinType.LEFT);
return null;
}
});
if (status != null) {
spec = spec.and(new Specification<Object>() {
@Override
public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<?> child = root.get("test");
Bindable<?> model = child.getModel();
if (model == null || model.getBindableType().equals(BindableType.PLURAL_ATTRIBUTE)) {
for (Join<?, ?> join : root.getJoins()) {
if (join.getAttribute().getName().equals("test")) {
child = join;
break;
}
}
}
return cb.equal(child.get("status"), status);
}
});
}
return spec;
} Please, note that this won't work. But if you add the two specifications in reversed order it works perfectly. (Maybe I'm insisting too much and wasting too much of your time. As I said, the workaround is easy, so just let me know if I should drop the matter) |
Jens Schauder commented Specifications have to be commutative. In a case like this it might be able to check the root if a join is already present and if not create it, if it is, use it. If that doesn't work you have still the option to write your own logic to combine Specifications as you desire |
Thankfully. I finally updated my old project to a new version and now I see that someone smarter fixed this in 2146. |
Triqui Galletas opened DATAJPA-959 and commented
This may sound stupid but I think the method Specifications.and(final Specification<T> other) should be written a bit differently.
These two lines should be swapped:
The call to other.toPredicate should happen after the call to spec.toPredicate.
I'm not sure if there is a better way, but I use to have query.distinct(), query.join() and query.fetch() calls inside the toPredicate methods in some specifications, and in some cases I need to know if a join, for instance, has been already applied or not. The only way to know is if the toPredicate methods for every specification is called in the same order they were created.
For example I build a dynamic specification where I have this:
And then I have some 'ifs' where I add more specifications that need to reference a property from that join.
My method getFetchOrJoin uses root.getJoins() and root.getFetches() to retrieve the already existing join, but it doesn't work if I add the first specification first and the second second. It only works if I add the second specification first and the first second.
This doesn't work:
This works:
Affects: 1.10.1 (Hopper SR1)
The text was updated successfully, but these errors were encountered: