29

Is there any way in OpenJPA to get hold of a nested object property via CriteriaBuilder?

Here's a small case.

@Entity
public class X {
       private Object Y;

       // getters, setters...
}

@Entity
public class Y {
       private String Z;

       // getters, setters...
}

So, when using CriteriaBuilder, we use X as Root, i.e.:

@PersistenceContext
private EntityManager entityManager;

//.....

Root<X> rootObj = criteriaBuilder.from(X.class);
CriteriaQuery<X> select;

String param1 = X.getY().getZ();

// initializing predicate, default value is TRUE
Predicate predicate1 = criteriaBuilder.isNull(null);

// construct search predicate which fails miserably due to IllegalArgumentExecption
if (X != null) {
    predicate1 = criteriaBuilder.and(predicate1, criteriaBuilder.equal(rootObj.<String> get("Y.Z"), param1));
}

Now, my grief is this -> get("Y.Z")

CriteriaBuilder doesn't know to fetch Z reflectively (however it can and will resolve Y). Is there any way to get hold of Z directly from get()?

Apart from using JPQL, I can think of one other method - which I dislike immensely: I suppose I could have exposed Z as an @Transient property in X (as to prevent OpenJPA from persisting it as a column), but that sounds like a Really Bad Idea: I am essentially flattening out an object graph manually and introduce unneeded garbage inside the entity bean, not counting the time needed to flatten out a complex graph or the error-proness of this (it can go awry in so many ways).

Is there a way to make this work? Any ideas are appreciated.

quantum
  • 3,000
  • 5
  • 41
  • 56

2 Answers2

39

Heh, the solution is suprisingly simple - and it looks really ugly, but it works.

predicate1 = criteriaBuilder.and(predicate1, criteriaBuilder.equal(rootObj.get("Y").<String> get("Z"), param1));}

I really don't know if there is a more elegant solution to this.

quantum
  • 3,000
  • 5
  • 41
  • 56
  • 6
    There is if you use metamodel: `predicate1 = criteriaBuilder.and(predicate1, criteriaBuilder.equal(rootObj.get(X_.Y).get(Y_.Z), param1));}` – gertas Jun 20 '11 at 11:05
  • True, however we don't use the metamodel classes and I personally found this syntax of chaining quite unexpected, especially the type declaration on the last getter in chain. Arguably, it could be more cleanly defined (and easily used) using reflection under the hood but - such as it is, this way will have to do. – quantum Jun 22 '11 at 15:01
  • I confirm that this solution works well even for me and with. For the ugly part, my suggestion is to isolate this code into a separate class from your code following delegate or dao pattern – Vokail Nov 07 '18 at 12:10
  • 2
    Here's a [gist](https://gist.github.com/ulisseslima/7ff9d4ecc49470ccf5c5979b8d806eef) for a base JPA repository that handles sub paths with simple strings. – dvlcube Apr 17 '19 at 02:35
  • Hi thanks for answer, i dlike to just add for someone that if you need to add nested Collection private Set Y then you need to use JOIN criteriaBuilder.equal(rootObj.join("Y"). get("Z"), param1)) for the access to Collection elements. – Dave Kraczo Nov 07 '22 at 17:44
  • Is there anyway to do this chaining when the Y is a collection of objects for example: `List Y` and I want to access SomeObject.somefield. – ds459 Feb 24 '23 at 13:18
15

For any arbitrary nested attribute path ("relation.subRelation.attribute"):

private Path<T> getPath(Root<T> root, String attributeName) {
    Path<T> path = root;
    for (String part : attributeName.split("\\.")) {
        path = path.get(part);
    }
    return path;
}
Oleg Mikhailov
  • 5,751
  • 4
  • 46
  • 54
  • your solutions look perfect and generic. but when I tried it in my code getting exception java.lang.IllegalArgumentException: Parameter value [com.example.model.Employee@5be1ea20] did not match expected type [java.lang.String (n/a)] can you please correct me what wrong I'm doing – umesh Apr 03 '20 at 11:56