Hibernate Criteria API - adding a criterion: string should be in collection - java

I have to following entity object
#Entity
public class Foobar {
...
private List<String> uuids;
...
}
Now I'd like to make a criteria query which would fetch all Foobar pojos whose uuids list contains the string "abc123", I'm just not sure how to make the appropriate criterion.

I assume you are using a version of Hibernate that implements JPA 2.0. Here's a JPA 2.0 solution that should work with any compliant implementation.
Please annotate uuids with JPA's #ElementCollection annotation. Don't use Hibernate's #CollectionOfElements as mentioned in some of the other answer comments. The latter has equivalent functionality but is being deprecated.
Foobar.java will look approximately like this:
#Entity
public class Foobar implements Serializable {
// You might have some other id
#Id
private Long id;
#ElementCollection
private List<String> uuids;
// Getters/Setters, serialVersionUID, ...
}
Here's how you can build a CriteriaQuery to select all Foobars whose uuids contain "abc123".
public void getFoobars() {
{
EntityManager em = ... // EM by injection, EntityManagerFactory, whatever
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<Foobar> cq = b.createQuery(Foobar.class);
Root<Foobar> foobar = cq.from(Foobar.class);
TypedQuery<Foobar> q = em.createQuery(
cq.select(foobar)
.where(b.isMember("abc123", foobar.<List<String>>get("uuids"))));
for (Foobar f : q.getResultList()) {
// Do stuff with f, which will have "abc123" in uuids
}
}
I made a self-contained proof-of-concept program while playing with this. I can't push it out right now. Please comment if you want the POC pushed to github.

I know this is old question, but I have just encountered this issue and found solution.
If you want to use Hibernate Criteria you can join your uuids collection and use its property elements to match elements. Just like that:
session.createCriteria(Foobar.class)
.createAlias("uuids", "uuids")
.add(Restrictions.eq("uuids.elements", "MyUUID"))
.list()

You could use a Query as in the example below or you could convert this to a NamedQuery. Unfortunately there doesn't seem to be a way to do this with Criteria.
List<Foobar> result = session
.createQuery("from Foobar f join f.uuids u where u =: mytest")
.setString("mytest", "acb123")
.list();

I've found this post from one year ago, and I've made this method, if it can help anybody with the same problem I had a few hours ago.
Public List<EntityObject> getHasString(String string) {
return getSession().createCriteria(EntityObject.class)
.add(Restriction.like("property-name", string, MatchMode.ANYWHERE).ignoreCase();
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Made the same with a group of strings too.
public List<EntityObject> getByStringList(String[] tab) {
Criterion c = Restrictions.like("property-name", tab[tab.length-1], MatchMode.ANYWHERE).ignoreCase();
if(tab.length > 1) {
for(int i=tab.length-2; i >= 0 ; i--) {
c = Restrictions.or(Restrictions.like("property-name",tab[i], MatchMode.ANYWHERE).ignoreCase(), c);
}
}
return getSession().createCriteria(EntityObject.class)
.add(c)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
}
It works with "or" statements, but can easily be replaced by "and" statements.

What you are asking is not supported out of the box by hibernate. See http://opensource.atlassian.com/projects/hibernate/browse/HHH-869
Here is a workaround available in the jira ticket :
entityCriteria.add(Restrictions.sqlRestriction(
"fooAlias.id in (select e.id from foobar_table e, values_table v" +
" where e.id = v.entity_id and v.field = ?)", "abc123"), Hibernate.String)) ;

The solution with the sqlRestriction from jira
http://opensource.atlassian.com/projects/hibernate/browse/HHH-869
seemed the best way to go for me since i heavily use criteria api. I had to edit Thierry's code so it worked in my case
Model:
#Entity
public class PlatformData
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long iID;
private List<String> iPlatformAbilities = new ArrayList<String>();
}
Criteria call:
tCriteria.add(Restrictions.sqlRestriction(
"{alias}.id in (select e.id from platformData e, platformdata_platformabilities v"
+ " where e.id = v.platformdata_id and v.element = ? )", aPlatformAbility.toString(),
Hibernate.STRING));

For starters, I don't think Hibernate can map a List<String>. However, it can map a list of other entities.
So if your code was something like this:
#Entity
public class Foobar {
private List<EntityObject> uuids;
...
}
And the EntityObject has a String-property called str, the criteria could look like this:
List<Foobar> returns = (List<Foobar>) session
.createCriteria.(Foobar.class, "foobars")
.createAlias("foobars.uuids", "uuids")
.add(Restrictions.like("uuids.str", "%abc123%"))
.list();

Related

How to test if Enum value is in EnumSet using JPQL

I have a property as follows:
#Entity
class Project implements Serializable {
#Convert(converter = TypeFlattener.class)
#Column(name = "assignable_types")
private EnumSet<Type> assignableTypes;
The point of the TypeFlattener is that I want to avoid normalization for a simple list, so basically it converts the EnumSet to/from a simple String with comma separated values matching the Enum.name()
Now the question is how can I filter only Projects that have the a specific Type in assignableTypes?
My best approach would be something like that:
#NamedQuery(
name="Project.findByType",
query="SELECT p FROM Project p WHERE :t IN p.assignableTypes"
)
What do I pass in for :t? The String version as follows? Can I even do this?
q.setParameter("id", "MAJOR");
The converter does not matter here as he only affects the database representation. The below query using MEMBER OF should work.
TypedQuery<Project> query = em.createQuery(
"SELECT p FROM Project p WHERE :type MEMBER OF p.assignableTypes", Project.class);
query.setParameter("type", Type.ONE);
List<Project> resultMemberOf = query.getResultList();

JPA left outer join using CriteriaBuilder results in error: Partial object queries are not allowed to maintain the cache or be edited

I have the following entity relationships
Product
#Entity
#Table(name="PRODUCTS")
public class Product
#Id
#Column(name="product_id")
private long productId;
#ManyToOne
#JoinColumn(name="EMP_NUMBER")
private Employee employee3;
....
....
Employee
#Entity
#Table(name="EMPLOYEES")
public class Employee
#Id
#Column(name="EMP_NUMBER")
private String empNumber;
#Column(name="EMP_NAME")
private String employeeName;
#OneToMany(mappedBy="employee3")
private List<Product> Product3;
....
....
In DAOImpl class I have the following
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Product> cq =
cb.createQuery(Product.class);
Root<Product> c = cq.from(Product.class);
Join<Product, Employee> join =
c.join(Product_.employee3, JoinType.LEFT);
cq.multiselect(c.get(Product_.productId),
c.get(Product_.employee3));
However when I execute, I am getting the following errors
org.eclipse.persistence.exceptions.QueryException
Exception Description: Partial object queries are not allowed to maintain
the cache or be edited.
You must use dontMaintainCache().
Query: ReadAllQuery(referenceClass=Product )
at org.eclipse.persistence.exceptions.QueryException.cannotCachePartialObjects
(QueryException.java:388)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.prepareQuery
(ObjectLevelReadQuery.java:2160)
What I am trying to achieve is to generate the following SQL
SELECT p.product_id,
emp.emp_name
FROM products p
LEFT OUTER JOIN employees emp
ON (p.emp_number = emp.emp_number)
How can I do this and get rid of errors?
I have resolved the issue by adding the below mentioned line, might be useful to others if they encounter same problem what been mentioned in question.
((org.eclipse.persistence.jpa.JpaQuery)query).getDatabaseQuery().dontMaintainCache();
Another better option is provided here
Thanks
According to this, there are two ways to create a criteria query createQuery(class) and createQuery() without the parameter. The first method creates a CriteriaQuery cast the result to class.
This means to resolve the problem, you can change CriteriaQuery<Product> cq = cb.createQuery(Product.class); to this one CriteriaQuery<Product> cq = cb.createQuery();.
And then use an Iterator to retrieve the result.
Iterator products = query.getResultList().iterator();
In my case and according to this: https://bugs.eclipse.org/bugs/show_bug.cgi?id=303205
The issue happens when you apply a multiselect and don't have a constructor with the selected parameters in the representation object.
For instance if you have
Select empNumber,employeeName from Employee
Then in your entity or representation object you need a constructor:
public Employee(int empNumber, StringemployeeName ){...}
Be careful since the order of the parameters matter.

JPA - is it possible to get result as an instance of #MappedSuperclass?

Regarding the following example, is it possible to retrieve list of AnsweredQuestion instances as objects of Question?
#MappedSuperclass
public abstract class Question{
#Column(name="TITLE")
private String title;
...
}
#Entity
#Table(name="ANSWEREDQUESTION")
public class AnsweredQuestion extends Question
{
#Column(name="ANSWER")
private String answer;
...
}
It is very important for me to retrieve only a few columns since the descendant class has many.
I tried something as follows, but it still returns list of AnsweredQuestion:
queryStr = " select q from AnsweredQuestion q where ..."
TypedQuery<Question> query = entityManager.createQuery(queryStr, Question.class);
return query.setParameter( ... ).getResultList();
If you need to return a few fields, you can also select them and use the new operator:
TypedQuery<Sample> query = entityManager.createQuery("select new com.acme.sample.Sample(e.fieldA, e.fieldB) from AnsweredQuestion e", Sample.class);
return query.setParameter( ... ).getResultList();
JPA implementation will look for a Sample constructor (the path must be complete) and invoke it while transforming the result. It is pretty handy (at the cost of creating new classes to represent the result) to avoid returning everything the database has to return :)

JPA: Query an embeddable List inside an entity

I am trying to "extract" Embeddable classes based on some criterias from a list in an Entity. Either with the help of JPQL or Criteria API. I am not a pro at this, so please help me out. Been googling for 4 hours solid for an answer without any results.
These are the classes:
#Entity
public class PostOffice {
#Id
private Long id;
#ElementCollection(fetch=FetchType.LAZY)
#CollectionTable(joinColumns=#JoinColumn(name = "CARRIERID"))
private List<PostalCarrier> carriers;
}
#Embeddable
public class PostalCarrier {
#JoinColumn(name = "area")
private Area area;
}
#Entity
public class Area {
#Id
private int code;
}
So, basically what I'm trying to achieve is something like this.
TypedQuery<PostalCarrier> query = entityManager.createQuery("SELECT p.carriers FROM PostOffice p
WHERE p.id = ?1 AND p.carriers.area.code = ?2", PostalCarrier.class);
query.setParameter(1, postOfficeId);
query.setParameter(2, areaCode);
I only want to get a list of the PostalCarriers at specific area code from a specific PostOffice.
Any help is much appreciated! :)
I think I'm almost there, but keep getting the following error:
Error compiling the query [SELECT h FROM PostOffice p INNER JOIN p.carriers h
WHERE p.id = ?1 AND h.area.code = ?2], line 1, column 71: unknown state or
association field [area] of class [com.test.PostalCarrier].
You must make a join to the PostalCarrier. You can't access a property from a collection. Thus, postalcarrier.area isn't correct.
select postalcarrier from PostOffice p
inner join p.carriers postalcarrier
where p.id = :postOfficeId
and postalcarrier.area.code = :areaCode

problem with "select new Object ... join ... where"

I'm having a problem with an HQL query
Three classes
ClassOne is my BusinessObject
public class ClassOne {
private int id;
private int status;
private Set<ClassTwo> classTwos;
+ other fields/getters/setters/constructor etc
}
ClassTwo is referenced in a set of ClassOne and is kind of the history of an object of ClassOne
public class ClassTwo {
private int id;
private int oldStatus;
private int newStatus;
private String message;
//+ getters/setters/constructor etc
}
ClassThree is my DTO/VO with just one classTwo (not the whole history)
public class ClassThree {
private int id;
private int status;
private ClassTwo classTwo;
public ClassThree(int pId, int pStatus, ClassTwo pClassTwo) {
id=pId;
status=pStatus;
classTwo=pClassTwo;
}
//+ getters/setters etc
}
Now I'd like to create an HQL query like this:
I'd like to get all objects of ClassThree with a certain status and if it exists the newest ClassTwo with a certain newStatus.
For example:
I'd like to get all the DTOs (ClassThree) of ClassOne whose status is now 1, but earlier in their history it has been 2 and I'd like to have the latest ClassTwo object which has 2 as newStatus.
SELECT new ClassThree(c1.id, c1.status, c2)
FROM ClassOne c1
LEFT JOIN c1.classtwos c2 (...)
and (...) is where I don't know what to do, I'm not even sure if it's a join / join fetch
Looked around and tried quite a lot already, but no clue. Especially with the join fetch I get some Hibernate errors like org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list.
Fetching the BusinessObject like that is no problem
SELECT distinct(c1)
FROM ClassOne c1
LEFT OUTER JOIN FETCH c1.classtwos c2
and I get the ClassTwos as my field.
Thanks in advance,
Jacob
P.S.: One thing might be important, ClassTwo has no reference to ClassOne!!
P.P.S : The simple SQL query which resolves my problem looks more or less like that:
select * from classone as c1 left join (select * from classtwo where newstatus = 2) c2 on c1.id=c2.id_classone whete c1.status = 1
This query works and gets all the information needed on my PostGreSQL DB, but I'd really like to have an HQL to continue to work with, especially for maintenance reasons and so on...
Update with workaround solution:
Getting the ids of all the ClassOnes with a status 1
Collection<Integer> ids = null;
ids = (Collection<Integer>) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session pSession) throws HibernateException, SQLException {
return getDocumentIds(pSession, pStatus);
}
}
);
Now I get all the DTOs which have been in status 2 (thanks to Ivan) with:
Named query Document.dto.with.transfer
SELECT new DocumentDTO(d.id, d.status, histo)
FROM Document d
LEFT JOIN d.histories histo
WHERE
d.id in (:ids)
AND
(histo.id =
SELECT MAX(innerhisto.id)
FROM Document innerd
JOIN innerd.histories innerhisto
WHERE d.id = innerd.id AND innerhisto.newStatus = 21)
(in my code I use some named queries)
List<DocumentDTO> lRes = new ArrayList<DocumentDTO>();
Query lQuery = getSession(false).getNamedQuery("Document.dto.with.transfer");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
afterwards I remove all the IDs already found from my list ids
for (DocumentDTO dto : lResultList) {
ids.remove(dto .getId());
}
I do a third query using a second constructor for the DTO, initializing my history with a dummy-object.
Named query Document.dto.simple
SELECT new DocumentDTO(d.id, d.status)
FROM Document d
WHERE
d.id in (:ids)
(another named query)
lQuery = getSession(false).getNamedQuery("Document.dto.simple");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
and it's done.
To include Documents with no history we should use LEFT JOIN and test for empty collection, then we use subquery (SELECT COUNT(...)) to detect all documents that have never been in status 2. The last OR-clause is for fetching the last history with the specified status.
Here is the HQL query:
SELECT new DocumentDto(doc.id, doc.status, hist)
FROM Document doc
LEFT JOIN doc.histories hist
WHERE doc.status = :docStatus
AND (size(doc.histories) = 0
OR (SELECT COUNT(innerhist)
FROM Document innerdoc JOIN innerdoc.histories innerhist
WHERE innerdoc.id=doc.id AND innerhist.newStatus = :historyStatus) = 0
OR (hist.newStatus = :historyStatus AND hist.id =
(SELECT max(innerhist.id)
FROM Document innerdoc
JOIN innerdoc.histories innerhist
WHERE innerdoc.status = :docStatus AND innerhist.newStatus = :historyStatus))
Then call setParameter("historyStatus", 2) and setParameter("docStatus", 1) on your query to get the correct result.
That's it!
Please note, I've made an assumption, that we can use a value of id attribute of History as an indicator of the order in which objects were put in your database.

Categories

Resources