Projections on nested collections - java

I have three entities Topic, Subject and Category. How can I prefetch id and name columns for each of the entity while retrieving all the categories with subjects and topics? I don't need other fields since it affects the performance.
#Entity
class Topic{
private Long id;
private String name;
...
//other fields
}
#Entity
class Subject{
private Long id;
private String name;
...
//other fields
#OneToMany(fetch=FetchType.LAZY)
private List<Topic> topics;
}
#Entity
class Category{
private Long id;
private String name;
...
//other fields
#OneToMany(fetch=FetchType.LAZY)
private List<Subject> subjects;
}

I would suggest creating a result class on top of the projection itself as it eases the result set handling. The ResultClass needs to have a constructor with relevant query result fields and you have to use the fully qualified name in the query itself.
select new org.mypkg.ResultClass(c.id, c.name, s.id, s.name, t.id, t.name)
from Category c
inner join c.subjects s
inner join s.topics
Then you simply:
List<ResultClass> results = em.createQuery(query).list();

Related

Order By child field in One To One relationship Spring Data Jpa

I have a one to one relationship like this:
#Entity
public class Modification {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="modification_id")
Long id;
#Column(name="second_line", length=1000)
String firstLine;
#OneToOne
#JoinColumn(name="tm_satus")
StatusOrder tmStatus;
// Constructors getters and setters
}
#Entity
public class StatusOrder {
#Id
#Column(name="status")
int status;
#Column(name="status_order")
int order;
// Constructors getters and setters
}
So every Modification has a StatusOrder.
Now I want to perform a query where I select from Modification table ordered by order field in StatusOrder.
Is there a way to have a method in my repository like:
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);
Try these,
#Query("SELECT m FROM Modiciation m ORDER BY m.tmStatus.order DESC")
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);
or (I don't know if this'll work.)
Page<Modification> findAllOrderBytmStatus_orderDesc(Pageable pageable);

HQL Query for Specific Nested Object in a query

I have a Class which has other objects as it's members fields.
e.g.:
class Orders{
private Integer code;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name="code", nullable=false)
private Category category;
private PaymentType pt;
}
class Category{
private Integer id;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="id", nullable=false)
private Brands brand;
....
}
class Brands{
private Integer id;
private String brandName;
}
I need to get all the Brands for that particular order.
How do I get just the brands through Orders table in Hql?
Are the relations one-to-one? If so, you should be able to query it with:
#Query("SELECT FROM Orders orders.category.brand WHERE ...")
Brands findBrandBy(...);
if you are using Spring data.

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

Projection on hibernate relationship annotation

I have a two tables which are represented as below:
tableA {
private String code;
private String desc;
//few other properties followed by getter and setter
}
tableB {
private String code;
private String desc;
private String tableACode;
//few other properties followed by getter and setter
}
Now, I wanted to define a #ManyToOne relationship in tableB for tableA such that I wanted to store only the tableA.desc (I wanted to limit the size of this object in memory when it is cached).
Is this possible to do so?
I tried something like below.. But the resulting query is not what I'm expecting.
tableB {
private String code;
private String desc;
private String tableACode;
#ManyToOne
#JoinColumnsOrFormulas(value = {#JoinColumnOrFormula(column=#JoinColumn(name = "tableACode", referencedColumnName = "code")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "SELECT a.desc from tableA e WHERE a.code = tableACode", referencedColumnName = "tableACode"))})
private String tableADesc;
//few other properties followed by getter and setter
}
It's possible, only if you create the EntityA to contain only the:
id
desc
Then you can simply use a #ManyToOne in EntityB:
#ManyToOne
private EntityA entityA;
So, even if your EntityA database table has multiple columns, you only persist the desc.
For new EntityA entries, all non-persisted columns will be set to NULL, which may break some NOT NULL constraints, so be aware of it.

Hibernate criteria on embedded id member member value

I would like to find an entity using a critera with restriction on the value of an attribute of a second entity wich is a member of the embedded id of my first entity.
First entity :
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Second entity :
#Entity
public class Owner {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
#OneToMany (mappedBy = "id.owner")
private List<Car> cars;
// getters and setters...
}
In this example, I would like to obtain the car with the color 'black', model 'batmobile' and the owner's firstname 'Bruce' (oops... spoiler ;) )
I tried to do something like that but it won't work :
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("id.owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
Result :
Hibernate: select this_.model as model1_0_0_, this_.owner_id as owner_id3_0_0_, this_.color as color2_0_0_ from Car this_ where this_.color=? and this_.model=? and o1_.firstname=?
ERROR: Unknown column 'o1_.firstname' in 'where clause'
What is the right way to obtain what I want ?
update
I tried in hql :
String hql = "FROM Car as car where car.color = :color and car.id.model = :model and car.id.owner.firstname = :firstname";
Query query = em.createQuery(hql);
query.setParameter("color", "black");
query.setParameter("model", "batmobile");
query.setParameter("firstname", "Bruce");
List<Car> cars = query.getResultList();
It works but is there a way to do this with criteria ?
You forgot to add the #Column annotation on top of the firstname and lastname fields (and the color field in Car). In hibernate if a field is not annotated, it doesn't recognize it as a database field. This page should give you a good idea about how to set up your model objects.
NOTE: You can have the column annotation over the getters and be fine, but you didn't show the getters. Either place is fine.
Look at what HQL is spitting back out, specifically the statement (formated for easier reading):
select
this_.model as model1_0_0_,
this_.owner_id as owner_id3_0_0_,
this_.color as color2_0_0_
from Car this_
where
this_.color=?
and this_.model=?
and o1_.firstname=?
It looks like hibernate is translating the field "id.owner" to "o" as your alias told it to to, but for some reason it's not writing down that "id.owner=o" as intended. You may want to do some research into why it may be doing that.
As per https://hibernate.atlassian.net/browse/HHH-4591 there is a workaround.
You have to copy the needed relation-property of the #EmbeddedId (owner in this case) to the main entity (Car in this case) with insertable = false, updatable = false as follows
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#ManyToOne
#JoinColumn(name = "column_name", insertable = false, updatable = false)
private Owner owner;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Then just create directly the alias instead of using the composite id property
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();

Categories

Resources