Hibernate criteria join table issue - java

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()

Related

Spring boot JPA & Criteria API - Select single column

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.

Hibernate to filter or remove child objects

Hibernate to filter child object which does not match criteria condtion
I am facing one issue regarding child object remove.
I am applying filter on child object and i want that object only but it is fatching all the object. Please find below
example
In that I have applied filter on Dogs name with Yellow. So I want only yellow object of Dogs. But when I iterating with Parent Object in that case p.getDogs(). It will give all the list of that relation ship. I want list of Dogs object which name would be only 'Yellow'
public class TestClass {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Test");
createDatabaseDate(emf);
EntityManager em = emf.createEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> root = cq.from(Person.class);
cq.select(root);
Path<String> name = root.join("dogs").get("name");
cq.where(cb.and(cb.equal(name, "Yellow")));
TypedQuery<Person> query = em.createQuery(cq);
for(Person p : query.getResultList()){
for(Dog dog : p.getDogs()){
System.out.println(dog.getName());
}
}
System.out.println(query.getResultList());
emf.close();
}
}
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String nickName;
private Integer age;
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Dog> dogs;
}
Thanks
I have gone through all solution but i could not find the solution regarding criteria condition. For the alternative solution I have written jpa query and then i iterate through the object of that result.
Please find my solution as below.
String hql ="select p.id, p.name,d.name from Person p join fetch p.dogs d where d.name = :name ";
Query query = em.createQuery(hql);
query.setParameter("name", "Yellow");
List<Object[]> resultList = query.getResultList();
for (Object[] obj : resultList) {
System.out.println((Integer)obj[0]);
System.out.println((String)obj[1]);
System.out.println((String)obj[2]);
}
By doing this you will only get required child object not all the child object.
Thanks

How to use CriteriaQuery for ElementCollection and CollectionTable

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

Hibernate criteria implementation for this entity model (subquery, self-join)

Given the following entity one-to-many model:
One Repository can be linked to many AuditRecords.
Many AuditRecords can all link to the same Repository
#Entity
class AuditRecordEntity {
private AuditRepositoryEntity auditRepository;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
public AuditRepositoryEntity getAuditRepository() {
return auditRepository;
}
...
}
#Entity
class AuditRepositoryEntity {
private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();
#OneToMany(mappedBy = "auditRepository")
public List<AuditRecordEntity> getAuditRecords() {
return auditRecords;
}
...
}
Minor correction, in ERD diagram below, for 'repositoryId', read 'auditRepository'
I am trying to get the Criteria API implementation to:
Get the latest (by accessTime) AuditRecord for each distinct Repository? I.e. a list of AuditRecords, one for each Repository, where the AuditRecord is the last AuditRecord for that Repository (in the case where a Repository has many AuditRecords).
I have the HQL query to do this:
select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)
But need to use the Criteria APi instead:
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Object> query = builder.createQuery();
Root<AuditRecordEntity> root = query.from(AuditRecordEntity.class);
// what next?
I have got this to work(around) by using the output from the HQL query as input to the criteria API:
final List<UUID> auditRecordIds = execute("select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)")
Root<AuditRecordEntity> root = criteriaQuery.from(AuditRecordEntity.class);
criteriaQuery.select(root);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(root.get("id").in(auditRecordIds.toArray()));
entitySearchCriteria.addPredicates(predicates);
...

Hibernate query by example

I have class A that has some primitive attributes and also member of type B.
type B has a map:
// mapping name to number
private Map<String, Double> myMap = null;
#ElementCollection(fetch=FetchType.EAGER)
#MapKeyColumn(name = "NAME")
#Column(name = "NUMBER")
#CollectionTable(name = "NAME_MAPPING", uniqueConstraints = { #UniqueConstraint(columnNames = { "NAME", "NUMBER" }) })
public Map<String, Double> getMyMap()
{
return this.myMap;
}
Snippet of A:
private String name = null;
private B b = null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "B_FK")
public B getB()
{
return b;
}
Now I want to find A by Example. I defined the following:
public List<A> findByExample(A a)
{
Session session = getSession();
Criteria criteria = session.createCriteria(A.class);
Example example = Example.create(a);
Criteria bCriteria = criteria.createCriteria("b");
B b = material.getB();
bCriteria.add(Example.create(b));
criteria = criteria.add(example);
criteria = criteria.setFetchMode("b", FetchMode.JOIN);
return criteria.list();
}
I tried all kinds of variations but with no success. the method returns all DB entries with the same A.name and ignore the equality of the Map in B.
any clue on what am I doing wrong?
Thanks,
Ronen.
The Example restrictions ignore associations (and, although this is not documents, element collections). Even if you used just the Criteria API (without using Example), element collections can't be queries with Criteria (se https://hibernate.onjira.com/browse/HHH-869). You'll have to revert to HQL or SQL restrictions.

Categories