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
Related
I'm still using the old org.hibernate.Criteria and get more and more confused about fetch modes. In various queries, I need all of the following variants, so I can't control it via annotations. I'm just switching everything to #ManyToOne(fetch=FetchType.LAZY), as otherwise, there's no change to change anything in the query.
What I could find so far either concerns HQL or JPA2 or offers just two choices, but I need it for the old criteria and for (at least) the following three cases:
Do a JOIN, and fetch from both tables. This is OK unless the data is too redundant (e.g., the master data is big or repeated many times in the result). In SQL, I'd write
SELECT * FROM item JOIN order on item.order_id = order.id
WHERE ...;
Do a JOIN, fetch from the first table, and the separation from the other. This is usually the more efficient variant of the previous query. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...;
SELECT order.* FROM order WHERE ...;
Do a JOIN, but do not fetch the joined table. This is useful e.g., for sorting based on data the other table. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...
ORDER BY order.name, item.name;
It looks like without explicitly specifying fetch=FetchType.LAZY, everything gets fetched eagerly as in the first case, which is sometimes too bad. I guess, using Criteria#setFetchMode, I can get the third case. I haven't tried it out yet, as I'm still missing the second case. I know that it's somehow possible, as there's the #BatchSize annotation.
Am I right with the above?
Is there a way how to get the second case with the old criteria?
Update
It looks like using createAlias() leads to fetching everything eagerly. There are some overloads allowing to specify the JoinType, but I'd need to specify the fetch type. Now, I'm confused even more.
Yes you can satisfy all three cases using FetchType.LAZY, BatchSize, the different fetch modes, and projections (note I just made up a 'where' clause with Restrictions.like("name", "%s%") to ensure that I retrieved many rows):
Do a JOIN, and fetch from both tables.
Because the order of an item is FetchType.LAZY, the default fetch mode will be 'SELECT' so it just needs to be set as 'JOIN' to fetch the related entity data from a join rather than separate query:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.setFetchMode("order", FetchMode.JOIN);
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting single SQL query:
select
this_.id as id1_0_1_,
this_.name as name2_0_1_,
this_.order_id as order_id3_0_1_,
order2_.id as id1_1_0_,
order2_.name as name2_1_0_
from
item_table this_
left outer join
order_table order2_
on this_.order_id=order2_.id
where
this_.name like ?
Do a JOIN, fetch from the first table and the separately from the other.
Leave the fetch mode as the default 'SELECT', create an alias for the order to use it's columns in sorting, and use a projection to select the desired subset of columns including the foreign key:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(org.hibernate.criterion.Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting first SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
and subsequent batches (note I used #BatchSize(value=5) on the Order class):
select
order0_.id as id1_1_0_,
order0_.name as name2_1_0_
from
order_table order0_
where
order0_.id in (
?, ?, ?, ?, ?
)
Do a JOIN, but do not fetch the joined table.
Same as the previous case, but don't do anything to prompt the loading of the lazy-loaded orders:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getName()));
The resulting single SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
My entities for all cases remained the same:
#Entity
#Table(name = "item_table")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
// getters and setters omitted
}
#Entity
#Table(name = "order_table")
#BatchSize(size = 5)
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters and setters omitted
}
I am using Spring Boot 2 with Hibernate and I have an entity named ItemCarga with this:
#ManyToMany(cascade = { CascadeType.DETACH }, fetch = FetchType.LAZY)
#JoinTable(name = "rel_transp_carga", joinColumns = { #JoinColumn(name = "fk_item_carga") }, inverseJoinColumns = { #JoinColumn(name = "fk_transportadora") })
#LazyCollection(LazyCollectionOption.TRUE)
private Set<Transportadora> transportadoras = new HashSet<Transportadora>();
Whey do a query using that entity on my repository, like this:
#Query("select e from ItemCarga e where e.cnpjCeramica = :cnpjCeramica and (e.dataInserido between :inicio and :fim) ")
List<ItemCarga> listarProdutosPorPeriodo(
#Param("cnpjCeramica") String cnpjCeramica,
#Param("inicio") Date dataInicial,
#Param("fim") Date dataFinal,
Sort sort);
The result is a set of ItemCarga entity with the attribute transportadoras fetched with all its items.
It shouldn't it be null or empty?
Shouldn't be ignored since I didn't mention that attribute on my select?
You did mention the attribute in the query: select e from ItemCarga e. This means you're fetching the whole ItemCarga entity. Since you defined transportadoras as fetch = FetchType.LAZY, a proxy is created (the data is not fetched from the database).
If you're invoking the query withing transaction, you can iterate over the set, then hibernate will fetch the child entities (this often leads to n+1 select problem). If you try to access it outside of transaction, LazyInitializationException will be thrown.
Since it's just a hint for hibernate, you can make sure, the Set won't be fetched in a couple of ways:
by not querying for it, for example:
#Query("select e.field1, e.field2 from ItemCarga e ...")
List<Object[]> listarProdutosPorPeriodo...
The downside is that you have to cast the results,
by using dto and query mapping. I won't describe it in detail, more you can find here,
by using projections - interfaces with getters and setters for the fields you want to fetch. More details here.
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.
I am trying to use the JPA CriteriaBuilder to generate a query for an entity called "TestContact" that has a many-to-many join with another entity called "SystemGroup" where the attribute for this join called "groups". The objective of the query is to retrieve records from the "TestContact" entity where the "groups" attribute is either in a list or is empty.
The code I'm using is as follows
public List<TestContact> findWithCriteriaQuery(List<SystemGroup> groups) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<TestContact> cq = cb.createQuery(TestContact.class);
Root<TestContact> testContact = cq.from(TestContact.class);
cq.select(testContact);
Path<List<SystemGroup>> groupPath = testContact.get("groups");
// cq.where(groupPath.in(groups));
// cq.where(cb.isEmpty(groupPath));
cq.where(cb.or(cb.isEmpty(groupPath), groupPath.in(groups)));
TypedQuery<TestContact> tq = em.createQuery(cq);
return tq.getResultList();
}
The problem is this query only returns results where group is in the list "groups" but for some reason isn't also returning the results where group is empty (i.e. there is no entry in the join table)
If I change the where clause to cq.where(cb.isEmpty(groupPath)); then the query correctly returns the results where group is empty.
If I change the where clause to cq.where(groupPath.in(groups)); then the query correctly returns the results where the group is in the list "groups".
What I don't understand is why when I try to combine these two predicates using the CriteriaBuilder or method the results don't include the records where the group is either in the list or is empty.
The groups attribute in the "TestContact" entity is declared as follows
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name = "TEST_CONTACT_GROUPS", joinColumns = { #JoinColumn(name = "CONTACT_ID", referencedColumnName = "CONTACT_ID") }, inverseJoinColumns = { #JoinColumn(name = "GROUP_ID", referencedColumnName = "GROUP_ID") })
private List<SystemGroup> groups;
The JPA provider is EclipseLink 2.5.0, the Java EE application server is GlassFish 4 and the database is Oracle 11gR2.
Can anyone please point out where I'm going wrong?
Update
I've tried the suggestion from #Chris but Eclipse is returning the following error on Join<List<SystemGroup>> groupPath = testContact.join("groups", JoinType.LEFT)
Incorrect number of arguments for type Join; it cannot be
parameterized with arguments >
Looking at the JavaDoc for Join it says the type parameters are...
Z - the source type of the join, X - the target type of the join
I've tried Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT); which then causes Eclipse to return the following error on cb.isEmpty
Bound mismatch: The generic method isEmpty(Expression) of type
CriteriaBuilder is not applicable for the arguments
(Join). The inferred type SystemGroup is not
a valid substitute for the bounded parameter >
The testContact.get("groups"); clause forces an inner join from testContact to groups, which filters out testContacts with no groups. You need to specify a left outer join, and use that in your isEmpty and in clauses.
Root<TestContact> testContact = cq.from(TestContact.class);
cq.select(testContact);
Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT);
cq.where(cb.or(cb.isEmpty(testContact.get("groups")), groupPath.in(groups)));
I usually refer to https://en.wikibooks.org/wiki/Java_Persistence/Criteria#Join for examples
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