JPA: Query an embeddable List inside an entity - java

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

Related

How to properly get values in OneToMany relationship based on field of "many" side?

Let's say I have an entity
Restaurant
with field:
#OneToMany(mappedBy = "restaurant", fetch = FetchType.LAZY)
private Set<MenuEntry> menu;
MenuEntry entity has next field:
#Column(name = "date_time_created", nullable = false)
private LocalDate dateCreated;
which obviously represents date when entry was created.
I need to write JPQL query which would give me Restaurant where set of menus includes entries with specified dateCreated.
I've written method with next annotations:
#EntityGraph(attributePaths = {"menu"}, type = EntityGraph.EntityGraphType.LOAD)
#Query("SELECT r FROM Restaurant r WHERE r.id=:restaurantId AND r.menu
IN (SELECT m FROM MenuEntry m WHERE m.dateCreated=:dateCreated)")
But when I try to perform this query I have "malformed numeric constant" exception. Could you please explain me, how to properly get values in my situation ? Thank you!
Select r from Restaurant r JOIN r.menu m where m.dateCreated=:dateCreated and r.id=:restaurantId
Try this... This will work... No need to use inner query

Using hibernate #Formula to fetch a collection

I have a similar db structure. The only difference that I have more tables on the path from A to C entity:
And I have following mapping for this structure:
#Entity
#Table(name = "a")
class A {
#Id
private int id;
private String title;
#ElementCollection(targetClass=String.class)
#Formula("(select (c.useful_information) from A a " +
"join B b on a.id = b.a_id " +
"join C c on b.id = c.b_id " +
"where a.id = id)")
private List<String> usefulStuff;
}
My aim is to get a list of all useful things from table C in entity A.
But I get syntax errors.
Could you say what's wrong in my example? And maybe you know better way for this purpose?
The problem in your #Formula annotation is the "from A a". The value passed to this annotation is actually SQL, not JPQL.
Thus, if you want to alias the table to reference it elsewhere, you need to rather write FROM A AS a.

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.

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.

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

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

Categories

Resources