Spring boot JPA & Criteria API - Select single column - java

I am trying to retrieve a single column from the table, but I am getting compilation error about return type.
SQL
select oComment from comment where oNote = note and version > 0;
I have Comment table and Note table. Comment table has comment, note and version columns. The comment itself is a note. Now I want to retrieve all comments of the note which has version greater than 0. But here I want only comment column which of note type.
Comment.java
#Entity
#Table(name="comment")
#Cache(usage=CacheConcurrencyStrategy.READ_WRITE, region="comments")
public class Comment implements Serializable {
private static final long serialVersionUID = -4420192568334549165L;
public Comment() {
}
#Id
#OneToOne
#JoinColumn(name="commentuuid",referencedColumnName="noteuuid")
private Note oComment;
#Id
#OneToOne
#JoinColumn(name="noteuuid",referencedColumnName="noteuuid")
private Note oNote;
}
Note.java
#Entity
#Table(name = "note")
#Cache(usage=CacheConcurrencyStrategy.READ_WRITE, region="notes")
public class Note implements Serializable{
private static final long serialVersionUID = 4089174391962234433L;
#Column(name="title")
private String m_szTitle;
#Column(name="htmlcontent")
private String m_sbHtmlContent;
#Column(name="textcontent")
private String m_sbTextContent;
#Id
#Column(name="noteuuid", columnDefinition="varchar(36)")
private String noteUuid;
}
CustomRepositoryMethod
public List<Note> findAllByNote(Note oNote, int iOffset, int iResultSize) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Comment> cq = cb.createQuery(Comment.class);
Root<Comment> oComment = cq.from(Comment.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(oComment.get("oNote"), oNote));
predicates.add(cb.greaterThan(oComment.get("version"), 0));
Subquery<Note> subquery = cq.subquery(Note.class);
Root<Note> note = subquery.from(Note.class);
cb.desc(note.get("m_dtCreationDate"));
cq.where(predicates.toArray(new Predicate[0]));
cq.multiselect(oComment.<Note>get("oComment"));
return (List<Note>)em.createQuery(cq).setFirstResult(iOffset).setMaxResults(iResultSize).getResultList();
}
error
Error at return statement,
Cannot cast from List<Comment> to List<Note>

in CustomRepositoryMethod replace first
line CriteriaQuery<Comment> cq = cb.createQuery(Comment.class); to CriteriaQuery<Note> cq = cb.createQuery(Note.class)
cb.createQuery parameter accept result Class in docs you can see.
update
// assuming query like
// select oComment from comment inner join Note on comment.noteuuid=Note.noteuuid where Note.noteUuid = 1 and version > 0;
CriteriaBuilder cb = em.getCriteriaBuilder();
// data type of oComment
CriteriaQuery<Note> query = cb.createQuery(Note.class);
// from comment
Root<Comment> comment = query.from(Comment.class);
//join
Join<Comment, Note> note = comment.join(comment.get("oNote"));
//version Condition
Predicate version=cb.greaterThan(comment.get("version"),0 );
//Note condition
predicate note=cb.equal(note.get("noteuuid"),note.getNoteUuid());
// get oComment and where condtion
query.select(comment.get("oComment")).where(cb.and(version,note));
return em.createQuery(query).setFirstResult(iOffset).setMaxResults(iResultSize).getResultList();

Your criteria query's root is Comment not Note
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Comment> cq = cb.createQuery(Comment.class);
Root<Comment> oComment = cq.from(Comment.class);
and you are trying to do
return (List<Note>)em.createQuery(cq).setFirstResult(iOffset)
.setMaxResults(iResultSize).getResultList();
the compilation error is inevitable in this scenario, because em.createQuery(cq).getResultList() will return List<Comment> not List<Note>

It is not necessary to write a custom repository method since the one you are creating is already generated in spring-data.
If your repository extends the CrudRepository you will be given that method you are looking for for free.
The pattern is findAllBy[propertyOfClass].
But please be aware of that you actually have no Collection of NOTE in your entity.
Perhaps you should first change the OneToOne association into a OneToMany.

Can be built as a criteria query as follows:
CriteriaQuery<Country> q = cb.createQuery(Country.class);
Root<Country> c = q.from(Country.class);
q.select(c.get("currency")).distinct(true);
The select method takes one argument of type Selection and sets it as the SELECT clause content.

Related

JPA Criteria query to fetch data from master and child table with entitygraph

I am trying to fetch data from parent to child both based on filter criteria using JPA Criteria query so that can avoid multiple queries to DB, but not able to achieve desired result. Following are my sample entities( without getters/setters)
#Entity
public class ParentTable implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "KEY_COLUMN",length = 30)
private String keyColumn;
#Column(name = "CODE",length = 30)
private String code;
#Column(name = "KEY_DESC",length = 240)
private String desc;
#OneToMany(mappedBy = "parentTable",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ChildTable> childTableList;
}
#Entity
public class ChildTable implements Serializable{
private static final long serialVersionUID = 1L;
public ChildTable() {
super();
}
#Id
#Column(name = "ID",length = 80)
private String id;
#Column(name = "PARENT_KEY_COLUMN",length = 30,insertable = false,updatable = false)
private String parentKeyColumn;
#Column(name = "CHILD_CODE",length = 30)
private String childCode;
#Column(name = "CHILD_DESC",length = 240)
private String chldDesc;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_KEY_COLUMN", referencedColumnName = "KEY_COLUMN")
private ParentTable parentTable;
}
Criteria builder snippet -
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> query = cb.createQuery(ParentTable.class);
Root<ParentTable> fromParent = query.from(ParentTable.class);
Join<ParentTable, ChildTable> details = fromParent.join("childTableList");
List<Predicate> conditions = new ArrayList();
conditions.add(cb.equal(details.get("childCode"), childCode));
conditions.add(cb.equal(details.get("chldDesc"),chldDesc));
TypedQuery<ParentTable> typedQuery = em.createQuery(query.select(fromParent).where(conditions.toArray(new Predicate[] {})));
List<ParentTable> parentTableList = typedQuery.getResultList();
This executes and gives result of parent table only, if i fetch childtable data I can see JPA query getting exceuted again, can this be avoided and fetch list of child entities which matches 3 dynamic params? 1. ParentTable.code, 2. ChildTable.childCode , 3. ChildTable.chldDesc .
Can anyone help me to construct JPA query like below which executes in one DB hit instead of multiple round trip, which is happening in above snippet of code?
select * from ParentTable p,ChildTable c where p.KEY_COLUMN=c.PARENT_KEY_COLUMN and p.CODE=? and c.CHILD_CODE=? and c.CHILD_DESC=?
Update :
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> cq = builder.createQuery(ParentTable.class);
Root<ParentTable> root = cq.from(ParentTable.class);
Join<ParentTable, ChildTable> join = root.join("childTableList");
Predicate p1=builder.equal(root.get("code"), "code");
Predicate p2=builder.like(join.get("chldDesc"), "%chldDesc%");
Predicate p3=builder.equal(join.get("childCode"), "childCode");
Predicate andPredicate = builder.and(p1,p2, p3);
cq.select(root).where(andPredicate);
EntityGraph<ParentTable> fetchGraph = entityManager.createEntityGraph(ParentTable.class);
fetchGraph.addSubgraph("childTableList");
List<ParentTable> parentTableList=entityManager.createQuery(cq).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
parentTableList.forEach(System.out::println);
This approach forms the expected query like above mentioned but again one more query is formed like below , why second query is triggered even-though its not required?
select * from ParentTable parentTable0_ where parentTable0_.code=?
JPA is required to give you managed entity results that reflect the data in the database. The filter you put on the query does not filter the internal relationships.
Your 'join' clause only affects the filter applied to returning ParentTable entities. Every entity returned will have a complete 'childTableList' collection, so even though you are returning ParentTable instances that have specific codes and descriptions, the childTableList shows all its children. That is JPA for you, and specific providers do have a way to filter these mapped collections (AdditionalCriteria ) IMO they are a bad route with many problems.
If you want to have childTableEntries that match the specific codes and descriptions, your query should be more of the form (using JPQL)
"Select c, p from ChildTable c join c.parentTable p where c.childCode = :code and c.chldDesc = :desc"
This will return you a List result, where the Object array has the child and parent entries for each row that matches. So duplicate parents if one has more than one child that matches.
Otherwise, the extra query is being caused by accessing the childTableList on the parentTable entries, because they are marked lazy. Your criteria query is specifying 'join', as you want to use the childTableList entries in filtering parentTable entities. If you want the childTableList fetched with the parentTables, you need to use fetchJoins. Root implements FetchParent, which would allow you to specify a 'fetch' on the childTableList in addition to the join you've defined. In JPQL, something like:
"select p from ParentTable p fetch join p.childTableList, join p.childTableList c where c.childCode = :code and c.chldDesc = :desc"

ANY clause on a subquery using jpa criteria API

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!

Hibernate criteria join table issue

I have 3 entities as you can see below. I want to write a query that fetches products. In this query the parameter is a list of optionValues id.
now my question is how to join these entities?
Product:
public class Product{
//other col
#OneToMany(mappedBy = "product")
private Set<Attribute> attributeSet = new HashSet<>();
}
Attribute:
public class Attribute{
#OneToOne
#JoinColumn(name = "OPTION_VALUE_ID")
private OptionValue optionValue;
#ManyToOne
#JoinColumn(name="PRODUCT_ID",referencedColumnName="id")
private Product product;
}
optionValue:
public class OptionValue{
#Column(name = "id")
private Long id;
#Column(name = "value",updatable = true)
private String value;
}
I wrote a query but I think my code is not a good solution.
Criteria aCriteria = null;
if (!optionValueList.isEmpty()) {
aCriteria = currentSession().createCriteria(Attribute.class, "attribute");
aCriteria.createAlias("attribute.optionValue", "optionValue");
aCriteria.add(Restrictions.in("optionValue.id", optionValueList));
attributes = aCriteria.list();
}
PagingData<Product> pagingData = new PagingData<>();
Criteria criteria = currentSession().createCriteria(Product.class, "product");
if (!attributes.isEmpty()) {
for (Attribute attribute:attributes){
longList.add(attribute.getId());
}
criteria.createAlias("product.attributeSet", "attribute");
criteria.add(Restrictions.in("attribute.id", longList));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
The general idea is to start with creating criteria of objects you want to return, and travel further by adding criteria which is joined. So I start with Parent class, add qualifiers and end up with most nested element, OptionValue.
Code below is untested, but you should get the idea:
Criteria criteria = currentSession()
.createCriteria(Product.class)
.createCriteria("attributeSet", "join_between_product_and_attribute");
if (!attributes.isEmpty()) {
Set<String> attributeIds = new HashSet<>();
for (Attribute attribute : attributeList) {
attributeIds.add(attribute.getId());
}
criteria.add(Restrictions.in("id", attributeIds));
}
criteria = criteria.createCriteria("optionValue", "join_between_attribute_optionvalue");
if (!optionValueList.isEmpty()) {
criteria.add(Restrictions.in("id", optionValueList));
}
an even easier solution would be to use a CriteriaQuery. i did not test the following code, but i think it should work correctly. it requires hibernate 5, but also works with some modifications in hibernate 4:
CriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return sessionFactory.getCurrentSession().createQuery(query).getResultList();
i am assuming, that you can access the optionValueList since you posted it in your question.
For the solution with EntityManager i am assuming you already were able to instantiate one.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return entityManager.createQuery(query).getResultList();
if you have an EntityManagerFactory, replace the first entityManager with it and the second one with entityManagerFactory.createEntityManager()

Using field of type ListAttribute of static metamodel in a CriteriaQuery

I have the following entity
#Entity
#Table(name = "merchants")
public class Merchant implements Serializable {
...
#ElementCollection
#CollectionTable(name = "keywords", joinColumns = #JoinColumn(name = "merchant_id"))
private List<String> keywords;
...
}
which has corresponding static metamodel
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(Merchant.class)
public abstract class Merchant_ {
...
public static volatile ListAttribute<Merchant, String> keywords;
...
}
How do I write queries using CriteriaBuilder to check if keywords attribute contains given value?
It turned out to be pretty straightforward
String keyword = "<KEYWORD>";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Merchant> query = cb.createQuery(Merchant.class);
Root<Merchant> root = query.from(Merchant.class);
query.select(root).where(
cb.isMember(keyword, root.get(Merchant_.keywords)));
return em.createQuery(query).getResultList();
But original task is to find entities which contain at least one keyword from a given collection. For now I don't see any better solution rather that unite isMemeber predicate with or clause for each item in keyword collection. However there are not so many of them are expected to be.
But any better solution is appreciated.

Resolving Criteria on Polymorphic child class attribute jpa hibernate query

Using hibernate 3.6.10 with hibernate jpa 2.0.
My problem boils down to needing to set some criteria on a column of a child object during a somewhat complex joining query.
I have a set of objects similar to:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Ball
{
private String name;
//...getter and setter crud...
}
#Entity
public class BeachBall extend ball
{
private boolean atTheBeach;
//...getter and setter crud...
}
#Entity
public class SoccerBall extend ball
{
private int numberOfKicks;
//...getter and setter crud...
}
#Entity
public class Trunk
{
private Set<Ball> balls;
#OneToMany(mappedBy = "trunk", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Ball> getBalls()
{
return balls;
}
}
#Entity
public class Car
{
private Trunk trunk;
private String carModel;
//...getter and setter crud...
}
Now i need to query how many soccer balls have 20 kicks in a car with a specific model.
Using JPA I tried to do something like:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(ballJoin.get("numberOfKicks"), 20)};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
At runtime the above code dies because numberOfKicks is only on soccerballs and due to how its typed in Trunk it only knows about ball. If i manually create a from on the soccerballs and setup criteria to join it i can query numberOfKicks, however i feel like there must be a way to inform the query that the set is in fact a set.
Please note i cannot post any of the actual code so all above examples are just examples.
Using JPA and hibernate like above is there a way to force hibernate to know that the set< ball > is actually set< soccerball >?
Due to time restrictions i'm taking the easy way out :(. If anyone can answer better then what i have i'll gladly choose their answer over mine.
To make the criteria api recognize that i'm looking for the inherited table i changed my query code to be:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Root<Soccerball> soccerballs = criteriaQuery.from(SoccerBall.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(soccerball.get("numberOfKicks"),20), criteriaBuilder.equal(soccerball.get(SoccerBall_.id),car.get(Car_.id))};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
The following retrieves all Cars with nested list attributes satisfying equality criteria for subclass type in a collection and equality on root element.
I've modified the query to work with the datamodel in the original question.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> carQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> carRoot = carQuery.from(Car.class);
Subquery<SoccerBall> ballQuery = carQuery.subquery(SoccerBall.class);
Root<SoccerBall> soccerBall = ballQuery.from(SoccerBall.class);
ballQuery.select(soccerBall);
ballQuery.where(criteriaBuilder.equal(soccerBall.get(SoccerBall_.numberOfKicks), 25));
Join<Car, Trunk> carTrunkJoin = carRoot.join(Car_.trunk);
SetJoin<Trunk, Ball> trunkBallJoin = carTrunkJoin.join(Trunk_.balls);
carQuery.select(carRoot);
carQuery.where(criteriaBuilder.and(
trunkBallJoin.in(ballQuery),
criteriaBuilder.equal(carRoot.get(Car_.carModel), "Civic")));
TypedQuery<?> typedQuery = entityManager.createQuery(carQuery);
List<?> result = typedQuery.getResultList();
The equivalent SQL is:
SELECT * FROM car JOIN trunk JOIN ball WHERE ball.id IN (SELECT soccerball.id FROM soccerball WHERE soccerball.numberOfKicks = 25)

Categories