Hibernate unidirectional One-to-many with multiple constraints - java

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...

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)

Jpa select with relations

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.

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

Hibernate entity with restriction

We have a DB table that is mapped into a hibernate entity. So far everything goes well...
However what we want is to only map enentitys that satisty a specific criteria, like ' distinct(fieldA,fieldB) '...
Is it possible to map with hibernate and hibernate annotations? How can we do it? With #Filter?
I would recommend that you use #Where annotation. This annotation can be used on the element Entity or target entity of a collection. You provide a clause attribute written in sql that will be applied to any select that hibernate performs on that entity. It is very easy to use and very readable.
Here is an example.
#Entity
//I am only interested in Donuts that have NOT been eaten
#Where(clause = "EATEN_YN = 'N'")
public class Donut {
#Column(name = "FILLING")
private String filling;
#Column(name = "GLAZED")
private boolean glazed = true;
#Column(name = "EATEN_YN")
private boolean eaten = false;
...
}
You could create a view and then map that view to entity:
create view my_data as
select ... from ...
#Entity(table="my_data")
public class MyData { ... }
One option is to map the table normally, then you could fetch your always entities through a query or a filter.
You could also make a native SQL query and map the entity on the results:
Query q = sess.createSQLQuery("SELECT DISTINCT fieldA, fieldB FROM some_table")
.addEntity(MyEntity.class);
List<MyEntity> cats = q.list();
It might be also possible to add DISTINCT to this type of HQL query:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
Methods 1, 3 and 4 will make a read-only mapping.
Could you be more specific about the criteria you are using? The view approach is more generic since you can't do everything with a hibernate query or filter.
perhaps you could create a new Pojo that encapsulates the fields and the condition that they should statisy . And then then make that class a 'custom user defined type', such that Hibernate will have to use the mapping class that you provide, for mapping that 'type'..
In addition to the options mentioned by Juha, you can also create an object directly out of a SQL query using the NamedNativeQuery and SqlResultSetMapping annotations.
#Entity
#SqlResultSetMapping(name = "compositekey", entities =
#EntityResult(entityClass = MiniBar.class,
fields = { #FieldResult(name = "miniBar", column = "BAR_ID"), })
)
#NamedNativeQuery(name = "compositekey",
query = "select BAR_ID from BAR", resultSetMapping = "compositekey")
#Table(name = "BAR")
public class Bar {
Flavor the SQL query to your taste

How to have 2 collections of the same type in JPA?

I've got 2 entities in JPA: Entry and Comment. Entry contains two collections of Comment objects.
#Entity
public class Entry {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#IndexColumn(base = 1, name = "dnr")
private List<Comment> descriptionComments = new ArrayList<Comment>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#IndexColumn(base = 1, name = "pmnr")
private List<Comment> postMortemComments = new ArrayList<Comment>();
...
}
To store such objects, JPA+Hibernate creates "Entry" table, "Comment" table and SINGLE "Entry_Comment":
create table Entry_Comment (Entry_id integer not null, postMortemComments_id integer not null, pmnr integer not null, descriptionComments_id integer not null, dnr integer not null, primary key (Entry_id, dnr), unique (descriptionComments_id), unique (postMortemComments_id))
Storing of objects fail as descriptionComments_id and postMortemComments_id cannot be "not null" at the same time.
How do I store object containing two collections of the same type using JPA+Hibernate?
This is one of the many Hibernate bugs (HHH-3410 to be precise).
I've managed to fix it by adding #JoinTable annotations to #OneToMany relationships, each having its own table name.
In your case it would look like this:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name="entity_descriptioncomments")
#IndexColumn(base = 1, name = "dnr")
private List<Comment> descriptionComments = new ArrayList<Comment>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name="entity_postmortemcomments")
#IndexColumn(base = 1, name = "pmnr")
private List<Comment> postMortemComments = new ArrayList<Comment>();
Note: you must add #IndexColumn annotation as well (because of the other Hibernate issue with multiple EAGER bags: HHH-1718/EJB-346).
To store 2 collections like that in JPA with DataNucleus (http://www.datanucleus.org) you would do exactly as you've done. You have no #JoinTable annotation hence a FK should be placed in Comment for each of the collections. If you actually do have #JoinTable somewhere (or XML equivalent) then setting the names of the respective join tables (one for each collection) would work too (so they have their own join table). Having a shared join table between 2 collections is possible in DataNucleus too, but that's not standard JPA, instead a vendor extension.
How that maps to Hibernate I've no idea, but then this is JPA so should be consistent since thats the point of having a spec ;-)
There is a flaw with current mapping from data model/domain model point of view: you actaully have a single #OneToMany relationship between Entry and Comment. And Comment entity should have one more attribute called type that takes 2 values: 'description' or 'postMortem'.
To be inline with your current implementation of Entry entity you may want to consider breaking down Comment entity into 2 different entities (possibly using JPA inheritance features) and using #JoinTable annotation in Entry.
If all you care about is ordering, how about configuring the two index column definitions to have the same name?

Categories