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.
Related
I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee
I am trying to fetch data from parent to child both based on filter criteria using JPA Criteria query so that can avoid multiple queries to DB, but not able to achieve desired result. Following are my sample entities( without getters/setters)
#Entity
public class ParentTable implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "KEY_COLUMN",length = 30)
private String keyColumn;
#Column(name = "CODE",length = 30)
private String code;
#Column(name = "KEY_DESC",length = 240)
private String desc;
#OneToMany(mappedBy = "parentTable",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ChildTable> childTableList;
}
#Entity
public class ChildTable implements Serializable{
private static final long serialVersionUID = 1L;
public ChildTable() {
super();
}
#Id
#Column(name = "ID",length = 80)
private String id;
#Column(name = "PARENT_KEY_COLUMN",length = 30,insertable = false,updatable = false)
private String parentKeyColumn;
#Column(name = "CHILD_CODE",length = 30)
private String childCode;
#Column(name = "CHILD_DESC",length = 240)
private String chldDesc;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_KEY_COLUMN", referencedColumnName = "KEY_COLUMN")
private ParentTable parentTable;
}
Criteria builder snippet -
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> query = cb.createQuery(ParentTable.class);
Root<ParentTable> fromParent = query.from(ParentTable.class);
Join<ParentTable, ChildTable> details = fromParent.join("childTableList");
List<Predicate> conditions = new ArrayList();
conditions.add(cb.equal(details.get("childCode"), childCode));
conditions.add(cb.equal(details.get("chldDesc"),chldDesc));
TypedQuery<ParentTable> typedQuery = em.createQuery(query.select(fromParent).where(conditions.toArray(new Predicate[] {})));
List<ParentTable> parentTableList = typedQuery.getResultList();
This executes and gives result of parent table only, if i fetch childtable data I can see JPA query getting exceuted again, can this be avoided and fetch list of child entities which matches 3 dynamic params? 1. ParentTable.code, 2. ChildTable.childCode , 3. ChildTable.chldDesc .
Can anyone help me to construct JPA query like below which executes in one DB hit instead of multiple round trip, which is happening in above snippet of code?
select * from ParentTable p,ChildTable c where p.KEY_COLUMN=c.PARENT_KEY_COLUMN and p.CODE=? and c.CHILD_CODE=? and c.CHILD_DESC=?
Update :
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> cq = builder.createQuery(ParentTable.class);
Root<ParentTable> root = cq.from(ParentTable.class);
Join<ParentTable, ChildTable> join = root.join("childTableList");
Predicate p1=builder.equal(root.get("code"), "code");
Predicate p2=builder.like(join.get("chldDesc"), "%chldDesc%");
Predicate p3=builder.equal(join.get("childCode"), "childCode");
Predicate andPredicate = builder.and(p1,p2, p3);
cq.select(root).where(andPredicate);
EntityGraph<ParentTable> fetchGraph = entityManager.createEntityGraph(ParentTable.class);
fetchGraph.addSubgraph("childTableList");
List<ParentTable> parentTableList=entityManager.createQuery(cq).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
parentTableList.forEach(System.out::println);
This approach forms the expected query like above mentioned but again one more query is formed like below , why second query is triggered even-though its not required?
select * from ParentTable parentTable0_ where parentTable0_.code=?
JPA is required to give you managed entity results that reflect the data in the database. The filter you put on the query does not filter the internal relationships.
Your 'join' clause only affects the filter applied to returning ParentTable entities. Every entity returned will have a complete 'childTableList' collection, so even though you are returning ParentTable instances that have specific codes and descriptions, the childTableList shows all its children. That is JPA for you, and specific providers do have a way to filter these mapped collections (AdditionalCriteria ) IMO they are a bad route with many problems.
If you want to have childTableEntries that match the specific codes and descriptions, your query should be more of the form (using JPQL)
"Select c, p from ChildTable c join c.parentTable p where c.childCode = :code and c.chldDesc = :desc"
This will return you a List result, where the Object array has the child and parent entries for each row that matches. So duplicate parents if one has more than one child that matches.
Otherwise, the extra query is being caused by accessing the childTableList on the parentTable entries, because they are marked lazy. Your criteria query is specifying 'join', as you want to use the childTableList entries in filtering parentTable entities. If you want the childTableList fetched with the parentTables, you need to use fetchJoins. Root implements FetchParent, which would allow you to specify a 'fetch' on the childTableList in addition to the join you've defined. In JPQL, something like:
"select p from ParentTable p fetch join p.childTableList, join p.childTableList c where c.childCode = :code and c.chldDesc = :desc"
I have entity Person
#Entity(name = "Person")
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person")
private Set<Phone> phones=new HashSet<Phone>();
public Person() {
}
public Person(String name) {
this.name = name;
}
Ad entity Phone :
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", nullable = false)
private Person person;
public Phone() {
}
They have one-to-many relation.
Now I want to build in jpa criteria such query:
select p.phones from person p join phone ph where p.name = :name;
So I want to extract Set<Phone> phones from Person entity where person's name is parameter.
I've written this jpa criteria query:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
CriteriaQuery<Person> where = query.where(builder.equal(root.get("name"), "Mary Dick"));
CompoundSelection<Set> projection = builder.construct(Set.class, root.get("phones"));
where.select(projection); //compile error: The method select(Selection<? extends Person>) in the type CriteriaQuery<Person> is not applicable for the arguments (CompoundSelection<Set>)
}
But it gives compile error:
The method select(Selection<? extends Person>) in the type CriteriaQuery<Person> is not applicable for the arguments (CompoundSelection<Set>)
How is it correct? Do I need metamodel classes?
CompoundSelection<Y> construct(Class<Y> result, Selection<?>... terms)
This method is useful only when the query would involve certain projections which are not entirely encapsulated by a single entity class. If that is the case, first parameter would be the custom POJO class (with suitable constructor) with fields which corresponding to the select clause of the query.
In this case, the selection is already a part of the entity class. So, you can simply choose the fields you need.
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
query.where(builder.equal(root.get("name"), "Mary Dick"));
query.select(root.get("phones"));
Above query will return a list of person. But if you are looking for just an iterable list of phones, try with a slightly different query.
select ph from phone ph join ph.person p where p.name = :name;
And its equivalent CriteriaQuery:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Phone> query = builder.createQuery(Phone.class);
Root<Phone> root = query.from(Phone.class);
Join<Phone, Person> join = root.join(root.get("person"))
query.where(builder.equal(join.get("name"), "Mary Dick"));
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.
I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.