JPQL Join on one to many database - java

I know that it has been mentioned couple of times here before, but I really can't make it working for me,
I have two entities: Recipe, Ingredient:
#Entity
#Table(name = "Recipe")
public class Recipe {
#Id
#GeneratedValue
#Column(name = "Recipe_id")
private Long id;
private String name;
private String description;
#Lob
#Basic(fetch = FetchType.LAZY)
#Column(length = 100000)
private byte[] image;
#OneToMany(mappedBy = "recipe", fetch = FetchType.EAGER)
#Cascade({CascadeType.ALL})
private List<Ingredient> ingredientsList;
....
}
and Ingredient:
#Entity
#Table
public class Ingredient {
#Id
#GeneratedValue
private Long id;
private String name;
private int cpt;
private String cptyType;
#ManyToOne
#JoinColumn(name = "Recipe_id")
private Recipe recipe;
....
}
I have also set up JPA Reposiotries, I would like to create custom query which would be equivalent to:
SELECT *
FROM `Recipe`
INNER JOIN `Ingredient` ON Recipe.Recipe_id = Ingredient.Recipe_id
WHERE Ingredient.name = "fancyName"
LIMIT 0 , 30
Until now I've tried with this one:
#Query("Select r from Recipe r join r.id i where i.name = :ingredient")
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);
End up with expcetion:
Caused by: java.lang.NullPointerException
at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromJoinElement(HqlSqlWalker.java:395)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.joinElement(HqlSqlBaseWalker.java:3477)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3263)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3141)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:694)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:550)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:287)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:235)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:248)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:183)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:101)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:119)
at org.hibernate.internal.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:214)
at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:192)
I've tried with thing such as this:
#Query("Select r from Recipe r join fetch r.ingredientsList where r.name = :ingredient")
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);
this is not resulting in any errors, but returns empty result.
It is a trival issue, but I don't have any experience with jpql before = /
EDIT:
still getting empty result:
DEBUG (SqlStatementLogger.java:104) - select recipe0_.Recipe_id as Recipe1_1_0_, ingredient1_.id as id0_1_, recipe0_.description as descript2_1_0_, recipe0_.image as image1_0_, recipe0_.name as name1_0_, ingredient1_.cpt as cpt0_1_, ingredient1_.cptyType as cptyType0_1_, ingredient1_.name as name0_1_, ingredient1_.Recipe_id as Recipe5_0_1_, ingredient1_.Recipe_id as Recipe5_1_0__, ingredient1_.id as id0__ from Recipe recipe0_ inner join Ingredient ingredient1_ on recipe0_.Recipe_id=ingredient1_.Recipe_id where ingredient1_.name=?
DEBUG (CollectionLoadContext.java:224) - No collections were found in result set for role: com.bla.model.Recipe.ingredientsList
EDIT 2:
after removing fetch from statement:
DEBUG (SqlStatementLogger.java:104) - select recipe0_.Recipe_id as Recipe1_1_, recipe0_.description as descript2_1_, recipe0_.image as image1_, recipe0_.name as name1_ from Recipe recipe0_ inner join Ingredient ingredient1_ on recipe0_.Recipe_id=ingredient1_.Recipe_id where ingredient1_.name=?
DEBUG (StatefulPersistenceContext.java:899) - Initializing non-lazy collections

Your last query searches all the recipes whose name is the ingredient name passed as argument. That's not what you want. What you want is all the recipes having an ingredient whose name is the ingredient name passed as argument:
select r from Recipe r
join r.ingredientList i
where i.name = :ingredient
Side note: why can't I have two recipes using the same ingredients? Quite limiting. The association should be a ManyToMany.

Your JPQl query should look like this:-
(As JPA already baked in the join metadata but for the #OnetoMany we can use this for JPQl)
#Query("Select r from Recipe r join Ingredient i on r.id = i.recipe.id where i.name=:ingredient)
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);

Related

Hibernate and Criteria Api generates wrong Join condition

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

Create Hibernat Query with CriteriaBuilder including Join, Group By and Count in Java

In my Java programm I got two entities Invoice and Category (like insurance, salary, car,...). Each Invoice belongs to exactly one Category. Now I want to display a table which lists all existing categories and the number if times they are actually used for an invoice. Like this:
The SQL would look like this:
select categories.name, categories.id, count(invoice.id) as usages
from categories
left join invoice on categories.id = invoice.category_id
group by categories.id
As I'm pretty new to Hibernate I studied the official documentation, but the documentation does only show examples based on a Person and Address relationship. However in my case Category does not have a direct linking to Invoice (it is the other way around: each invoice has a category id). So I played around with the code and came up with the following source code. Which obviously has one serious drawback as I do not get the categories without invoices:
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<CategoryWrapper> criteria = criteriaBuilder.createQuery(CategoryWrapper.class);
Root<Invoice> invoiceRoot = criteria.from(Invoice.class);
Join<Invoice, Category> invoiceJoin = invoiceRoot.join(Invoice_.category);
criteria.select(
criteriaBuilder.construct(
CategoryWrapper.class,
invoiceJoin.get(Category_.id),
invoiceJoin.get(Category_.name),
criteriaBuilder.count(invoiceRoot.get(Invoice_.id))
)
);
criteria.groupBy(invoiceJoin.get(Category_.id));
return session.createQuery(criteria).getResultList();
My question is: How can I get all categories with the number of usages even if no invoice exists for a specific category?
Entities
#Entity
#Table(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
[... getter and setter ]
}
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "date")
private Date date;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
#Column(name = "description")
private String description;
#Column(name = "amount")
private String amount;
#Column(name = "income", columnDefinition = "BOOLEAN")
private boolean income;
[...getter and setter...]
}
EDIT1
updated the SQL to use a left join. However using a LEFT join in the java code does not work.
Join<Invoice, Category> invoiceJoin = invoiceRoot.join(Invoice_.category, JoinType.LEFT);
I only get the categories with invoices (as shown in the image above). But in the database more categories are available:
EDIT2
As invoices is the left table and categories is the right table I tried to use RIGHT join
Join<Invoice, Category> invoiceJoin = invoiceRoot.join(Invoice_.category, JoinType.RIGHT);
but that did not work
Caused by: java.lang.UnsupportedOperationException: RIGHT JOIN not
supported
EDIT3
I checked the console output: even if I use a JoinType.LEFT (as shown in EDIT1) the programm will still use a INNER join:
select category1_.id as col_0_0_, category1_.name as col_1_0_, count(invoice0_.id) as col_2_0_ from invoice invoice0_ inner join categories category1_ on invoice0_.category_id=category1_.id group by category1_.id
But why?

How to filter select from table by another table by exclusion principle

My application under Spring Boot v1.5.7
I have 3 entities (schematically):
#Entity
public class Word {
#Id
#GeneratedValue
private Integer id
...
}
#Entity
public class UserWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
#Entity
public class UserAnotherWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
And now I need to select all Words for User, but exclude Words placed in user's lists
Native SQL for user_id=1 is
select *
from Word w
left join UserWordList uwl
on w.id = uwl.word_id and uwl.user_id = 1
left join UserAnotherWordList uawl
on w.id = uawl.word_id and uawl.user_id = 1
where uwl.word_id is NULL
and uawl.word_id is NULL
What is a best way to do it? Ideally I would like to use Spring Data features or HQL, but I don't understand how...
UPD
I solve my problem with native query:
#Entity
#NamedNativeQuery(
name = "User.getWordsToProcess",
resultClass = Word.class,
query = "<...native query to select Words...>"
)
public class User {...}
...
public interface UserRepository extends CrudRepository<User, Integer> {
List<Word> getWordsToProcess(Integer userId);
}
Fastest answer is Criteria api (but that is deprecated in hibernate 5.2 and above.)
So you can use Hql :
getSession().createQuery(" select * from UserWordList u left join fetch u.word
left join fetch u.user").list()
And you can use union or create another query to fetch UserAnotherWordList.
Also you can set any restrictions in Hql like below:
Query query = getSession().createQuery(" select * from UserWordList u left join fetch u.word left join fetch u.user us where us.user = :sample").list();
query.setParameter("sample",value);
query.list();

JPA Criteria multiselect with fetch

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.

Hibernate: separate sql query for every collection

I have a Person class that has a collection of Contacts. Everything works ok, I get the list of persons with their contacts. However, in log I see that a separate query is made to read collection of every person. That is too bad.
How to make hibernate make a join to read all the data in one query? I use JPA.
This is the person class:
#Entity
#Table(name = "tbl1")
public class PersonItem implements Serializable{
#Id
#Column(name="col1")
private String guid;
.....
#ElementCollection(targetClass = ContactItem.class,fetch=FetchType.EAGER)
#CollectionTable(name="tbl2",joinColumns=#JoinColumn(name="col2"))
private List<ContactItem> contacts;
....
}
This is the contact class
#Embeddable
#Table(name = "tbl2")
public class ContactItem implements Serializable {
#Column(name="col1")
private String guid;
#Column(name="col3")
private String info;
}
This is the way I get the list of persons:
Query query = em.createQuery("Select p from PersonItem p WHERE p.guid IN (:guids)");
query.setParameter("guids", guids);
List<PersonItem> list=query.getResultList();
And this what I see in log (I have three persons in DB):
Hibernate: select personitem0_.col1 as col1_0_, personitem0_.col4 as col2_0_, personitem0_.col2 as col3_0_, personitem0_.col3 as col4_0_ from tbl1 personitem0_ where personitem0_.col1 in (? , ? , ?)
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Please, begin from a more simple mapping. Use plural names, and column prefixes.
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column(name = "f_guid")
private String guid;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Contact> contacts;
}
#Entity
#Table(name = "contacts")
public class Contact {
#Id
#Column(name = "f_guid")
private String guid;
#Column(name = "f_info")
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_person")
private Person person;
}
Person is associated to contacts by a foreign key fk_person in the contacts table.
Update
Looks like JPQL overrides a default fetching strategy. You need to specify a fetch explicitly
select p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
If you have duplicates, cause of joins, you can use distinct
select distinct p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
Try #Fetch on your relation.
Also i would suggest to use #OneToMany relation int this case
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN) //You can use SUBSELECT as well
private List<ContactItem> contacts;
You can read more about fetching strategies here
fetch-“join” = Disable the lazy loading, always load all the collections and entities.
fetch-“select” (default) = Lazy load all the collections and entities.
batch-size=”N” = Fetching up to ‘N’ collections or entities, Not record.
fetch-“subselect” = Group its collection into a sub select statement.

Categories