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

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)

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

How to perform left join on two sql queries using criteria API

I have two tables and their classes as follows:
#Entity
#Table(name="Master")
public class Master {
#Column(name = "master_col_one")
private String masterColumnOne;
#Column(name = "master_col_two")
private String masterColumnTwo;
// Other fields and so on
#NotNull
#JoinColumn(name = "slave_col_three", nullable = false)
#ManyToOne(optional = false)
private Slave slaveData;
}
#Entity
#Table(name="slave")
public class Slave {
#Column(name = "slave_col_one")
private String slaveColumnOne;
#Column(name = "slave_col_two")
private String slaveColumnTwo;
// Other fields
}
My native SQL query is like this:
SELECT * FROM master m LEFT JOIN
(SELECT DISTINCT slave_col_one, slave_col_two FROM slave) s ON m.master_col_one = s.slave_col_one
AND m.master_col_two = s.slave_col_two
I need to build a criteria query for the above native SQL query.
I have built this much query so far:
final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
final CriteriaQuery<Master> criteriaQuery = criteriaBuilder
.createQuery(Master.class);
final Root<Master> root = criteriaQuery.from(Master.class);
Join<Master, Slave> join = root.join(Master_.slaveData);
I think my join is not correct as it's not fetching distinct selective columns from slave table as per requirements, I searched the internet a lot but couldn't come up with a solution.

JPA CriteriaQuery join three tables not directly navigable

i need to translate this sql query to jpa criteria:
SELECT tbl1.id_t1, tbl2.name, tbl3.name, tbl4.symbol, tbl1.limit, tbl1.value, tbl1.uncertainty
FROM table_1 AS tbl1
JOIN table_2 AS tbl2 ON tbl2.id_t2=tbl1.id_t2
JOIN table_3 AS tbl3 ON tbl3.id_t3=tbl1.id_t3
JOIN table_4 AS tbl4 ON tbl4.id_t4=tbl1.id_t4
WHERE (tbl2.id_l=1 AND tbl3.id_l=1) AND tbl1.id_s=1;
my mapping between pojo and database table are as follows:
Table_1
#Entity
#Table("table_1")
public class Table1 {
#Id
#Column(name="id_t1")
private Long idRowT1
#ManyToOne
#JoinColumn(name="id_t2")
private Table2 tbl2;
#ManyToOne
#JoinColumn(name="id_t3")
private Table3 tbl3;
#ManyToOne
#JoinColumn(name="id_t4")
private Table4 tbl4;
#Column(name="limit")
private String limit;
#Column(name="value")
private String value;
#Column(name="uncertainty")
private String uncertainty;
// getter and setter
}
Table_2
#Entity
#Table("table_2")
public class Table2 {
#Id
#Column(name="id_t2")
private Long idT2;
// getter and setter
}
Table_2_lang
#Entity
#Table("table_2_lang")
#IdClass(Table2LangPK.class)
public class Table2Lang {
#Id
#Column(name="id_t2")
private Long idT2;
#Id
#Column(name="id_l")
private Lang l;
#Column(name="name")
private String name;
// getter and setter
}
Table_3
#Entity
#Table("table_3")
public class Table3 {
#Id
#Column(name="id_t3")
private Long idT3;
// getter and setter
}
Table_3_lang
#Entity
#Table("table_3_lang")
#IdClass(Table3LangPK.class)
public class Table3Lang {
#Id
#Column(name="id_t3")
private Long idT3;
#Id
#Column(name="id_l")
private Lang l;
#Column(name="name")
private String name;
// getter and setter
}
Table_4
#Entity
#Table("table_4")
public class Table4 {
#Id
#Column(name="id_t4")
private Long idT4;
#Column(name="name")
private String name;
// getter and setter
}
To send data from business layer to front-end i'm using value objects defined as follows:
Simple entity
public class SimpleEntityVO {
private Long entityId;
private String name;
// getter and setter
}
Complex Entity
public class SimpleEntityVO {
private Long entityId;
private SimpleEntityVO tbl2VO;
private SimpleEntityVO tbl3VO;
private SimpleEntityVO tbl4VO;
// ... other field of table_1
// getter and setter
}
In my EJB i need to implement a method that return a list of ComplexEntityVO starting from Table_1
...
private CriteriaBuilder cB = eM.getCriteriaBuilder();
public List<ComplexEntityVO> findAll(Long id_s, Long id_l) {
CriteriaQuery<ComplexEntityVO> cQ = cB.createQuery(ComplexEntityVO.class)
Root<Table1> tbl1Root = cQ.from(Table1.class);
// UPDATE BEGIN
Root<Table2Lang> tbl2Root = cQ.from(Table2Lang.class);
...
Selection<SimpleEntityVO> sESTbl2 = cB.construct(SimpleEntityVO.class, tbl2Root.get(Table2Lang_.id_t2), tbl2Root.get(Table2Lang_.name));
// The selection for table_3_lang and table_4 are the same
// UPDATE END
TypedQuery<ComplexEntityVO> tQ = eM.createQuery(cQ);
}
...
To achieve the results i've tried with join betwen Table1 and Table2Lang, tried with selection like the one exposed below
`Selection<SimpleEntityVO> sES = cB.construct(SimpleEntityVO.class, ...);`
using Root for lang table, tried with solution exposed here
https://community.oracle.com/message/10795956#10795956
but when i try to execute this statement
`cQ.select(cB.construct(ComplexEntityVO.class, id_t1, SimpleEntityVO)`
or this
`cQ.multiselect(...)`
i get the: IllegalArgumentException
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: , near line 1, column 64
[select new com.example.vo.ComplexEntityVO(generatedAlias0.id_t1,
new com.example.labims.vo.SimpleEntityVO(generatedAlias1.table2.id_t2, generatedAlias1.name),
new com.example.vo.SimpleEntityVO(generatedAlias2.table_3.id_t3, generatedAlias2.name),
new com.example.vo.SimpleEntityVO(generatedAlias3.id_t4, generatedAlias3.name),
generatedAlias0.limit, generatedAlias0.value, generatedAlias0.uncertainty)
from com.example.Table1 as generatedAlias0,
com.example.model.Table2Lang as generatedAlias1,
com.example.model.Table3Lang as generatedAlias2,
com.example.model.Table4 as generatedAlias3
where ( generatedAlias0.id_s=:param0 ) and ( ( generatedAlias1.lang.id_l=:param1 ) and ( generatedAlias2.lang.id_l=:param1 ) )]
From the cause of execption understanded that i can't instanciate new object inside select or multiselect statement, but i don't find a way to achieve the original SQL query using criteria API.
UPDATE
i've added an excerpt of what i've tried to achieve the result between //UPDATE BEGIN and //UPDATE END
I think make hibernate show sql == true
and take query by console,test showed query your databases and find error hbernate not generate query correct
There are two approaches to solve this problem.
Add a constructor method to ComplexEntityVO like this:
public ComplexEntityVO(Long id, Long simpleId2, String simpleName2 /* etc ... */) {
this.simpleEntityVo = new SimpleEntityVO(simpleId2, simpleName2);
// etc for other associations
}
add a ProjectionList to your query, return a List<Object[]> instead of a List<ComplexEntityVO> and then iterate over the results like so
for(Object[] o: results) {
ComplexEntityVO cvo = new ComplexEntityVO((Long)o[0]);
new SimpleEntityVO vo2 = new SimpleEntityVO((Long) o[1], (String) o[2]);
cvo.setTbl2VO(vo2);
// ... etc for other associations
}
Although the second is uglier I would prefer it, since it is more flexible, and allows more opportunities for debugging, logging etc.
See AliasToBeanResultTransformer(MyDTO.class) fails to instantiate MyDTO

JPA bidirectional 1..N association, avoid querying in child to set parent

I am using Spring Data JPA + Hibernate for a webapp. For a particular domain model A, we have a 1-to-many association in another domain B. Such that A will have a Set getB() and B will have A getA().
While querying for a A graph, I see hibernate is using 1+n queries. A single outer join query for fetching the A graph, but then 'n' queries for setting A in each B.
Am I missing any pattern here? Since all the childs have the same parent, is not somehow possible to avoid these 'n' queries?
#MappedSuperclass
#Data
public abstract class Batch implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "batch_id", referencedColumnName = "batch_id")
protected BatchID batchId;
}
/*
//The parent class in a simplified form
*/
#Entity
#Table(name = "DRYRUN")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class DryrunBatch extends Batch {
/**
*
*/
private static final long serialVersionUID = -1596595930859735318L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter#Setter
protected Long id;
public DryrunTNStatus newTNStatus()
{
final DryrunTNStatus tn = new DryrunTNStatus();
tn.setBatch(this);
getTnStatus().add(tn);
return tn;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "batch")
#Getter#Setter
private Set tnStatus = new HashSet();
}
//The child class in a simplified form
#Entity
#Table(name = "DRYRUN_TN_STATUS")
#Data
public class DryrunTNStatus implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4388406636444350023L;
public DryrunTNStatus(String accountNo, String telNo) {
super();
this.accountNo = accountNo;
this.telNo = telNo;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BATCH_ID", referencedColumnName = "BATCH_ID")
private DryrunBatch batch;
public DryrunTNStatus()
{
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
The code to fetch the object graph using JpaRepository. Using Spring JPA support to enforce an outer join. I preferred this over Hibernate's #Fetch annotation.
DryrunBatch drBatch = drBatchRepo.findOne(new Specification() {
#Override
public Predicate toPredicate(Root root, CriteriaQuery query,
CriteriaBuilder cb) {
query.distinct(true);
root.fetch("tnStatus", JoinType.LEFT);
return cb.equal(root.get("batchId").get("id"),
batch.getId());
}
});
And finally the hibernate queries from log. I am running a junit that fetches a parent with 10 childs from DB.
//this query can fetch data for the complete graph??
Hibernate: select distinct dryrunbatc0_.id as id1_6_0_, tnstatus1_.id as id1_9_1_[etc..] from dryrun dryrunbatc0_ left outer join dryrun_tn_status tnstatus1_ on dryrunbatc0_.batch_id=tnstatus1_.batch_id where dryrunbatc0_.batch_id=15
//and then 10 queries like
Hibernate: select dryrunbatc0_.id as id1_6_3_, [etc..] from dryrun dryrunbatc0_ left outer join batch_id batchid1_ on dryrunbatc0_.batch_id=batchid1_.batch_id inner join users user2_ on dryrunbatc0_.created_by=user2_.login_id left outer join dryrun_tn_status tnstatus3_ on dryrunbatc0_.batch_id=tnstatus3_.batch_id where dryrunbatc0_.batch_id=?
You've encountered the famous N+1 problem with lazy loading. There is no JPA standard way to tackle this, however, every JPA provider provides means to turn on "Batch fetching", which will load all lazy references at once instead loading each in a single SQL query.
Here is information on how to turn it on in hibernate.
Here is an article with explanation of how batch fetching works and examples using eclipselink.

Container managed security in Glassfish

I have followed this blog-tutorial and successfully got it to work:
http://jugojava.blogspot.com/2011/02/jdbc-security-realm-with-glassfish-and.html
I have named my two entities Group and User. The have a bi-directional many-to-many relationship.
Now the reason I have done it as in the blog is because I am making an administrator page where I want to be able to add new users. I also let users have the oppertunity to register them self, and they will have the role user.
#Entity
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String groupName;
#ManyToMany
#JoinTable(joinColumns = {#JoinColumn(name="group_id")}, inverseJoinColumns = {#JoinColumn(name="user_id")})
private List<User> users;
....
}
and
#Entity
#Table(name = "app_user")
public class User implements Serializable {
public static final String ALL = "User.all";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String username;
private String email;
private String password;
#ManyToMany(mappedBy = "users")
private List<Group> groups;
....
}
My question is how I would assign the group user to a user when it register without me picking the groups from a list in the view?
This is what I have done in the application code but it binds the code the the id of the group in the database, are there better ways?
Method from EJB
public void persistAsUser(User user) {
Group group = new Group(2L, "user");
user.addGroup(group);
userRepository.persist(user);
}
You may want to define a UNIQUE index on the field groupName. Then, create a Data Access Object for the Group table, which provides a method for getting a Group from a groupName (code not tested):
public class GroupDAO implements Serializable {
#PersistenceContext private EntityManager em;
public Group findByGroupName(String groupName) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Group> cq = cb.createQuery(Group.class);
Root<Group> group = cq.from(Group.class);
cq.where(cb.equal(group.get(Group_.groupName), groupName));
TypedQuery<Group> q = em.createQuery(cq);
return q.getSingleResult();
}
}
If you don't like Criteria Builder, you can use a Named Query. Add this annotation to your Group Entity Class:
#NamedQuery(name = "Group.findByGroupname", query = "SELECT f FROM group f WHERE f.groupname = :groupname")
and build a Named Query as follows:
return em.createNamedQuery("Group.findByGroupname").setParameter("groupname", groupName).getResultList();

Categories

Resources