Complex order by in JPQL - jpa

Let me start by saying I'm not a DB guy. This stuff just confuses me to death, but unfortunately I got roped into doing this at work, so I'm kinda stuck :-)
I'm trying to get a complex order by to work in JPQL, and I'm not having any luck at all.
My current query looks like this:
select distinct msg from CSMessage msg, Error err where msg = err.msg order by err.task.src
What I'm trying to accomplish with this is to get all the msgs's with related errors, then sort the whole thing using the source document (err.task.src), to get all errored messages with the same source to appear together.
Needless to say this doesn't work at all. I get an exception that says; "ORDER BY item should be in the SELECT DISTINCT list"
I've looked over the docs and other sources and there doesn't seem to be anything in there that can help me.
Can anyone point me in the right direction?
Thanks
Edit 1:
The entities look like this:
CSMessage
public class CSMessage extends BaseModel implements Serializable
{
private static final long serialVersionUID = 1L;
.
.
.
Other fields not shown for brevity
.
.
.
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "TASK_ID")
private Task task;
}
Error:
public class Error
{
private static final long serialVersionUID = 1L;
#Column(name = "ERR_STRING", length = 255)
private String errString;
#Column(name = "ERR_TYPE")
private Integer errType;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "MSG_ID")
private CSMessage msg;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "SRC_ID")
private CommonSource src;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "TASK_ID")
private Task task;
}
Task
public class Task
{
private static final long serialVersionUID = 1L;
#Column(name = "CORRELATION_UUID", length = 36)
private String correlationId;
#Column(name = "CURRENT_NODE", length = 255)
private String currentNodeName = "empty";
#Column(name = "PROCESS_NAME", length = 255)
private String processName = "empty";
#Column(name = "SITE_ID", length = 10)
private String siteId = "1";
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SRC_ID")
private CommonSource src;
}

You have several options:
select distinct msg from CSMessage msg, Error err where msg = err.msg order by err.task.src
Add err.task.src to select clause (select msg, err.task.src) and change return type in your method
Order in memory instead of bd, using interface Comparable (implements Comparable and use Collections.sort)
Also you could use criteriaQuery or nativeQuery

If your only problem is to order the result list, then one way to do it is to add #OrderBy on the global fields (default order is ASC which can be left out):
#OrderBy("YOUR_COLUMN_NAME")
If you want descending order, the syntax will be:
#OrderBy("YOUR_COLUMN_NAME DESC")
For example:
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "TASK_ID")
#OrderBy("TASK_ID") // <-------- ASC
private Task task;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SRC_ID")
#OrderBy("SRC_ID DESC") // <-------- DESC
private CommonSource src;

Related

JPA Repository Query on additional table #ManytoMany

I want to do select like this in my jpa spring repository
SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC;
My enitity
#Entity
#Table(name = "symptomp")
public class Symptomp {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "symptomp_id")
private Long symptomp_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinTable(name = "symptomp_sicknes",joinColumns = #JoinColumn(name = "symptomp_id"),inverseJoinColumns = #JoinColumn(name = "sicknes_id"))
private Set<Sicknes> sicknes = new HashSet<>();
#Entity
#Table(name = "sicknes")
public class Sicknes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sicknes_id")
private Long sicknes_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinColumn(name = "speciesId")
private Species species;
My Symptomp repository:
public interface SymptompRepository extends JpaRepository<Symptomp, Long> {
#Query("select p from Symptomp p where name like ?1%")
public List<Symptomp> findAllBySymptompName(String symptomp);
public Symptomp findByName(String symptomp);
public List<Symptomp> findByNameIn(List<String> list);
Integer countDistinctSymptompByName(String id);
}
How I can create this select in my JPA repository?
I try get value like in select but i got error mapping bean.
You can get query result as List<Object[]> using nativeQuery=true parameter
#Query("SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC", nativeQuery=true)
List<Object[]> getQueryResult();
Other option is to create dto class with appropriate constructor
public class QueryResultDto {
Long sicknesId;
Long count;
public QueryResultDto(Long sicknesId, Long count) {
this.sicknesId = sicknesId;
this.count = count;
}
}
Then using JPQL
#Query("select new yourproject.dtopath.QueryResultDto(...")
List<QueryResultDto> getQueryResult(#Param("symptompIds") List<Long> symptompIds);
If you want to avoid a native Query the best way is to create an Entity for that JoinTable. Then you can query it easily. Additional benefit if this is that if in future a requirement will pop up that you have to store additional attributes in that relation you will have the Entity already there to do that easily.

How to use JPA criteriaBuilder to search on attributes in a collection of sub-attributes

I have an Entity that maps to a table defined this way:
#Entity
#Table(name = "cmmn_calendar_evnt")
public class CommonCalendarEvent implements java.io.Serializable
{
private Integer cevId;
private Set<CommonCalendarEventPart> commonCalendarEventParts = new HashSet<CommonCalendarEventPart>(0)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEV_ID", unique = true, nullable = false)
public Integer getCevId()
{
return this.cevId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "commonCalendarEvent")
public Set<CommonCalendarEventPart> getCommonCalendarEventParts()
{
return this.commonCalendarEventParts;
}
}
and CommonCalendarEventPart is defined like this:
#Entity
#Table(name = "cmmn_calendar_evnt_part")
public class CommonCalendarEventPart implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer ceeId;
private CommonCalendarEvent commonCalendarEvent;
private PartParticipant partParticipant;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEE_ID", unique = true, nullable = false)
public Integer getCeeId()
{
return this.ceeId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_CEV_ID", nullable = false)
public CommonCalendarEvent getCommonCalendarEvent()
{
return this.commonCalendarEvent;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_PPT_ID", nullable = false)
public PartParticipant getPartParticipant()
{
return this.partParticipant;
}
}
and finally:
#Entity
#Table(name = "part_participant")
public class PartParticipant implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer pptId;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "PPT_ID", unique = true, nullable = false)
public Integer getPptId()
{
return this.pptId;
}
}
I want to use the CriteriaBuilder to generate a query finding all CommonCalendarEvent for a specific Participant ID.
In Hql it would look something like this: (although I have not confirmed that this Hql is correct either)
"from commonCalendarEvent cce where :pptId in (cce.commonCalendarEventParts.partParticipant.pptId)"
I've tried some approaches of what I thought were intuitive attempts at writing a criteriaBuilder approach, but my attempts have resulted in errors ranging from:
“unexpected end of subtree” to just implementation errors.
.....
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> criteria = builder.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> root = criteria.from(CommonCalendarEvent.class);
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf = root.fetch(CommonCalendarEvent_.commonCalendarEventParts, JoinType.LEFT);
Join<CommonCalendarEvent, CommonCalendarEventPart> evj = (Join<CommonCalendarEvent, CommonCalendarEventPart>) evf;
Join<CommonCalendarEventPart, PartParticipant> evpj = evj.join(CommonCalendarEventPart_.partParticipant);
List<Predicate> pred = new ArrayList<Predicate>();
pred.add(builder.equal(evpj.get(PartParticipant_.pptId), pptId));
criteria.where(builder.and(pred.toArray(new Predicate[] {})));
return getEntityManager().createQuery(criteria).getResultList();
.............
above yields an "unexpected end of subtree" error.
Any Help is appreciated.
+1 for using Lazy initialization. The JPA model is Object, or Entity oriented, so you need to get used to thinking in those terms. A PartParticipant is not identified by its id in JPA, but by the object itself. Assuming you have a list of participants:
PartParticipant pp = em.find(PartParticipant.class, 2);
List<PartParticipant> pps = new ArrayList<PartParticipant>();
pps.add(pp);
Then you pass that list to the queries. In JPQL:
TypedQuery<CommonCalendarEvent> cev = em.createQuery("select cev from CommonCalendarEvent cev join fetch cev.commonCalendarEventParts cce where cce.partParticipant in :pps", CommonCalendarEvent.class);
List<CommonCalendarEvent> cevs = cev.setParameter("pps", pps).getResultList();
Notice the fetch is needed to prevent LazyInitializationExceptions.
Knowing the JPQL, the CriteriaQuery should follow pretty much the same:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> q = cb.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> r = q.from(CommonCalendarEvent.class);
Join<CommonCalendarEvent, CommonCalendarEventPart> j = r.join("commonCalendarEventParts");
r.fetch("commonCalendarEventParts");
q.select(r).where(j.get("partParticipant").in(pps));
List<CommonCalendarEvent> rs = em.createQuery(q).getResultList();
You don't need to do anything special with the fetch other than execute it. As you can see, the query uses the PartParticipant Id.
select
commoncale0_.CEV_ID as CEV_ID1_0_0_,
commoncale1_.CEE_ID as CEE_ID1_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_1_1_,
commoncale1_.CEE_PPT_ID as CEE_PPT_3_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_0_0__,
commoncale1_.CEE_ID as CEE_ID1_1_0__
from cmmn_calendar_evnt commoncale0_
inner join cmmn_calendar_evnt_part commoncale1_ on commoncale0_.CEV_ID=commoncale1_.CEE_CEV_ID
where commoncale1_.CEE_PPT_ID in (?)
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf is not necessary, and the first join statement should be corrected:
Join<CommonCalendarEvent, CommonCalendarEventPart> evj =
root.join(CommonCalendarEvent_.commonCalendarEventParts);
The rest of the query seems correct.

query with OneToMany - openJPA vs EclipseLink

openjpa is complaining about an incorrect argument for a JPA query that EclipseLink properly handles. EclipseLink returns the set of validation messages for the motor.
Two questions:
1) Is my query wrong and EclipseLink is kindly handling it anyway?
2) Any suggestions on how to restructure the query for openjpa?
Thanks for thinking about my question!
Query
SELECT m.valMessages FROM ThreePhaseMotorInput m WHERE m.id = :id
Actual openjpa exception
Caused by: <openjpa-2.3.0-r422266:1540826 nonfatal user error> org.apache.openjpa.persistence.ArgumentException:
Query projections cannot include array, collection, or map fields.
Invalid query: "SELECT m.valMessages FROM ThreePhaseMotorInput m WHERE m.id = :id"
at org.apache.openjpa.kernel.ExpressionStoreQuery$AbstractExpressionExecutor.assertNotContainer(ExpressionStoreQuery.java:328)
at org.apache.openjpa.kernel.ExpressionStoreQuery$DataStoreExecutor.<init>(ExpressionStoreQuery.java:770)
at org.apache.openjpa.kernel.ExpressionStoreQuery.newDataStoreExecutor(ExpressionStoreQuery.java:179)
at org.apache.openjpa.kernel.QueryImpl.createExecutor(QueryImpl.java:749)
at org.apache.openjpa.kernel.QueryImpl.compileForDataStore(QueryImpl.java:707)
at org.apache.openjpa.kernel.QueryImpl.compileForExecutor(QueryImpl.java:689)
at org.apache.openjpa.kernel.QueryImpl.compile(QueryImpl.java:589)
at org.apache.openjpa.persistence.EntityManagerImpl.createNamedQuery(EntityManagerImpl.java:1038)
at org.apache.openjpa.persistence.EntityManagerImpl.createNamedQuery(EntityManagerImpl.java:1017)
ThreePhaseMotorInput mapping
public class ThreePhaseMotorInput implements IThreePhaseMotorInput, Serializable {
private static final long serialVersionUID = 8084370807289186987L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Integer version;
private Integer status;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = UnapprovedThreePhaseMotor.class)
#JoinColumn(name = "unapproved_id")
private IThreePhaseMotor unapprovedMotor;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = ApprovedThreePhaseMotor.class)
#JoinColumn(name = "approved_id")
private IThreePhaseMotor approvedMotor;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY, targetEntity = ValidationMessage.class)
#JoinColumn(name = "input_id", referencedColumnName = "id", nullable = false)
#OrderColumn(name = "idx")
private List<IValidationMessage> valMessages;
ValidationMessage mapping
public class ValidationMessage implements Serializable, IValidationMessage {
private static final long serialVersionUID = 8765213112015434057L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "record_id")
private Long recordId;
#Column(name = "field_name")
private String fieldName;
#Column(name = "validation_msg")
private String validationMsg;
private Integer status;
#Column(name = "fail_field")
private String failField;
#Column(name = "error_source")
private Integer errorSource;
Check http://docs.oracle.com/javaee/6/tutorial/doc/bnbuf.html#bnbvx - SELECT clause: A SELECT clause cannot specify a collection-valued expression. For example, the SELECT clause p.teams is invalid because teams is a collection.
But you can use valMessages for INNER/OUTER join and select IValidationMessage entities trough it, e.g.:
SELECT ivm
FROM ThreePhaseMotorInput tpmi
INNER JOIN tpmi.valMessages ivm
WHERE tpmi.id = :id

QueryDSL / JPQL : how to build a join query?

I've tried to read through the QueryDSL docs but I am still very confused. I'm accustomed to writing a lot of SQL, but this is my first real crack at using QueryDSL w/ JPQL (JPA2).
I have the following entity:
#Entity
public class Provider implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
private String name;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "provider_contact", joinColumns = #JoinColumn(name = "contact_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "provider_id", referencedColumnName = "id"))
#OrderColumn
private Collection<Contact> contact;
}
where Contact is a simple entity with an id for a pk.
#Entity
public class Contact {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
/**
* User first name
*/
#NotNull
private String firstName;
/**
* User last name
*/
#NotNull
private String lastName;
}
I'm trying to write a query which returns a Contact object given a specific Contact.id and Provider.id. If the Contact object is not a part of the Provider's Contact collection, I'm looking for a null value.
I've tried the following:
public Contact getContact( long providerId, long contactId ){
Predicate p = QProvider.provider.id.eq(providerId).and(QContact.contact.id.eq(contactId));
JPQLQuery query = new JPAQuery(em);
return query.from(QProvider.provider).innerJoin(QProvider.provider.contact).where(p).singleResult(QContact.contact);
}
but I'm getting the following error:
Caused by: java.lang.IllegalArgumentException: Undeclared path 'contact'. Add this path as a source to the query to be able to reference it.
at com.mysema.query.types.ValidatingVisitor.visit(ValidatingVisitor.java:78)
at com.mysema.query.types.ValidatingVisitor.visit(ValidatingVisitor.java:30)
at com.mysema.query.types.PathImpl.accept(PathImpl.java:94)
I'm presuming it has something to do with the fact that my predicate references QContact.contact direction and not part of the QProvider.provider.contact object, but I'm really at a loss as to figure out how this should be done.
Am I even on the right track? I'm not even sure my join is correct either.
This should work
public Contact getContact(long providerId, long contactId) {
QProvider provider = QProvider.provider;
QContact contact = QContact.contact;
return new JPAQuery(em).from(provider)
.innerJoin(provider.contact, contact)
.where(provider.id.eq(providerId), contact.id.eq(contactId))
.singleResult(contact);
}

query entity with condition on ManyToMany relation

I have two Entites
#Entity
public Report()
#Id
#Column(name = "REPORT_ID")
private long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="reports_projects",
joinColumns={#JoinColumn(name="report_id", referencedColumnName="REPORT_ID")},
inverseJoinColumns={#JoinColumn(name="project", referencedColumnName="PROJECT_ID")})
private List<Project> projects;
second is:
#Entity(name = "projects")
public class Project
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PROJECT_ID")
// seems like spring's jpa has issue hanlde "_" between the words
private long id;
#Column(name = "CODE", nullable = false)
private String code;
#Column(name = "DESCRIPTION", nullable = false)
private String description;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
i'm tring to query reports by projects.code
tried few stuff like
#Query("select reports from org.jpp.domain.quicksearch.ReportQS reports inner join reports.projects p where p.code in :code")
And
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<QuickSearchResult> query = cb.createQuery(QuickSearchResult.class);
Metamodel m = em.getMetamodel();
EntityType<ReportQS> ReportQSMetaModel = m.entity(ReportQS.class);
Root<ReportQS> reportsQS = query.from(ReportQS.class);
Root<Project> projects = query.from(Project.class);
Join<ReportQS, Project> joinReportsProjects = reportsQS.join("projects");
Predicate condition = cb.equal(projects.get("code"),"gnrl");
query.select(reportsQS).where(condition);
TypedQuery<QuickSearchResult> q = em.createQuery(query);
I get empty result for both of the queries
Any idea how to get this to work ?
Thanks in advance,
Oak
Try following code:
String query = "select r from ReportQS r join r.projects p where p.code = :code";
List<ReportQS> reports = em.createQuery(query,ReportQS.class).setParameter("code","grnl").getResultList();
Make sure that ReportQS is name of entity class (in your sample code you have different class name and different entity name used in query).