How to use joined columns on hibernate criteria? - java

I have 3 entities joined via onetoone relations. My goal is get entities using hibernate criteria where match.status != null. And how to tell hibernate to not join algo entity to result, should be (pick.algo = null).
#Entity
public class Pick {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int pid;
#Column(columnDefinition="DATETIME")
private Date insertTime;
#Column(columnDefinition="DATETIME")
private Date setupResTime;
#OneToOne
private DbMatch match;
#OneToOne
private Algo algo;
#Transient
private Integer algoID;
....
Criteria query:
public List<Pick> getPicksHistory(){
Criteria criteria = session.createCriteria(Pick.class);
criteria.add(Restrictions.isNotNull("match.status"));
return criteria.list();
}

from the hibernate documentation (http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#querycriteria-associations) :
Criteria criteria = session.createCriteria(Pick.class);
criteria.createAlias("match", "m");
criteria.add(Restrictions.isNotNull("m.status"));
criteria.setFetchMode("algo", FetchMode.LAZY);
criteria.list();

You can add alias and apply your condition to the alias name
Criteria criteria = session.createCriteria(Pick.class);
criteria.createAlias("match", "match", JoinType.INNER_JOIN); //<---
criteria.add(Restrictions.isNotNull("match.status"));
TO get nulls you can use e.g. JoinType.LEFT_OUTER_JOIN

Related

Transforming an sql query into CriteriaQuery

I have two tables and they maintain the parent-child relationship between them by a foreign key.
The query looks something like below. I want to use the criteriaquery along with jpa. So can anyone help me with the criteriaquery & how the two entity classes would look like
ps:if there is any custom enity class required apart from these two entities classes help me with that as well.
Select parent.notification_id,parent.city,parent.name,parent.accountNo,
case when child.accountNo is not null then 'Yes' else 'No' end as checked
FROM parent
JOIN child ON parent.notification_id=child.notification_id_child
AND child.accountNo='test' WHERE parent.city='delhi' or parent.city='all' or parent.accountNo="test";
The column 'notification_id_child' of table 'child' is the foreign key and refers to the primarykey of table 'parent'.
There are multiple strategies that you can use to implement this:
MappedSuperclass (Parent class will be mapped with this annotation and not entity)
Single Table (Single table for each hierarchy, you can use #DiscriminatorColumn JPA annotation for identifying each hierarchy)
Joined Table (Each class for the parent and child)
In this scenario, you would have to join both the tables on the common column to fetch the results.
These are some good answers on joining tables
Joining two table entities in Spring Data JPA
Link for some good answers on usage of discrimintaorColumn
How to access discriminator column in JPA
Finally, I managed to solve the problem. My entity classes and criteria query looks something like the below.
Parent Entity
#Entity
#Table(name="parent")
public class Parent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="notification_id")
private Long notificationId;
#Column(name="city")
private String city;
#Column(name="name")
private String name;
#Column(name="accountNo")
private String accountNo;
#JoinColumn(name="notification_id_child")
#OneToMany
private List<Child> child;
//Getters Setters
}
Child Entity
#Entity
#Table(name="child")
public class Child{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="accountNo")
private String accountNo;
#Column(name="notification_id_child")
private String notificationIdChild;
//Getters Setters
}
Custom Entity
public class CustomEntity{
private Long notificationId;
private String city;
private String accountNo;
private String checked;
}
Criteria Query
#PersistenceContext
EntitiManager em;
CriteraBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CustomEntity> cq = cb.createQuery(CustomEntity.class);
Root<Parent> parentEntity = cq.from(Parent.class);
Join<Parent,Child> join = parentEntity.join("child", JoinType.LEFT);
join.on(cb.equal(join.get("accountNo"),"test"));
Path<String> notificationIdPath = parentEntity.get("notificationId");
Path<String> cityPath = parentEntity.get("city");
Path<String> accountNoPath = parentEntity.get("accountNo");
cq.multiselect(notificationIdPath, cityPath, accountNoPath,
cb.selectCase().when(join.get("accountNo").isNotNull(),"Yes").otherwise("No"));
Path<String> accountNoPath = parentEntity("accountNo");
Predicate accountNoPredicate = cb.equal(accountNoPath, "test");
Predicate cityPredicateAll = cb.equal(cityPath,"all");
Predicate cityPredicateSpecified = cb.equal(cityPath,"delhi");
cq.where(cb.or(cityPredicateAll, cityPredicateSpecified, accountNoPredicate));
TypedQuery<CustomEntity> query = em.createQuery(cq);
List<CustomEntity> CustomEntityList = query.getResult();

Get collections within an Entity when mapping it to DTO using Transformers

I have an Entity called Student
#Entity
#Table(name = "students")
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId;
#Column(name = "STUDENT_NAME", nullable = false, length = 100)
private String studentName;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student", cascade = CascadeType.ALL)
private List<Note> studentNotes;
// Some other instance variables that are not relevant to this question
/* Getters and Setters */
}
and an entity called as Note
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "NOTE_ID")
private Integer noteId;
#Column(name = "NOTE_CONTENT")
private String noteText;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "STUDENT_ID")
private Student student;
/* Getters and Setters */
}
As you can see the relationship dictates that a Student can have multiple number of notes.
For displaying some information about the student on a particular page I need only the studentName, count of notes and all the notes.
I created a StudentDTO for that and it looks something like this:
public class StudentDTO {
private Long count;
private String name;
private List<Note> notes;
/* Getters and setters */
}
And I am using the following code to map the Student and Notes returned from the DB to the StudentDTO
private static void testDTO() {
Session session = getSessionFactory().openSession();
String queryString = "SELECT count(n) as count, s.studentName as name, s.studentNotes as notes " +
"from Student s join s.studentNotes n where s.id = 3";
Query query = session.createQuery(queryString);
List<StudentDTO> list = query.setResultTransformer(Transformers.aliasToBean(StudentDTO.class)).list();
for (StudentDTO u : list) {
System.out.println(u.getName());
System.out.println(u.getCount());
System.out.println(u.getNotes().size());
}
}
The above code fails when there are notes fetched in the query but if I remove the notes and get only name and count it works fine.
When notes is included in the query, this is the error that is fired by Hibernate:
select
count(studentnot2_.NOTE_ID) as col_0_0_,
. as col_3_0_,
studentnot3_.NOTE_ID as NOTE_ID1_2_,
studentnot3_.NOTE_CONTENT as NOTE_CON2_2_,
studentnot3_.STUDENT_ID as STUDENT_3_2_
from
students studentx0_
inner join
notes studentnot2_
on studentx0_.STUDENT_ID=studentnot2_.STUDENT_ID
inner join
notes studentnot3_
on studentx0_.STUDENT_ID=studentnot3_.STUDENT_ID
where
studentx0_.STUDENT_ID=3;
And this is the error message that I get:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as col_3_0_, studentnot3_.NOTE_ID as NOTE_ID1_2_, studentnot3_.NOTE_CONTENT as N' at line 1
Now I can see where the query is wrong but it is generated by Hibernate, not something that I have control on. Is there something that I need to change in my queryString to acheive the result that I need.
I do not want to manually map the results to my DTO, is there a way that I can directly map my studentNotes in Student.java to notes in StudentDTO.java
Looks like this query is wrong. The better way is to get just the student. You can always get collection of notes from a student.
Session session = getSessionFactory().openSession();
String queryString = from Student s where s.studentId = 3;
Query query = session.createQuery(queryString);
Student student = query.getSingleResult();
sysout(student.getNotes().size())
Also, I never retrieved collection this way in SELECT clause; so, not sure but do you really need
join s.studentNotes
in your query? Not sure if my answer is helpful.
Your query is wrong as you would need two joins to also select the count of notes, but that's not even necessary, as you could determine the count by just using the size of the notes collection.
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for your example could look like this
#EntityView(Student.class)
interface StudentDTO {
#Mapping("studentName")
String getName();
#Mapping("studentNotes")
List<NoteDTO> getNotes();
default int getCount() { return getNotes().size(); }
}
#EntityView(Note.class)
interface NoteDTO {
// attributes of Note that you need
#IdMapping Integer getId();
String getNoteText();
}
Querying could look like this
StudentDTO student = entityViewManager.find(entityManager, StudentDTO.class, studentId);

How to add sub-select to select

I want to execute a query like this:
SELECT Table1.COL1,
Table1.COL2,
(SELECT SUM(Table2.COL3)
FROM Table2
WHERE Table2.UID = Table1.UID) SUMOF
FROM Table1;
How can I do it?
I usually create a Criteria add ProjectionList to it, to fill COL1 and COL2 only.
I have created a DetachedCriteria to calculate the sum...
Now, how to attach this detached criteria to the main one? My intuition says - it's some sort of Projection which needs to be added to the list, but I don't see how. Also, not sure how WHERE Table2.COL4 = Table1.COL5 of detached criteria will work.
Also, I'm sure this query might be written in different way, for example with join statement. It's still interesting if there's a way to run it like this.
DetachedCriteria and main Criteria
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Table2.class, "table2");
detachedCriteria
.setProjection(
Projections.projectionList()
.add(Projections.sum("table2.col3"), "sumCol3")
)
.add(Restrictions.eq("table2.uid", "table1.uid"))
;
Criteria criteria = session.createCriteria(Table1.class, "Table1");
criteria
.setProjection(
Projections.projectionList()
.add(Projections.property("Table1.col1"), "col1")
.add(Projections.property("Table1.col2"), "col2")
)
;
Entities (very short version)
#Entity
#Table(name = "Table1")
public class Table1 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col1")
public String getCol1();
#Column(name = "col2")
public String getCol2();
#Column(name = "col3")
public String getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
#Entity
#Table(name = "Table2")
public class Table2 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col3")
public BigDecimal getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
For a correlated subquery (like the one you presented above), you can use #Formula which can take an arbitrary SQL query. Then, you'll need to fetch the entity and the subquery will be executed.
However, a native SQL is more elegant if you only need this query for a single business requirement.
As for derived table queries (e.g. select from select), neither JPA nor Hibernate support derived table queries for a very good reason.
Entity queries (JPQL pr Criteria) are meant to fetch entities that you plan to modify.
For a derived table projection, native SQL is the way to go. Otherwise, why do you think EntityManager offers a createNativeQuery method?

JPA/Hibernate SELECT all parent fields with Child count in Single Query

I am using JPA 2.0, with Hibernate 1.0.1.Final.
I want all Parent table fields with no of children in single query.
In Other word, I want to translate from SQL into CriteriaAPI of JPA/Hibernate.
select kgroup.*, count(userGroup.uid)
from kernelGroup kgroup
left join kernelUserGroup userGroup on (kgroup.groupId = userGroup.groupId)
group by kgroup.groupId
I have following JPA Entities.
#Entity
#Table(name="kernel_group")
public class KernelGroup implements Serializable {
#Id
private int groupId;
private boolean autoGroup;
private String groupName;
#OneToMany
private Set<KernelUserGroup> kernelUserGroups;
private long jpaVersion;
}
#Entity
#Table(name="kernel_usergroup")
public class KernelUserGroup implements Serializable {
#EmbeddedId
private KernelUserGroupPK id;
private long jpaVersion;
#ManyToOne
private KernelGroup kernelGroup;
#ManyToOne
private KernelUser kernelUser;
}
#Embeddable
public class KernelUserGroupPK implements Serializable {
private String uid;
private int groupId;
}
My Current Criteria Query is like this :
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<KernelGroupDto> cQuery = cb.createQuery(KernelGroupDto.class);
Root<KernelGroup> root = cQuery.from(KernelGroup.class);
Join<KernelGroup, KernelUserGroup> userGroupsJoin = root.join(KernelGroup_.kernelUserGroups, JoinType.LEFT);
cQuery.select(cb.construct(KernelGroupDto.class, root, cb.count(userGroupsJoin.get(KernelUserGroup_.id).get(KernelUserGroupPK_.uid))));
cQuery.groupBy(root.get(KernelGroup_.groupId));
em.createQuery(cQuery).getResultList();
Now the Problem is, It fires multiple Queries to the database.
1) One query to retrieve groupId and no of count of users
2) N Queries to retrieve group info for each group.
I want only one Query to retrieve GroupInfo and no of count of the users as shown in Above SQL Query.
Please give me good suggestion.
Implement Using the bellow Code.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<KernelGroupDto> cQuery = cb.createQuery(KernelGroupDto.class);
Root<KernelGroup> root = cQuery.from(KernelGroup.class);
Join<KernelGroup, KernelUserGroup> userGroupsJoin =
root.join(KernelGroup_.kernelUserGroups, JoinType.LEFT);
cQuery.select(cb.construct(KernelGroupDto.class, root.
<Long>get("id"),cb.count(userGroupsJoin)));
cQuery .groupBy( root.<Long>get("id") );
cQuery.groupBy(root.get(KernelGroup_.groupId));
em.createQuery(cQuery).getResultList();
This code will work. for child count.
you must have the constructor of the class KernelGroupDto(Long id, Long childCount)

one-to-many: making Hibernate select the reference's id instead of joining it

I have two classes stored in my database using Hibernate. Let's call them Container and Item. Item has a one-to-many relation to Container:
#entity(name = "containers")
public class Container {
#Id
#GeneratedValue
private long id;
}
#entity(name = "items")
public class Item {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "container_id")
private Container container;
}
I want to select select all for all items the tuple [ (long)item.id, (long)item.container_id ], but Hibernate seems to insist on retrieving [ (long)item.id, (Container)item.container ], introducing a useless (and expensive) join.
I tried that criteria query:
Criteria criteria = session.
createCriteria(Link.class).
add(Restrictions.isNotNull("container")).
setProjection(Projections.projectionList().
add(Projections.id()).
add(Projections.property("container")));
Is there a matching criteria query. Has to be possible without HQL queries or native SQL queries, hasn't it?
Edit 1: Working HQL query:
session.createQuery("SELECT item.id, item.container.id " +
"FROM items AS item " +
"WHERE item.container <> NULL")
Edit 2: FetchType.LAZY is not an option.
Criteria criteria = session.createCriteria(Link.class);
criteria.createAlias("container", "containerAlias");
criteria.add(Restrictions.isNotNull("containerAlias.id"));
criteria.setProjection(Projections.projectionList()
.add(Projections.id())
.add(Projections.property("containerAlias.id")));
It should be sufficient to add a fetch = FetchType.LAZY attribute to the #ManyToOne annotation. If you've annotated an ID column in Container, the test for whether item.container is null should not require a join.

Categories

Resources