Jpa select with relations - java

I have 4 tables in relations. A,B,C,D.
So I wrote select bellow:
select NEW org.example.ExtendsA(a,b.name,c.name,d.name)
from A a LEFT JOIN a.bItems b LEFT JOIN a.cItems c LEFT JOIN b.dItems d
order by b.name ASC;
A is unique but the relations are incomplette.
and I tried this:
select NEW org.example.ExtendsA(a,b.name,c.name,d.name)
from A a LEFT JOIN FETCH a.bItems b LEFT JOIN FETCH a.cItems c
LEFT JOIN FETCH b.dItems d order by b.name ASC;
A is not unique.
A object definition is:
#Entity
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#NotNull
#Size(min = 1, max = 100)
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "aId")
private List<B> bItems;
#OneToMany(mappedBy = "aId")
private List<C> cItems;
}
Some relations are empty but need A object with null relations.
Some A object has more than one relations between B and C and I want to select with all in one A object (distinct A).
Would you help me how to solve this issue? Maybe the approach is bad?
I use EclipeLink data provider.

This is a typical problem with loading OneToMany relathionships.
This is because of the nature of SQL result sets. Imagine a SQL result of a join of entity with it's other entities linked to it. On the left side, fields of this entity will be duplicated as many times as many related entities it has.
Unforunately, EclipseLink doesn't filter them out and you get many items of the same entity in your result. Although, EclipseLink is smart enough and each item will actually be the same Java object instance.
It's also the reason why you can't use setMaxResults in such queries.
You need to use distinct keyword that in this particular keys will not be mapped to a real SQL distinct, but will filter duplicated entities. Or, you can filter them manually.

Related

How to get collection of property in Join JPA criteria API

I'm trying to get a list of property policy_type_id from ListAttribute<Article, PolicyType>, but I can't figure out how to do it.
I come up with an inefficient method was select whole Collection of PolicyType then filter it later
Root<ArticleVersion> a = cq.from(ArticleVersion.class);
Join<ArticleVersion, Article> join1 = a.join(ArticleVersion_.article, JoinType.INNER);
cq.where(getCondition(cb, join1));
cq.multiselect(join1.get(Article_.article_id), join1.get(Article_.policyTypes), a);
Sadly, hibernate generate an error query like this
select article1_.article_id as col_0_0_, . as col_1_0_, articlever0_.article_version_id as col_2_0_ . As you can see, there is a . in select that make query broken (which I believe select all)
#Entity
#Table(name = "PolicyType", schema = "SM_Request")
#Getter
#Setter
#NoArgsConstructor
public class PolicyType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int policy_type_id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "PolicyTypeArticle", schema = "SM_Request",
joinColumns = #JoinColumn(name = "policy_type_id"), inverseJoinColumns = #JoinColumn(name = "article_id"))
#JsonIgnore
private List<Article> articles;
}
After long searching, I think that hibernate doesn't support query tuple of primitive types and list of objects (which is kinda sad, compare to LINQ to query). I decided to break down my query into smaller parts. First, I select tuples of article_id and ArticleVersion. After that, I select a list of PolicyType which also contains article_id, and union 2 lists back.
By the time I wrote this, I have an idea that I could select all 3 joins together and transform data the way I want. But It really depend on many aspects, like how many join or which type of join you're using, how fast data in each table grown (JOIN queries vs multiple queries)

hibernate join fetch one to many multiple tables

I have the following entity relationship in my data model.
ERD
Entity A: one-to-many :Entity B
Entity B: one-to-many :Entity C
Entity B: one-to-many :Entity D
Hibernate Entities:
public class EntityA {
private Integer id;
#OneToMany
private List<EntityB> entityBList;
}
public class EntityB {
private Integer id;
#ManyToOne
private EntityA entityA;
#OneToMany(fetch=FetchType.LAZY)
private List<EntityC> entityCList;
#OneToMany(fetch=FetchType.LAZY)
private List<EntityD> entityDList;
}
public class EntityC {
private Integer id;
#ManyToOne
private EntityB entityB;
}
public class EntityD {
private Integer id;
#ManyToOne
private EntityB entityB;
}
Requirement: I want to query on Entity C with join fetch of Entity B and also eagerly fetch Entity D at the same time. After the query is completed I expect that if I do entityC.getEntityB().getEntityDList(), it should not lead to N+1 query problem in hibernate.
I was trying the following JPQL query:
select ec from EntityC ec
join fetch ec.entityB eb
join fetch eb.entityD ed
where ec.id = :id
This leads to duplicates in the result because of cross join with Entity D. Instead of one result, I get n results where n is the size of list of Entity D.
How can I avoid this? Is there any way to fetch the Entity D without cross join in JPQL?
The first thing is to use the DISTINCT JPQL keyword on your query, e.g. as:
TypedQuery<EntityC> query = em.createQuery("SELECT DISTINCT ec FROM EntityC ec JOIN FETCH ec.entityB eb JOIN FETCH eb.entityDList ed WHERE ec.id = :id", EntityC.class);
// ^^^^^^^^
This will eliminate the duplicates but has the side-effect of passing the DISTINCT to the SQL query as well, which is not something that you want. Read details here, here and in this excellent answer. Long story short - quoting Vlad Mihalcea's answer:
By passing DISTINCT to the SQL query, the EXECUTION PLAN is going to execute an extra Sort phase which adds overhead without bringing any value[...]
The solution is in the linked articles and answers, but in short, if you are on Hibernate >= 5.2.2, use the hibernate.query.passDistinctThrough hint:
TypedQuery<EntityC> query = em.createQuery("SELECT DISTINCT ec FROM EntityC ec JOIN FETCH ec.entityB eb JOIN FETCH eb.entityDList ed WHERE ec.id = :id", EntityC.class);
query.setHint("hibernate.query.passDistinctThrough", false);
// -OR-
query.setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false); // import org.hibernate.jpa.QueryHints

Hibernate unidirectional One-to-many with multiple constraints

Suppose I have some generic tables apple, orange etc and a table note which contains notes about a row in one of my generic table. The note is stored by saving the entity_type (e.g. the table name) and entity_id (e.g. the id of the row).
I'm trying to make a unidirectional One-to-Many mapping from apple to note. Effectively creating this relationship:
SELECT *
FROM apple f
INNER JOIN note n
ON f.id = n.entity_id
AND n.entity_type = 'apple'
I've been trying something like:
#Entity
public class Apple {
...
#OneToMany
#JoinColumn(name = "entity_id", referencedColumnName = "id")
#WhereJoinTable(clause = "entity_type = 'apple'")
private Set<Note> changeNotes = new HashSet<>();
Which isn't working (error is #WhereJoinTable on an association without join table). Any ideas?
Update:
I think this is the sort of thing I"m trying to do https://docs.oracle.com/html/E13946_02/ref_guide_mapping_notes_nonstdjoins.html
However hibernate is looking for a column instead of just using the string...

Returning result from 3 tables using JPA criteria

I have the following situation:
#Entity
public class Period
{
String Name;
}
#Entity
public class Bill
{
Period period;
#OneToMany(mappedBy = "bill", fetch = FetchType.LAZY)
private List<Entry> entry = new ArrayList<Entry>(0);
}
#Entity
public class Entry
{
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BILL_ID", nullable = false)
Bill bill;
String text;
BigDecimal amount;
}
So what I need is to fetch all the data in a single query, either with the root being the Bill or the Entry using JPA 2.0 criteria (with Hibernate behind). I've read few posts about this problem HERE and HERE and it seems that I can't use subqueries in the result or fetch data two levels deep.
EDIT: To make my problem more clear: When I use Entry as root, I can't fetch Period and when I use Bill as root I can't fetch all other tables in Entry. Also I can't use eager fetch because there are other use cases that need those tables.
Are there any other ways to do this?
Thanks!
To fetch data from association, you use left join fetch clauses:
select distinct b from Bill b
left join fetch b.period
left join fetch b.entry
where b...
or
select distinct e from Entry e
left join fetch e.bill b
left join fetch b.period
where e...
Regarding Criteria, its fetch() method returns a Fetch, which itself has a method fetch() returning a Fetch(), which itself has a method fetch() returning a Fetch, etc. So yes, its supports as many levels you want.

HQL. Intersection of two lists

Update: look my answer below on how to check if 2 list intersects (both for #ElementCollection with string/enums and usual entities list mapped like #OneToMany)
I have an entity which contains #ElementCollectionfield with enums.
public enum StatusType {
NEW, PENDING, CLOSED;
}
#Entity
public class MyEntity {
#ElementCollection
#CollectionTable(name = "status_type", joinColumns = {#JoinColumn(name = "my_entity_id")})
#Column(name = "status_type", nullable = false)
private Set<StatusType > statusTypes = new HashSet<StatusType >();
...
}
Now I want to get all entities which contains status NEW or PENDING (or both).
I'm trying to use this query:
SELECT DISTINCT u FROM MyEntity u WHERE u.statusTypes in :statusTypes
But I'm getting exception: org.postgresql.util.PSQLException: No value specified for parameter 9.
How to properly query on collections and filter by intersections?
Problem solved by adding JOIN clause to HQL. Hibernate couldn't implicitly recognize that query needs JOIN clause. May be it will help someone:
SELECT DISTINCT u FROM MyEntity u
LEFT JOIN u.statusTypes statusTypes
WHERE statusTypes in :statusTypes
I set the query params like this:
query.setParameter( "statusTypes", listOfStatusTypesEnums);
It will select rows where at least one element of listOfStatusTypesEnums list is present in entity's statusTypes property (i.e. if 2 list are intersects in some way).
If you have usual list of entities (which are not #ElementCollection, but #OneToMany etc), same rule will work as well. Just use like this: LEFT JOIN u.subEntities subEntities WHERE subEntities.id in :subEntityIds

Categories