Eclipselink problem with subquery in where clause - jpa

I have the following structure: (I am using eclipselink 2.6.0)
#Entity
public class A {
#Id
private double id;
...
#OneToMany
private List<B> b;
#OneToMany(mappedBy = "a")
private List<C> c;
...
}
#Entity
public class B {
#Id
private double id;
private boolean hidden;
}
#Entity
public class C {
#Id
private double id;
private String label;
private String value;
#ManyToOne
private A a;
}
I need to find A that are not hidden, and has s1 = value1, s2 = value 2, this is the implementation using criteria:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<A> root = cq.from(A.class);
Join<A, B> property = root.join(A_.b, JoinType.LEFT);
String currentUser = ctx.getCallerPrincipal().getName();
property.on(cb.equal(property.get(B_.creator), currentUser));
C af = new C("value1", "value2");
Subquery<B> sq = cq.subquery(B.class);
Root<C> sqRoot = sq.from(C.class);
sq.where(cb.and(cb.equal(sqRoot.get(C_.A), root),
cb.equal(sqRoot.get(C_.label), af.getLabel()),
cb.like(sqRoot.get(C_.value), "%" + af.getValue() + "%")));
sq.select(sqRoot);
cq.where(cb.and(
cb.exists(sq),
cb.or(cb.isNull(property.get(B_.id)),
cb.equal(property.get(B_.hidden), false))));
cq.select(root).distinct(true);
The generated query is:
SELECT DISTINCT t1.ID
FROM B t0, A t1
WHERE (EXISTS (SELECT 1 FROM C t2 WHERE (((t2.A_ID = t1.ID) AND (t2.LABEL = "value1")) AND t2.VALUE LIKE "%value2%")) AND ((t0.ID IS NULL) OR (t0.HIDDEN = false)))
As you can see, the provider ignores the left Join totally as if it doesn't exist! It doesn't do any join between tow tables A and B..
Note that in the JPA JSR, the query should be correct. It also worked as a named query. But I can't use it here since I have lots of changing conditions...
The query should be:
SELECT DISTINCT t1.ID
FROM A t1 LEFT JOIN B t0 ON t1.ID=t0.A_ID AND t0.creator="currentUserValue"
WHERE (EXISTS (SELECT 1 FROM C t2 WHERE (((t2.A_ID = t1.ID) AND (t2.LABEL = "value1")) AND t2.VALUE LIKE "%value2%")) AND ((t0.ID IS NULL) OR (t0.HIDDEN = false)))
I tried to change eclipseLink to the 2.7.7 release...The query changed a bit, but it still wrong, with no left join between A and B.
I appreciate any help with this problem.

Related

How to use string_agg with PostgreSQL in #Query annotation without nativeQuery flag?

I need to remove the nativeQuery flag from the #Query annotation.
The table structure may change in the future, and code without nativeQuery will make it easier to maintain later.
I have a class Parent that is linked to class Child with the #ManyToMany annotation.
Class Child has a field pseudonym that is a value of the String type.
The result of the query needs to be sorted by the String value from class Child, which I have to sort and then concatenate into one String value.
The query without the nativeQuery flag in the #Query annotation works if I do not add an additional sort in the string_agg function:
order by string_agg(c.pseudonym, ',')
If I add additional required sorting as below, an exception occurs
order by string_agg(c.pseudonym, ',' order by c.pseudonym)
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found 'order' near line 1, column ...
#Entity
#Getter
#Setter
#Table(name = "parent")
public class Parent {
#Id
private Long id;
private String name;
#ManyToMany
#JoinTable(
name = "parent_child_link",
joinColumns = {#JoinColumn(name = "parent_id")},
inverseJoinColumns = {#JoinColumn(name = "child_id")}
)
#OrderBy("pseudonym ASC")
private List<Child> childs = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "child")
public class Child {
#Id
private Long id;
private String pseudonym;
#ManyToMany(mappedBy = "childs")
private Set<Parent> parents = new HashSet<>();
}
public interface ParentRepository extends JpaRepository<Parent, Long> {
#Query(nativeQuery = true, value =
"select p.*" +
" from parent p" +
" left join parent_child_link link on p.id = link.parent_id" +
" left join child c on link.child_id = c.id" +
" where p.name = :name" +
" group by (p.id)" +
" order by string_agg(c.pseudonym, ',' order by c.pseudonym)")
Page<Parent> find(#Param("name") String name, Pageable pageable);
}
Please try with nested query:
select p.*
from (
select p, string_agg(c.pseudonym, ',' order by c.pseudonym) ord
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
group by (p.id)
) inn(p, ord)
order by ord
or:
select p.*
from(
select p, c.pseudonym
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
order by p, pseudonym
) inn(p, pseudonym)
group by p.id
order by string_agg(pseudonym, ',')

Override column selection in jpa

I need to select an integer column (int) by left joining its entity.
When there are no rows, its value is "null", what I need is to have 0 instead of null.
I searched for an annotation or a way to override the select query with no result. Note that I'm using eclipselink.
I am searching for something that always makes the selection of the column "coalesce(columnName,0)" instead of columnName.
EDIT:
A code Sample:
public class parent{
#Id
private int id;
private String field1;
private String field2;
private List<Child> children;
}
public class Child{
#Id
private int id;
private int myNumber;
private String field;
}
The JPA Query is similar to
SELECT p FROM parent p LEFT JOIN p.children c ON c.field = 'aaa' WHERE p.field1 = 'bbb'
The translation of this query would be:
SELECT t0.id, t0.field1, t0.field2, t1.id, t1.myNumber, t1.field
FROM parent t0 LEFT OUTER JOIN child t1 on t0.id = t1.parent_id AND t1.field = 'aaaa
WHERE t0.field1 = 'bbb'
In the case where there are no rows in CHILD, the field: t1.myNumber will have the value null.
To fix it, in Sql statement, normally I will use coalesce(t1.myNumber,0). So the query will be:
SELECT t0.id, t0.field1, t0.field2, t1.id, coalesce(t1.myNumber,0), t1.field
FROM parent t0 LEFT OUTER JOIN child t1 on t0.id = t1.parent_id AND t1.field = 'aaaa
WHERE t0.field1 = 'bbb'
What I need is to accomplish this result using some annotation, or any other tool if exists, whithout changing the JPA query if possible.

JPA Create Criteria with GROUP COUNT and HAVING on nested objects

Given this SQL query
SELECT
ug.lookup_key,
count(ug.id) as count
FROM user u
INNER JOIN user_group ug on ug.id = u.id
WHERE
u.age >= 11 AND
u.age <= 20 AND
ug.lookup_key in('12345')
GROUP BY ug.lookup_key
HAVING count(ug.id) < 7
I have written this
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id"))).groupBy(d.get("lookupKey"));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
The SQL returns {"12345",4} whereas the code returns {"12345", 37}. The SQL is the correct result. There are 37 users in the database for groups with that lookup key, so I understand where the numbers are coming from but I do not understand how to do the JOIN, GROUP BY, HAVING with the CreateCriteria query so that I get the results. I don't want to use JPQL.
The entities...
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private int age;
private double salary;
#ManyToOne(optional=false,cascade=CascadeType.ALL, targetEntity=UserGroup.class)
#JsonBackReference
private UserGroup group;
// Getters and Setters //
}
#Entity
public class UserGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String lookupKey;
#OneToMany(mappedBy="group",targetEntity=User.class, fetch=FetchType.EAGER)
#JsonManagedReference
private Collection users;
// Getters and Setters //
}
And also, here is the method in which it is implemented
public void summarizeGroupsByLookupKey(long ageFrom, long ageTo, List<String> lookupKeys, long numUsers){
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id")));
query.groupBy(d.get("lookupKey"));
query.having(criteriaBuilder.<Long>lessThan(criteriaBuilder.count(d.get("id")), numUsers));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
}
By way of info...using Spring Boot 1.5.1 and all the default JPA, Hibernate, etc. from there.
Can a JPA expert offer some help? Thanks!
Change the multiselect part to use countDistinct(..)
query.multiselect(d.get("lookupKey")
,criteriaBuilder.countDistinct(d.get("id")));
and also having(..)
query.having(criteriaBuilder.<Long>lessThan(
criteriaBuilder.countDistinct(d.get("id")), numUsers)
);
Original query returned row per matching user in which rows userGroup.id was then multiplied.

JPA inheritance issue, why the generated query is different between wildfly10 and jboss7

I am migrating a project from jboss7 to wildfly10. The strange thing is the generated query in jboss is different in wildfly10, that causes the tables structure have to be changed, but it is not expected.
public class BaseAnnotation implements Serializable {
private static final long serialVersionUID = 6636704943305921427L;
}
#Entity
#Table(name="one")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class oneBaseAnnotation extends BaseAnnotation {
#Id
#GeneratedValue(generator = "baseAnnotationSequencer")
#SequenceGenerator(name = "baseAnnotationSequencer", sequenceName = "BASEANNOTATION_SEQ")
private Long id;
private String annotationType;
.....
}
#Entity
public class TwoStructureAnnotation extends oneBaseAnnotation {
private static final long serialVersionUID = -5838272604038154615L;
#OneToMany
#JoinTable(name= "CSA_CS")
private List<TwoStructure> twoStructures = new ArrayList<TwoStructure>();
public TwoStructureAnnotation() {
setAnnotationType("Two Structure");
}
.....
}
public class..... {
protected List<T> createQuery(int first, int pageSize,
List<SortMeta> multiSortMeta, Map<String, String> filters,
String joinField) {
// Setup
CriteriaBuilder cb = getObjectEntityManager().getCriteriaBuilder();
CriteriaQuery<T> criteria = (CriteriaQuery<T>) cb.createQuery();
Root<A> annotationRoot = criteria.from(TwoStructureAnnotation.class);
ListJoin<A, T> joinRoot = annotationRoot.joinList("twosStructures");
Predicate restrictions = cb.conjunction();
// Filter
filters.putAll(this.getBaseFilter());
restrictions = cb.and(restrictions,
createGlobalFilter(filters, joinRoot, cb));
restrictions = cb.and(restrictions,
cb.equal(annotationRoot, annotation));
...
// Query creation
criteria.where(restrictions);
criteria.select(joinRoot);
// Restrict Returns
TypedQuery<T> returnQuery = getObjectEntityManager().createQuery(
criteria);
returnQuery.setFirstResult(first);
returnQuery.setMaxResults(pageSize);
List<T> results = returnQuery.getResultList();
....}
The query below, the different that the key in the inner join on table CSA_CS. I have no idea why, please suggest me, thank you.
--in Jboss7
select * from
( select
crystalstr2_.id as id1_43_,
crystalstr2_.pdbEntry_id as pdbEntry_id3_43_,
crystalstr2_.title as title2_43_
from
ONE crystalstr0_
inner join
CSA_CS crystalstr1_
on crystalstr0_.id=crystalstr1_.ONE_id
inner join
TwoStructure crystalstr2_
on crystalstr1_.crystalStructures_id=crystalstr2_.id
where
crystalstr0_.DTYPE='TwoStructureAnnotation'
and 1=1
and 1=1
and crystalstr0_.id=? )
where
rownum <= ?
---In wildfly10
select
*
from
( select
crystalstr2_.id as id1_36_,
crystalstr2_.pdbEntry_id as pdbEntry_id3_36_,
crystalstr2_.title as title2_36_
from
ONE crystalstr0_
inner join
CSA_CS crystalstr1_
on crystalstr0_.id=crystalstr1_.TWOStructureAnnotation_id
inner join
TwoStructure crystalstr2_
on crystalstr1_.crystalStructures_id=crystalstr2_.id
where
crystalstr0_.DTYPE='TwoStructureAnnotation'
and 1=1
and 1=1
and crystalstr0_.id=? )
where
rownum <= ?
Tables:
table-TWOSTRUCTURE
ID
TITLE
table-CSA_CS
ONE_ID
CRYSTALSTRUCTURES_ID
table-ONE
DTYPE
ID
ANNOTATIONTYPE
JBoss7 ships hibernate 4.x and wildfly 10 ships hibernate 5. In hibernate 5, Oracle is implemented 'inner join'. If you use Oracle10gDialect, then
Oracle10gDialect added support for ANSI joins. Subclasses (e.g. Oracle12cDialect) inherit this functionality.

JPA criteria api order by query for #EmbeddedId

I have 2 classes:
#Entity
#Table(name = "DRCOMMENTS" ,schema = "XXX")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Drcomments.findAll", query = "SELECT d FROM Drcomments d"),
public class Drcomments implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected DrcommentsPK drcommentsPK;
#Size(max = 50)
#Column(name = "SDESC")
private String commentSecondaryCodeDescription;
}
#Embeddable
public class DrcommentsPK implements Serializable {
#Column(name = "CODE")
private Short commentPrimaryCode;
#NotNull
#Column(name = "SCODE" , length=5)
private Short commentSecondaryCode;
}
I'm trying to create a query with a dynamic order by and a parameter, for example:
I want to select all Drcomments records when DrcommentsPK.commentPrimaryCode equels 1, and the order by will be by DrcommentsPK.commentSecondaryCode. this is what i tried:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Drcomments> q = cb.createQuery(Drcomments.class);
Root<Drcomments> c = q.from(Drcomments.class);
q.select(c);
q.where(cb.equal(c.get("drcommentsPK").get("commentPrimaryCode"), 1));
Path<Drcomments> valuePath = c.get("drcommentsPK").get("commentSecondaryCode");
Order[] orders;
if(sord.equals("desc"))
{
orders = new Order[] {cb.desc(valuePath)};
}
else
{
orders = new Order[] {cb.asc(valuePath)};
}
q.orderBy(orders);
query = em.createQuery(q);
query.setFirstResult(start);
query.setMaxResults(start + limit);
results = query.getResultList();
The problem is that the resultlist I get is not sorted in the commentSecondaryCode desc order..
am I doing somthing wrong? how can this be done? how to create a query that will be ordered by a field inside the emeddable class?
UPDATE:
this is the generated sql I get:
SELECT * FROM
(SELECT * FROM
(SELECT EL_TEMP.*, ROWNUMBER() OVER() AS EL_ROWNM FROM
(SELECT CMSSDESC AS a1, CMSSCODE AS a4, CMSPCODE AS a5 FROM DRCOMMENTS
WHERE (CMSPCODE = 1) ORDER BY CMSSCODE DESC, CMSPCODE DESC)
AS EL_TEMP)
AS EL_TEMP2 WHERE EL_ROWNM <= 50)
AS EL_TEMP3 WHERE EL_ROWNM > 0
when I run this code it wont return the records in the CMSSCODE desc order..
(beacuse the order by should be in the outer select..)
do I need to change somthing in the query.setFirstResult() and query.setMaxResults() ?
I how do I add it to the end in the criteria query, so it will be in the last select?
Thank's In Advance.
Your code is fine and standard compliant. If you are sure it doesn't work, then there must be bug in implementation or some odd stuff in those parts of code that is not shown. For example, check that value of sord is exactly "desc" (case sensitive), because otherwise you will fall to use ascending order.
I tried it with Hibernate 3.6.8.Final and EclipseLink (2.3.2). With both of them it works as expected - ORDER BY SCODE [ASC/DESC] is part of the executed SQL query.