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.
Related
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.
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.
Let's say I have a collection of Rates that all inherit from an AbstractRate
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "name")
#Table(name = "rates")
public abstract class AbstractRate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(precision = 13, scale = 4)
private BigDecimal value;
#OneToOne
private EffectiveDate effectiveDate;
...
}
And, that all have an EffectiveDate:
#Entity
public class EffectiveDate {
#Id
private LocalDateTime date;
...
}
(I acknowledge that a separate date object is a little over-kill for this model, but it's allowing me to bind multiple rates and other figures in the database.)
Now, I'd like to get a specific Rate, say SalaryRate, that is effective as of a certain date. I can do something like
salaryRateRepository.findByEffectivedate(
effectiveDateRepository.findTopByDateLessThanEqualOrderByDateDesc(sampleDate)
);
This should effectively give me a the MAX(date) and its matching Rate. Is this the right way to query these things? Some posts suggest
As an additional option, I have Querydsl setup and the repositories extend QuerydslPredicateExecutor. However, I'm not really familiar with how Querydsl's syntax works.
I think all is OK with findTopByDateLessThanEqualOrderByDateDesc(sampleDate).
Another variants should be:
#Query(value = "select * from effective_date ed where ed.date <= ?1 order by ed.date desc limit 1", nativeQuery = true)
EffectiveDate findEffectiveDate(LocalDateTime dateTime);
or
Predicate p = QEffectiveDate.effectiveDate.date.loe(sampleDate);
Pageable page = new PageRequest(0, 1, new Sort(Sort.Direction.DESC, "date"));
//...
EffectiveDate effectiveDate = effectiveDateRepository.findAll(p, page).getContent().stream().findFirst().orElse(null);
(not tested...)
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.
I want make a query where I join 2 tables, using the CriteriaBuilder. In MySQL the query I'm trying to make would look like this:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
AND item.type_id = 1
I want to get all orders and if they have an item of type #1, I want to join with this item. However, if no item of type #1 is found, I still want to get the order. I can't figure out how to make this with the CriteriaBuilder. All I know how to make is:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
Join<Item, Type> type = order.join(Item_.type, JoinType.LEFT);
cq.select(order);
cq.where(cb.equal(type.get(Type_.id), 1));
This query is broke, since it results in something like this in MySQL:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
WHERE item.type_id = 1
The result will only contain orders with items of type #1. Orders without are excluded. How can I use the CriteriaBuilder to create a query like in the first example?
It is possible starting from the version 2.1 of JPA using the on method Join<Z, X> on(Predicate... restrictions);
Here is how:
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
item.on(cb.equal(item.get(Item_.type), 1));
I think this is the same problem as posed in this question. It looks like it is not possible in CriteriaBuilder. It is possible in Hibernate Criteria API, but that probably won't help you.
JPA Criteria API: Multiple condition on LEFT JOIN
I know this question was made a long time a go, but recently a had the same problem and i found this solution from an Oracle forum, i copied and pasted just in case the link is not longer available.
MiguelChillitupaArmijos 29-abr-2011 1:41 (en respuesta a 840578) Think
you should use something like:
em.createQuery("SELECT DISTINCT e.Id" +
" from Email e " +
" left join e.idEmailIn e2 *with* e2.responseType = 'response'" +
" where e.type = 'in' and e.responseMandatory = true").getSingleResult();
An this is the link.
JPA Criteria : LEFT JOIN with an AND condition
There is a workaround if you are using Hibernate 3.6 with JPA 2.0
It is not the better solution, however it works perfect for me.
I´ve duplicate the entity with the #Where hibernate annotation.It means that everytime you use the join with this entity, hibernate will add the extra condition on the join statement at generated SQL.
For instance, initially we have the follow example:
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
}
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
In order to add extra conditions on criteria Join, we need duplicate the Address #Entity mapping , adding the #Where annotation #Where(clause = " ADDRESS_TYPE_ID = 2").
#Entity
#Table(name = "ADDRESS")
#Where(clause = " ADDRESS_TYPE_ID = 2")
public class ShippingAddress {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#OneToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
Also, we need to add the duplicate mapping association for the new entity.
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
#OneToOne(mappedBy = "person")
private ShippingAddress shippingAddress;
}
Finally, you can use a join with this specific Entity in your criteria :
PersonRoot.join(Person_.shippingAddress, JoinType.LEFT);
The Hibernate Snippet SQL should seems like this :
left outer join
address shippingadd13_
on person11_.person_id=shippingadd13_.person_id
and (
shippingadd13_.ADDRESS_TYPE_ID = 2
)
ON clause is supported in Hibernate 4.3 version, anyone is aware if there is a parameter indexing issue between the parameter index of the additional custom conditions with the index of the existing mapping filters when doing an outer join with ON clause?
Using the Person entity class below as an example, say I am adding this filter to limit the address types and the filter is enabled to populate the IN clause. The parameter index for the IN clause will cause the issue [2] when I add additional conditions (such as using 'street' column) part of the ON clause. Is is a known issue?
[1] #Filter(name = "addressTypes", condition = "ADDRESS_TYPE in (:supportedTypes)")
[2]
Caused by: ERROR 22018: Invalid character string format for type BIGINT.
private Set addresses;