I want select an entity with some determined fields. Other fields let be NULL.
My issue...
EntA has refences to EntB and list of EntC.
class EntA {
#Id
#GenericGenerator(name = "unique_id", strategy = "uuid")
#GeneratedValue(generator = "unique_id")
#Column(name = "id", nullable = false, length = 32, unique = true)
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "entbs", nullable = false, unique = false)
private EntB entB;
#OneToMany(mappedBy = "entcs", fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private List<EntC> listEntC;
... other fields , getters & setters ...
}
A few days ago i had complemented bacis procedure of selection with folowing code. Code provides selection an object with some determined fields, other fields will be as null. Some like as SQL`s "select fieldA, fieldJ from table ..."
public List<EntA> select(..., List<String> fieldList, ...) {
Criteria crit = sessionFactory.getCurrentSession().createCriteria(EntA.class);
...
ProjectionList pl = Projections.projectionList();
for (String prop : fieldList) {
pl.add(Projections.property(prop), prop);
}
crit.setProjection(pl);
crit.setResultTransformer(Transformers.aliasToBean(EntA.class));
...
return crit.list();
}
All works good, for example: i select some fields (not all in entity) even repeat some field. Result = object of class EntA which has fields "id","EntB" and "other_field_name" with values, other fields are NULL.
List<String> fieldList = new ArrayList<String>();
fieldList.add("id");
fieldList.add("entB");
fieldList.add("entB");
fieldList.add("entB");
fieldList.add("other_field_name");
select(..., fieldList, ...);
Hibernate:
/* criteria query */ select
this_.id as y0_,
this_.entB as y1_,
this_.entB as y2_,
this_.entB as y3_,
this_.other_field_name as y4_
from
EntA this_
Hibernate:
select
entb0_.id as id1_12_0_,
entb0_.idx as idx2_12_0_,
entb0_.name as name3_12_0_
from
EntB entb0_
where
entb0_.id=?
But...
fieldList.add("id");
fieldList.add("listEntC");
select(..., fieldList, ...);
Hibernate:
/* criteria query */ select
this_.id as y0_,
this_.id as y1_
from
EntA this_
java.lang.ArrayIndexOutOfBoundsException: 1
at org.hibernate.loader.criteria.CriteriaLoader.getResultRow(CriteriaLoader.java:168)
at org.hibernate.loader.criteria.CriteriaLoader.getResultColumnOrRow(CriteriaLoader.java:148)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:753)
at org.hibernate.loader.Loader.processResultSet(Loader.java:952)
at org.hibernate.loader.Loader.doQuery(Loader.java:920)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
at org.hibernate.loader.Loader.doList(Loader.java:2553)
at org.hibernate.loader.Loader.doList(Loader.java:2539)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2369)
at org.hibernate.loader.Loader.list(Loader.java:2364)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:126)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1682)
at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:380)
*************************************.select(BaseDao.java:141)
Several days i debug my code & hibernate-core sources and cant understand:
if i use simple selection without projections and transformers, i get normal object of EntA which List<EntC> lstEntC has values
if i use projection without transformers - i get not EntA
if i use projection with transformers, a can select only non-list fields
Have you any ideas about how to use Criteria`s selection with projection result into class?
thanks.
Related
Suppose we have a simple entity "Customer" which has a OneToOne relationship to an entity "Address". The foreign key is on the address side.
#Entity
public class Customer extends EntityBase {
#Column(name = "name", nullable = true)
private String name;
#OneToOne(mappedBy = "customer")
private Address address;
// getter, setter, ...
}
#Entity
public class Address extends EntityBase {
#OneToOne(optional = false)
private Customer customer;
#Column(nullable = true)
private String street;
#Column(nullable = true)
private String zip;
#Column(nullable = true)
private String city;
// getter, setter, ...
}
If you now load all Customer entities using hibernate and print the resulting queries to the console, you can see that Hibernate internally fires only a single query.
session.createCriteria(Customer.class).list();
What Hibernate does:
select
this_.id as id1_1_1_,
this_.name as name2_1_1_,
address2_.id as id1_0_0_,
address2_.city as city2_0_0_,
address2_.customer_id as customer5_0_0_,
address2_.street as street3_0_0_,
address2_.zip as zip4_0_0_
from
Customer this_
left outer join
Address address2_
on this_.id=address2_.customer_id
If you load the Customer entity with QueryDSL it will run one count query (what is expected and okay), one select query for the Customer entity and one query for each customer in the resultset. This means, if I want to load 1000 customers it will run 1002 SQL queries. This is a lot of network traffic and slows down the application.
new HibernateQuery<Customer>(session).from(QCustomer.customer).fetchResults();
What Hibernate with QueryDSL does:
select
count(customer0_.id) as col_0_0_
from
Customer customer0_
select
customer0_.id as id1_1_,
customer0_.name as name2_1_
from
Customer customer0_
select
address0_.id as id1_0_1_,
address0_.city as city2_0_1_,
address0_.customer_id as customer5_0_1_,
address0_.street as street3_0_1_,
address0_.zip as zip4_0_1_,
customer1_.id as id1_1_0_,
customer1_.name as name2_1_0_
from
Address address0_
inner join
Customer customer1_
on address0_.customer_id=customer1_.id
where
address0_.customer_id=?
Question:
Is it possible to set something like a global FetchMode for QueryDSL queries. In Hibernate you can specify this with #Fetch(FetchMode.JOIN) but unfortunately this is ignored by QueryDSL.
So my destination is to load 1000 customers with QueryDSL and only run 2 queries (count + select).
I already know that there is a way to specify something like this:
new HibernateQuery<Customer>(session)
.from(QCustomer.customer)
.leftJoin(QCustomer.customer.address).fetchJoin()
.fetchResults();
But this is error-prone because you have to specify it in every query and I don't want to declare every join by myself. QueryDSL already does it automatically when using predicates:
new HibernateQuery<Customer>(session)
.from(QCustomer.customer)
.where(QCustomer.customer.address.street.in("Musterstraße 12"))
.fetchResults();
So I want to use the above expression to load my customers, but I don't want to fire thousands of requests to my database and I also don't want to declare every join by myself. Is this possible?
I pushed an example project here: https://github.com/MatWein/testproject
Once I have the same problem. But i am working with Hibernate JPA criteria Query.
If you want to get your result in one Query then one way Is to use
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "address_id", insertable = false, updatable = false, referencedColumnName = "id")
private Address address;
Or i have a solution with Criteria Query. May be it helps you to convert it into DSL.
create a root of Customer class.
Root<Customer> root = . . .
Join<Customer, Address> join = (Join<Customer, Address>)root.fetch(Customer_.address);
for reference see my Question
I have following model:
#Entity
#Table(name = "SAMPLE_TABLE")
#Audited
public class SampleModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "SHORT_NAME", nullable = true)
private String shortName;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
Now I would like to query only columns: id, name, shortName and mentor which referes to User entity (not complete entity, because it has many other properties and I would like to have best performance).
When I write query:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
I have following exception:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
Query before exception:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
I know that I can replace fetch with join but then I will have N+1 problem. Also I do not have back reference from User to SampleModel and I do not want to have..
I ran into this same issue, and found that I was able to work around it by using:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
instead of
CriteriaQuery<X> crit = builder.createQuery(X.class);
A little extra work has to be done to produce the end result, e.g. in your case:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
It's been a long time since the question was asked. But I wish some other guys would benefit from my solution:
The trick is to use subquery.
Let's assume you have Applicant in your Application entity (one-to-one):
#Entity
public class Application {
private long id;
private Date date;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
This code will generate a select statement for Application, and select statements for Applicant per each Application.
Note that you have to define an appropriate constructor corresponding to your multiselect.
I got the same problem using EclipseLink as the JPA provider : I just wanted to return the id of a mapped entity («User» in Gazeciarz's example).
This can be achieved quite simply by replacing (in the query.multiselect clause)
root.get(SampleModel_.mentor)
with something like
root.get(SampleModel_.mentor).get(User_.id)
Then, instead of returning all the fields of User, the request will only return the its id.
I also used a tuple query but, in my case, it was because my query was returning fileds from more than one entity.
when i create a count query with hibernate - Criteria - add all the possible table from the entity class as left join which is bad performance .
The entity :
#Entity
#Table(name = "employees")
Public Class Employees {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "lz_job_stat_id")
private Integer id;
#ManyToOne
#JoinColumn(name = "departments_id")
private Departments departments;
#ManyToOne
#JoinColumn(name = "managers_id")
private Managers managers;
}
And the criteria :
public class EmployeeDao {
public List<EmpDao> findIt(){
.....
Criteria crit = createEntityCriteria().setFetchMode("departments", FetchMode.SELECT);
crit.add(Restrictions.eq("managers.deleted", false));
crit.setProjection(Projections.count("id"));
return crit.list();
}
}
And the produced SQL :
select count() as y0_
from employees this_
left outer join departments department3_
on this_.department_id=department3_.department_id
left outer join managers manager2_
on this_.manager_id=manager2_.manager_id
now when i try the crit.list - it create a left join for all the possible tables.
when its not supposed to create a join for all of them.
isnt Criteria smart enought to know i dont need this tables ? only the one i use the "WHERE CLAUSE"
is there a way to explicitly tell Criteria "DO NOT JOIN THIS TABLES !!!"
without SQL
Specify fetch type on ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "departments_id")
private Departments departments;
or IMHO more preferably in criteria:
criteria.setFetchMode("departments", FetchMode.SELECT)
I have two tables with no modeled relation:
Table comm with columns:
name
date
code
Table persondesc with columns:
code
description
Relationship between the two tables is many to one (many comm to one persondesc):
com.code = persondesc.code
These two tables are mapped with annotations but I have no relation declared.
What I'm trying to is to select comm table ordered by persondesc.description.
How can I do this JPA and Hibernate?
So if your classes have no "relation", then you do a query like
SELECT a FROM A a
CROSS JOIN B b
WHERE a.someField = b.otherField
ORDER BY b.anotherField
Which can be achieved using JPA Criteria, something like
CriteriaBuilder cb = emf.getCriteriaBuilder();
CriteriaQuery<A> query = cb.createQuery(A.class);
Root<A> aRoot = query.from(A.class);
Root<B> bRoot = query.from(B.class);
aRoot.alias("a");
bRoot.alias("b");
query.select(aRoot)
.where(cb.equal(aRoot.get(A_.someField), bRoot.get(B_.otherField))
.orderBy(cb.asc(bRoot.get(B_.anotherField)));
... Or just redesign your classes and do your developers a favour.
Hibernate 5.1 introduced explicit joins on unrelated entities for JPQL. So now you can just write a JOIN like native SQL:
List<Comm> results = entityManager
.createQuery("""SELECT c FROM Comm c
JOIN PersonDesc pd ON c.code = pd.code
ORDER BY pd.description""", Comm.class)
.getResultList();
Click here for more detailed example.
In case you need to sort by column which is in another table, you can create "fake" dependency with disabled insertable and updatable attributes. Domain model would looks like this:
Primary entity:
#Entity
public class Comm {
#Id
private Long id;
#Column(name = "name")
private String name;
#Column(name = "date")
private Date date;
#Column(name = "code")
private String code;
#OneToOne(fetch = FetchType.LAZY) // #ManyToOne is also possible
#JoinColumn(name = "code", referencedColumnName = "code", insertable = false, updatable = false)
private PersonDesc personDesc;
}
Entity with required data for sorting:
#Entity
public class PersonDesc {
#Id
private String code;
#Column(name = "description")
private String description;
}
After you define your domain model, it possible to create criteria query:
CriteriaBuilder cb = emf.getCriteriaBuilder();
CriteriaQuery<Comm> cq = cb.createQuery(Comm.class);
Root<Comm> root = cq.from(Comm.class);
Join<Comm, PersonDesc> leftJoin = root.join("personDesc", JoinType.LEFT);
cq.select(root);
cq.orderBy(cb.asc(root.get("personDesc.description")));
One of the simplest solution is to create view.
Then create an Entity class for that view and execute query against view.
View:
create or replace view view_comm_persondesc as select c.name, c.date, c.code, p.description from comm c inner join persondesc p on c.code = p.code;
Code
#Entity(name = "view_comm_persondesc")
public class ViewCommPerson{
#Id
private String code;
private String name;
private Date date;
private String description;
... All Getters/Setters ...
}
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ViewCommPerson> query = cb.createQuery(ViewCommPerson.class);
Root<ViewCommPerson> root = query.from(ViewCommPerson.class);
// You can add your filter here
List<ViewCommPerson> result = entityManager.createQuery(query).getResultList();
Hope it servers your use case.
For a project I'm trying to lookup all distinct categories within a List #ElementCollection field. Each foo instance has one or more String categories assigned. The code below does not work as JBOSS/Hibernate throws an exception when deploying the ear to the server:
Error in named query: Foo.listUniqueCategories: org.hibernate.QueryException: not an entity [SELECT DISTINCT f.categories FROM com.Foo f]
I have the class:
#Entity(name = "Foo")
#NamedQuery(name = "Foo.listUniqueCategories", query = "SELECT DISTINCT f.categories FROM Foo f")
public class FooEntity
{
#Id()
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
protected Long id;
#ElementCollection
#CollectionTable(name = "categories", joinColumns = #JoinColumn(name = "foo_id"))
private List<String> categories;
...
}
Is there anything wrong with the select distinct? Is it even supported to perform a 'SELECT DISTINCT' on an #EllementCollection?
Any help is appreciated!
Richard
You are confused between HQL and SQL .. replace your named query with the following. Named Queries are always HQL.
SELECT distinct f.categories FROM FooEntity f
But, I'm not sure if this will work. If you need to find out the distinct categories, why query on FooEntity? Why not create an entity for Categories and run a query like below. Also, you are calling join column on a list which is wrong as well ,it should join on entity types like below
#ElementCollection
#CollectionTable(name = "categories", joinColumns = #JoinColumn(name = "foo_id"))
private List<**Category**> categories; // replace String with Category
--
select distinct category.name from Category c
Where Category is a new entity that you have to create.