JpaRepository makes two identical queries to database while retreiving simple entity - java

I have a very primitive Entity:
#Entity
#Table(name = "person")
public class Person {
#Id
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
}
I use a simple JpaRepository<Person, Integer> with no overriding methos. Whenever I invoke repository.findAll()method I get the following log:
org.hibernate.SQL :
select
person0_.id as id1_2_,
person0_.address as address2_2_,
person0_.name as name3_2_
from
person person0_
Hibernate:
select
person0_.id as id1_2_,
person0_.address as address2_2_,
person0_.name as name3_2_
from
person person0_
The same result I get invoking repository.getOne(). Repository while trying to get a very simple data uses two identical queries one after another with seemingly no particular reason. Is this just how it visualise or is it actually making two queries instead of one? Why is that?

Related

JPA Criteria: QuerySyntaxException for left join on treat entity

Model:
#Entity
public class User {
#Id
private Integer id;
#JoinColumn(name = "user_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
I am trying to use JPA's criteria api to query for User entities based on an attribute of an implementation of Project. For example, query all users that have a project with "SOME_NAME" department (that field does not exist on DesignProject).
I see there is a way of doing so via downcasting of the Project entity for the query. I am trying something similar to:
CriteriaBuilder cb...
Root<User> userRoot...
root = ((From) root).join("projects", JoinType.LEFT);
root = cb.treat(root, AdminProject.class);
root = root.get("departmentName");
Exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]
What am I missing? Is it something related to the join, or how the downcasting occurs afterwards?
Edit
After the answer by #K.Nicholas, I have managed to make the query work on an isolated scenario, but not on my app.
But, I noticed that the entityManager.createQuery(query) call throws the exception above when called for the first time, and it works if I call it again without changing the query object. Here is the query generated on the second call (this query finds the objects I want from the database):
select generatedAlias0 from User as generatedAlias0 left join generatedAlias0.projects as generatedAlias2 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0
Why is the entity manager creating two different queries when called two consecutive times?
I would do the Entitys a little different, as you will see. The main concern is that you are using User as your root with a join to a list of Projects. This is a concern because you should have the foreign key on the Project class and use the projects field as a query only field. That is what I have done. It works better that way. It is also a concern because you have to do a join fetch instead of a join so that the projects get fetched along with the users.
So, first, the entities are like so:
#Entity
public class User {
#Id
private Integer id;
#OneToMany(mappedBy="user")
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
#ManyToOne
private User user;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
After a bit a digging I found a JPQL query that does the trick. This was a starting point:
List<User> users = entityManager.createQuery("select distinct(u) from User u join fetch u.projects p where TYPE(p) = 'Administrative' and p.departmentName = 'dept1'", User.class).getResultList();
After a bit more digging I found that the treat worked fine if you do it correctly and that with JPA 2.1 you should use an EntityGraph do get the join to do a fetch.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, Project> join = root.join("projects");
query.select(root).where(builder.equal(builder.treat(join, AdminProject.class).get("departmentName"), "dept1"));
EntityGraph<User> fetchGraph = entityManager.createEntityGraph(User.class);
fetchGraph.addSubgraph("projects");
users = entityManager.createQuery(query.distinct(true)).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
As a side note the queries generated as slightly different but I didn't look that closely at them. You should.

Optional OneToOne relation in Hibernate

Currently working on a project where we want to extract with Hibernate the following datamodel (model is a little bit simplified). We have a class A which contains some optional data which is stored in class B
#Entity
#Data
#Table(name = "A")
public class Country {
#Id
private UUID id;
private String someCommon;
#PrimaryKeyJoinColumn
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private B details;
}
#Entity
#Data
#Table(name = "B")
public class B {
#Id
private UUID id;
private String someDetail;
}
Fetching data works fine, except that when class B is not found for some instance of A, Hibernate does an extra query for that specific instance to retrieve the details of A. I.e in the logs these are the queries executed:
select a0_.id as id1_0_0_, b1_.id as id1_1_1_, a0_.some_common as some_common2_0_0_, b1_.some_detail as some_detail_2_1_1_ from a a0_ left outer join b b1_ on a0_.id=b1_.id
select b0_.id as id1_1_0_, b0_.some_detail as some_detail_2_1_0_ from b b0_ where b0_.id=?
Where in the second query the id is set to the id of the instance which does not have details.
So it looks like Hibernate is not supporting optional OneToOne relationships in an efficient manner. Any ideas on how to force Hibernate not doing the second query but just accepting the details are null?
There is no way to get rid of second query as you mentioned in hibernate because If the association is optional, Hibernate has no way to know if an address exists for a given person without issuing a query. so closest thing you can do is to call for second query only when its intended:
So as to avoid second query you have to opt for Lazy Loading:
To do that change your mapping to set optional to false and lazy loading will be on on details:
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
private B details;
Lazy loading makes sure details will be fetched only when its intended.

Many to many relationship for same type entity

I have an entity as below. I am curious if it is possible to create a relationship as I will be describing with the example:
I am creating 2 Person entities Michael and Julia.
I am adding Julia to Michael's friends set.
After that I am retrieving Michael as a JSON response and Julia is available in the response. But when I am retrieving Julia, her friends set is empty. I want to create the bidirectional friendship relation by saving just one side of the friendship. I would like to get Michael on Julia's friends set without doing any other operations. I think that it must be managed by Hibernate. Is it possible and how should I do it?
#ToString(exclude = "friends") // EDIT: these 2 exclusion necessary
#EqualsAndHashCode(exclude = "friends")
public class Person{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name",unique = true)
private String name;
#JsonIgnoreProperties("friends") // EDIT: will prevent the infinite recursion
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "FRIENDSHIP",
joinColumns = #JoinColumn(name = "person_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "friend_id",
referencedColumnName = "id"))
private Set<Person> friends;
Here is my service layer code for creating a friendship:
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
return repository.save(person);
}
else{
throw new FriendshipExistsException(personName, friendName);
}
}
Related Question:
N+1 query on bidirectional many to many for same entity type
Updated the source code and this version is working properly.
// Creating a graph to help hibernate to create a query with outer join.
#NamedEntityGraph(name="graph.Person.friends",
attributeNodes = #NamedAttributeNode(value = "friends"))
class Person {}
interface PersonRepository extends JpaRepository<Person, Long> {
// using the named graph, it will fetch all friends in same query
#Override
#EntityGraph(value="graph.Person.friends")
Person findOne(Long id);
}
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
friend.getFriends().add(person); // need to setup the relation
return repository.save(person); // only one save method is used, it saves friends with cascade
} else {
throw new FriendshipExistsException(personName, friendName);
}
}
If you check your hibernate logs, you will see:
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)

Understanding how spring-data handles #EntityGraph

(I made a SSCCE for this question.)
I have 2 simple entities : Employee and Company. Employee has a #ManyToOne relationship with Company with default fetch strategy (eager).
I want to be able to load the Employee without the Companywithout changing the fetch strategy defined in the Employee because I need to do that for only one use case.
JPA's entity graph seems to be intended for this purpose.
So I defined a #NamedEntityGraphon the class Employee:
#Entity
#NamedEntityGraph(name = "employeeOnly")
public class Employee {
#Id
private Integer id;
private String name;
private String surname;
#ManyToOne
private Company company;
//Getters & Setters
And a EmployeeRepository like this :
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
#EntityGraph(value = "employeeOnly", type = EntityGraph.EntityGraphType.FETCH)
List<Employee> findByCompanyId(Integer companyId);
}
Despite the use of #EntityGraph, I can see in the logs that the Company is still loaded by hibernate :
2016-11-07 23:16:08.738 DEBUG 1029 --- [nio-8080-exec-2] org.hibernate.SQL : select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?
2016-11-07 23:16:08.744 DEBUG 1029 --- [nio-8080-exec-2] org.hibernate.SQL : select company0_.id as id1_0_0_, company0_.name as name2_0_0_ from company company0_ where company0_.id=?
Why? How to avoid that?
Hibernate did not support handling non-lazy attributes as lazy, even with entity graphs. There was an issue for this: HHH-8776, but it's fixed now.
Previously, the only solution for the time being was to make the association lazy.
Modified Answer
per-specification, the fetch type for #ManyToOne is EAGER by default. But even through we set:
#ManyToOne(fetch = FetchType.LAZY)
private Company company;
You will get the same result. The problem because the way spring-data-jpa create HQL/JPQL for you. So adding #ManyToOne(fetch = FetchType.LAZY) won't work is not enough. To solve this, use#ManyToOne(fetch = FetchType.LAZY) and #Query annotation in your repository:
Employee.java :
#ManyToOne(fetch = FetchType.LAZY)
private Company company;
EmployeeRepository.java
#Query("from Employee e where e.company.id = :companyId")
List<Employee> findByCompanyIdUsingQuery(#Param("companyId") Integer companyId);
In the test, this is SQL that generated by your loadByCompanyId() (which is generate left outer join):
select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?
And this is SQL generated by method that use #Query annotation:
select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ where employee0_.company_id=?
You could check the latest code in my repository.
HTH.
Seems a bug in Hibernate.
#Dragan Bozanovic you are right. I only see one workaround in this case.
Set fetch = lazy
#Entity
#NamedEntityGraph(name = "Employee.withCompany" , attributeNodes = #NamedAttributeNode("company"))
public class Employee {
#Id
private Integer id;
private String name;
private String surname;
#ManyToOne(fetch = FetchType.LAZY)
private Company company;
Introduce new method for loading company eagerly
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
List<Employee> findByCompanyId(Integer companyId);
#Query("select e from Employee e left join e.company c where c.id = :companyId")
#EntityGraph(value = "Employee.withCompany", type = EntityGraph.EntityGraphType.FETCH)
List<Employee> findByCompanyIdFetchingCompany(#Param("companyId") Integer companyId);
}
And use following two interchangeably where required
#RequestMapping(value = "/by-company/{id}")
public void loadByCompanyId(#PathVariable Integer id) {
employeeService.loadByCompanyId(id);
}
#RequestMapping(value = "/by-company/eager-company/{id}")
public void loadByCompanyIdFetchingCompany(#PathVariable Integer id) {
employeeService.loadByCompanyIdFetchingCompany(id);
}
First one (for lazy loading) http://localhost:8080/employees/by-company/42
select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?
Second (eager loading) http://localhost:8080/employees/by-company/eager-company/42
select employee0_.id as id1_1_0_, company1_.id as id1_0_1_, employee0_.company_id as company_4_1_0_, employee0_.name as name2_1_0_, employee0_.surname as surname3_1_0_, company1_.name as name2_0_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?
I was under impression you have to specify fields within your Graph definition.
https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm (43.1.2.1 Fetch Graphs)
A fetch graph consists of only the fields explicitly specified in the
EntityGraph instance, and ignores the default entity graph settings.
Maybe not so cool solution is using a native query.
#Query(value = "select * from employee where company_id= ?1", nativeQuery = true)
List<Employee> findByCompanyId(Integer companyId);
I tested it and it was giving expected results, without being forced to set fetch = FetchType.LAZY
though the association is lazy by specifying (fetch=FetchType.Lazy) the company is exposed with a separate query call because you are exposing the Entity Employee which has property company. If you don't want the company to be exposed then either use #JsonIgnore, which is not very much recommended. And here comes one of the reasons why we make used of DTO so that we can expose only the required fields. You can create an EmployeeDTO with employee specific fields keep the association lazy which will ensure that separate query is not executed to fetch company details and map the entity to DTO either explicitly or you can make use of MapSturct Api

Receiving Error: 1064 on oneToMany mapping

Following is my DTO object of customer class. When i make some get query on hibernate i receive 1064 error
#Entity
#Table(name="customer")
public class Customer implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private int id;
#Column(name="code")
private String code;
#Column(name="address")
private String address;
#Column(name="phone1")
private String phone1;
#Column(name="phone2")
private String phone2;
#Column(name="credit_limit")
private BigDecimal creditLimit;
#Column(name="current_credit")
private BigDecimal currentCredit;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="customer_id")
private Set<Order> orders;}
then i call following method
public List<Order> allOrders(){
return orderDao.findAll();
}
this is the error i receive.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order orders0_ where orders0_.customer_id=5' at line 1
Hibernate:
select
customer0_.id as id1_0_,
customer0_.address as address2_0_,
customer0_.code as code3_0_,
customer0_.credit_limit as credit_l4_0_,
customer0_.current_credit as current_5_0_,
customer0_.phone1 as phone6_0_,
customer0_.phone2 as phone7_0_
from
customer customer0_
Hibernate:
select
orders0_.customer_id as customer2_0_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.customer_id as customer2_1_1_
from
order orders0_ where
orders0_.customer_id=?
Can you please tell what i'm doing wrong here
The problem is your Order entity: order is a reserved word in sql. Best is to change the table name to something else, e.g. #Table(name = "orders") - with an s.
Alternatively, see this answer:
If you are using Hibernate 3.5+, try
hibernate.globally_quoted_identifiers=true to quote all database
identifiers (this is something they added for JPA 2.0, see the secion
2.13 Naming of Database Objects of the spec for the JPA way to activate this if you are using JPA).

Categories

Resources