I have two entities Document and Property where a document has a set of properties:
#Entity
public class Document{
#Id
private Integer id;
#OneToMany
private Set<Property> properties;
}
And
#Entity
public class Property{
#Id
private Integer id;
private String key;
private String value;
}
I want to implement the following JPQL query using Criteria API:
SELECT d FROM Document d
WHERE "value11" = ANY(SELECT p.value FROM d.properties p WHERE p.key="key11")
I tried the following:
EntityManager em = PersistenceManager.INSTANCE.getEntityManager();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Document> query = criteriaBuilder.createQuery(Document.class);
Root<Document> fromDoc = query.from(Document.class);
Subquery<String> subQuery = query.subquery(String.class);
Root<Property> subRoot = subQuery.from(Property.class);
subQuery.select(subRoot.get(Property_.value));
SetJoin<Document, Property> join = fromDoc.join(Document_.properties, JoinType.INNER);
subQuery.select(join.<String> get("value"));
subQuery.where(criteriaBuilder.equal(join.<String> get("key"), "key11"));
query.where(criteriaBuilder.equal(criteriaBuilder.any(subQuery), "value11"));
Query q = em.createQuery(query);
List<Document> docs = q.getResultList();
PersistenceManager.INSTANCE.close();
But I got this exception:
Exception Description: The query has not been defined correctly, the
expression builder is missing. For sub and parallel queries ensure
the queries builder is always on the left. Query:
ReadAllQuery(referenceClass=Document ) at
org.eclipse.persistence.exceptions.QueryException.invalidBuilderInQuery(QueryException.java:689)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.appendFromClauseToWriter(SQLSelectStatement.java:537)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1704)
Any help would be appreciated!
Related
I have the following tables:
customers
orders
#Entity
public class Customer {
String id;
}
#Entity
public class Order {
String id;
String customerId;
}
I have no use to establish an entity's mapping between those; however I need a query to that should join these two tables:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Customer> criteriaQuery = criteriaBuilder.createQuery(Customer.class);
final Root<Customer> root = criteriaQuery.from(Customer.class);
final Join<Order, Customer> joinOrder = root.join(Order_.customerId.getName()); // doesn't work
final TypedQuery<Customer> queryData = entityManager.createQuery(
criteriaQuery
.where(
criteriaBuilder.lessThan(root.get(Customer_.creationDate), date)
// should add a predicate with order properties
)
);
return queryData.getResultList();
Is it possible to do something like above with JPA 2 ?
You can use subquery
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> customerQuery =
cb.createQuery(Customer.class);
Root<Customer> customerRoot = customerQuery.from(Customer.class);
Subquery<Order> subQuery = customerQuery.subquery(Order.class);
Root<Order> orderRoot = subQuery.from(Order.class);
//Replace this with the restriction you want to apply to order
Predicate predicate= orderRoot.get("xxxxx").in(xxx, xxx);
subQuery.select(orderRoot.get("customerId")).where(predicate);
customerQuery.select(customerRoot).where(customerRoot.get("id").in(subQuery));
em.createQuery(issueQuery).getResultList();
I have the following tables with the following structure
Table A {
id <-- Primary key
someColumn
}
Table B {
id <-- Primary key
someColumn
idOfA <-- Foreign key mapping to Table A
}
Entity classes look like below
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -78448557049178402L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToMany(mappedBy = "a")
private List<B> bs = new ArrayList<>();
}
#Entity
#Table(name = "B")
public class B implements Serializable {
private static final long serialVersionUID = -659500557015441771L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToOne
#JoinColumn(name = "a_id", nullable = false)
private A a;
}
Using JPA2, I want to select records from table A which do not have a reference in Table B.
The Expected native postgres query is
select * from A a
where a.id not in
(select b.idOfA from B b);
What I have so far managed to do is
public List<A> getANotInB() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// Select From Table B
CriteriaQuery<B> criteriaQueryB = criteriaBuilder
.createQuery(B.class);
Root<B> rootB = criteriaQueryB.from(B.class);
criteriaQueryB.select(rootB);
// Select records from Table A
CriteriaQuery<A> criteriaQueryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = criteriaQueryA.from(A.class);
criteriaQueryA.select(A);
// Create predicate
Predicate predicate = rootAttemptA.in(criteriaQueryB.getSelection());
criteriaQueryA.where(criteriaBuilder.not(predicate));
// Create query
TypedQuery<A> query = entityManager.createQuery(criteriaQueryA);
List<A> as= query.getResultList();
System.out.println(as);
return as;
}
I know the code above is incorrect and I have got a lot of basics wong.
Kindly help
Note: I Want to use JPA2 Criteria Query
Try this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// Select distinct aid from B
CriteriaQuery<B> bQuery = cb.createQuery(B.class);
Root<B> bRoot = bQuery.from(B.class);
bQuery.select(bRoot.get("a").get("id")).distinct(true);
// Select * from A where aid not in ()
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> aRoot = aQuery.from(A.class);
aQuery.select(aRoot).where(cb.not(aRoot.get("id").in(bQuery)));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Basically, you will construct part of the query and glue them together.
More information here:
JPA Criteria
I was able to get it done using subquery() as below. Posting it so that it can help others
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// select a from A a
CriteriaQuery<A> queryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = queryA.from(A.class);
queryA.select(rootA);
// Select distinct aId from B
CriteriaQuery<B> subQueryB = queryA.subquery(B.class);
Root<B> rootB = subQueryB.from(B.class);
bQuery.select(rootB.get("a")).distinct(true);
queryA.where(criteriaBuilder.not(criteriaBuilder.in(rootA.get("id").value(subQueryB))));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Thanks #Mạnh for showing the way
I have a very simple entity Product which has a code, name and tags. Tags are stored in another table (product_tag) with product_id and tag columns.
I need to search for products with certain tags using CriteriaQuery. To give an example I want to find products having 'fruit' and 'red' tags.
Using spring 4.1.x, spring-data-jpa 1.8 and hibernate 4.2.x.
My entity simply is;
#Entity
#Table(name = "product", uniqueConstraints ={
#UniqueConstraint(columnNames = "code")
}
)
#NamedQueries({
#NamedQuery(name = "Product.findAll", query = "select p from Product p")
})
public class Product extends EntityWithId {
#Column(name = "code", length = 128)
private String code;
#Column(name = "name", length = 512)
protected String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="product_tag", joinColumns=#JoinColumn(name="product_id"))
#Column(name="tag")
private Set<String> productTags = new HashSet<>();
}
here is the code how I initiate the search;
private void search() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> criteriaQuery = builder.createQuery(Product.class);
Root<Product> product = criteriaQuery.from(Product.class);
Predicate where = builder.conjunction();
if (!StringUtils.isEmpty(nameSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("name"), nameSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(codeSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("code"), codeSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.get("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
criteriaQuery.where(where);
List<Product> resultList = entityManager.createQuery(criteriaQuery).getResultList();
}
However when I run the search for tags 'fruit' I get an exception
java.lang.IllegalArgumentException: Parameter value [fruit] did not match expected type [java.util.Set (n/a)]
I really wonder to use CriteriaQuery for ElementCollection and CollectionTable.
productTags is mapped to a separate table, therefore you need to join with that table in your query.
...
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.join("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
...
Note the product.join("productTags") instead of product.get("productTags")
Try to use isMember() rather than in()
Check the example 5 and 7
How can the sql expression below be expressed using CriteriaBuilder?
select * from Ref where prac_id = (select prac_id from loc l join staff_loc sl where sl.loc = l.id and sl.pracstaff_id = 123)
Model Classes
#Entity
public class Ref {
private Long id;
private Prac prac;
}
#Entity
public class Loc {
Long id;
#ManyToOne
Prac prac;
#ManyToMany
Set<PracStaff> pracStaff;
}
#Entity
public class Prac {
Long id;
#OneToMany
Set<Loc> locs;
}
#Entity
public class PracStaff {
Long id;
#ManyToMany
Set<Loc> locs;
}
There's a join table that maps Loc to PracStaff; it has two columns: pracstaff_id and loc_id
A Loc can belong to only one Prac.
What I'm trying to get is all Ref objects that have a PracStaff with id 123 using CriteriaBuilder.
Here's the solution I got to work though I haven't tested it thoroughly. Using
Expression<Collection<PracStaff>>
to return the collection is what I was missing
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Ref> criteriaQuery = criteriaBuilder.createQuery(Ref.class);
Root<Ref> from = criteriaQuery.from(Ref.class);
criteriaQuery.select(from);
Subquery<Prac> subquery = criteriaQuery.subquery(Prac.class);
Root<Loc> fromLoc = subquery.from(Loc.class);
Expression<Collection<PracStaff>> pracStaffInLoc = fromLoc.get("pracStaff");
subquery.where(criteriaBuilder.isMember({pracStaffObj}, pracStaffInLoc));
subquery.select(fromLoc.<Prac>get("prac"));
Path<Prac> specialist = from.get("{field in Ref class}");
Predicate p = criteriaBuilder.equal(specialist, subquery);
I have an Entity which looks about this:
#Entity
#Table(name = "LOG_ENTRY")
public class LogEntry implements Serializable {
...
#JoinFetch(JoinFetchType.OUTER)
#ManyToOne
#JoinColumn(name = "SERVER_ID")
private Server server;
...
}
#Entity
#Table(name = "SERVER")
public class Server extends implements Serializable {
...
#Column(name = "NAME")
private String name;
...
}
when I construct a query in the following way:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<LogEntry> criteria = builder.createQuery(LogEntry.class);
Root<LogEntry> root = criteria.from(LogEntry.class);
The resault SQL is correct:
SELECT t0.NAME FROM {oj LOG_ENTRY t1 LEFT OUTER JOIN SERVER t0 ON (t0.ID = t1.SERVER_ID)}
But when I add the orderBy to the criteria in the following manner:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<LogEntry> criteria = builder.createQuery(LogEntry.class);
Root<LogEntry> root = criteria.from(LogEntry.class);
criteria.orderBy(builder.desc(root.get("server.name")));
I am getting the following SQL (without the outer join!!!)
SELECT t0.NAME FROM SERVER t0, LOG_ENTRY t1 WHERE (t0.ID = t1.SERVER_ID) ORDER BY t0.NAME DESC
I'm using eclipselink 2.3.3 (you might noticed that since I'm using the org.eclipse.persistence.annotations.JoinFetch in my Entity)
Is there any way to solve this?
Will switching to Hibernate make it behave correctly?