Spring data findAll() does not fetch eagerly - java

I have two entities with unidirectional one to many relationship.
#Entity
public class Basket {
#Id
#GeneratedValue
private Long id;
private int capacity;
}
#Entity
public class Item {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
private Basket basket;
}
I save couple of objects:
Basket basket1 = new Basket(100);
Basket basket2 = new Basket(200);
Basket basket3 = new Basket(300);
basketRepository.save(asList(basket1, basket2, basket3));
Item item1 = new Item("item1", basket1);
Item item11 = new Item("item11", basket1);
Item item2 = new Item("item2", basket2);
Item item22 = new Item("item22", basket2);
Item item3 = new Item("item3", basket3);
Item item33 = new Item("item33", basket3);
itemRepository.save(asList(item1, item11, item2, item22, item3, item33));
// Loading one item. Basket fetched eagerly.
itemRepository.findOne(1L);
// Loading many items. Baskets are not loaded (n+1 select problem).
itemRepository.findAll();
#ManyToOne annotation uses eager fetch by default.
When I load one Item using findOne(), Hibernate generates query with left outer join and Basket is fetched in the same query.
However when I use findAll(), Hibernate first fetches all Items and then executes N selects (one per each Basket), so that it leads to (n+1) select problem. Why Hiberante doesn't eagerly fetch Basket objects with findAll() method and how to fix this?

From JPA 2.0 spec, #ManyToOne are by default EAGER.
Now, when you use findAll() it is equivalent to firing a JPQL query like entityManager.createQuery(...) and it by default loads the items first and subsequently for each item it loads the basket entity and causing N+1 problem.
You can follow one of the two approaches:
Override the default query used by specifying #Query annotation on the findAll method and use the query with join like select i from Item i left join fetch i.basket.
Use #NamedEntityGraph with name say basket on Item class and specify which part of the Item graph needs to be loaded eagerly. On the findAll method, use #EntityGraph(value = "basket"). Note that as per spring jpa entity graph, we can also use attributePath to define ad-hoc entity graphs via #EntityGraph without the need of having to explicitly add #NamedEntityGraph to your domain types.

You can override findAll method with #Query annotation in your repository. Below is the sample code
public interface ItemRepository extends CrudRepository<Item, Long> {
#Override
#Query("select item from Item item left join fetch item.basket")
Iterable<Item> findAll();
}
Then you can log your sql queries to see that only one query is made
Hibernate: select item0_.id as id1_1_0_, basket1_.id as id1_0_1_, item0_.basket_id as basket_i3_1_0_, item0_.name as name2_1_0_, basket1_.capacity as capacity2_0_1_ from item item0_ left outer join basket basket1_ on item0_.basket_id=basket1_.id
And before it was
2018-03-09 13:26:52.269 INFO 4268 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select item0_.id as id1_1_, item0_.basket_id as basket_i3_1_, item0_.name as name2_1_ from item item0_
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?

Related

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.

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

JPA 2.1 - How to use dynamic join conditions with the Criteria API to fill OneToMany associations?

I'm using Glassfish 4.1 and JPA 2.1 powered by EclipseLink + Postgresql 9.4.1.
Let's assume we have a car rental company. A customer can rent a car, but the customer can rent
the same car only once. Now the goal is to return a list of all cars. However, for each car in the list
we want to tell the user whether the user ever rented this car before of not. This additional information
(for the UI) can be either a (transient?) boolean flag. In our case, I guess simply filling a corresponding association with the right data
fits exactly what we want (see code below). However, I am not very sure how to use a flag instead - any advice here? Anyway...
We have to use the Criteria API, as there are
many other dynamic filters which we need (irrelevant for this question), so using a NamedQuery with JPQL or
even a NamedNativeQuery is not possible and not in our favor.
In other words:
The list of cars should contain all available cars
Each car in the list ever rented by user 123456 should also have the corresponding rental (the length of this list would always be one then)
The Criteria API should generate exactly 1 native SQL query which uses the correct JOIN conditions
The association "rentals" for each car should be either empty or filled with exactly one Rental instance of the given user
Instead of the given association it would be possibe to use a boolean flag instead, i.e. "alreadyRented" - any idea?
I know how to do this outside of JPA directly on the DB. But I want to use JPA for this. Any I want JPA to fill
the association automatically using a single SELECT + LEFT JOIN query, however, things are not not as easy as I thought...
Any idea? Would you suggest a different data model?
Here is our Car Entity:
#Entity
public class Car {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#NotNull
private String manufacturer; //simplified
#OneToMany(mappedBy="car", fetch=FetchType.LAZY)
private List<Rental> rentals;
//...
}
According to this mapping, the "rentals" attribute holds a list of all rentals ever made for a given car. Please note that this list is not per user!
And here is the Rental Entity, which basically holds data for all rentals for a given car (again, this is simplified).
#Entity
#Table(
name="RENTALS",
uniqueConstraints={
#UniqueConstraint(columnNames={"CUSTOMER_ID", "CAR_ID"})
}
)
public class Rental {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(optional=false, fetch= FetchType.EAGER)
#JoinColumn(name="CUSTOMER_ID", nullable=false, updatable=false)
#NotNull
private Customer customer;
#ManyToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(name="CAR_ID", nullable=false, updatable=false)
#NotNull
private Car car;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#Column(nullable=false)
#NotNull
private Date fromDate;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#Column(nullable=false)
#NotNull
private Date toDate;
//...
}
And here is finally the Customer Entity, which is used in our Rental Entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#NotNull
private String firstName;
#Column(nullable=false)
#NotNull
private String lastName;
//...
}
And here is finally my EJB, which uses the injected EntityManager to access the DB:
#Stateless
#Local
public class CarBean {
#PersistenceContext(unitName = "myPU")
private EntityManager em;
//...
public List<Car> getCarsForCustomer(Long userId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> q = cb.createQuery(Car.class);
Root<Car> rootCar = q.from(Car.class);
List<Predicate> predicates = new ArrayList<Predicate>();
//...
//can't just do this because we need a different/dynamic JOIN condition!!
//rootCar.fetch("rentals", JoinType.LEFT);
//now let's try to create the dynamic join condition:
Predicate criteria = cb.conjunction();
Join<Car,Rental> rental = rootCar.join("rentals", JoinType.LEFT);
criteria = cb.and(criteria, cb.equal(rental.get("customer").get("id"), userId) );
rental.on(criteria);
q.select(rootCar).where(predicates.toArray(new Predicate[]{}));
return em.createQuery(q).getResultList();
}
}
All this will generate the following native SQL statement:
SELECT t1.ID, t1.MANUFACTURER
FROM CAR t1
LEFT OUTER JOIN RENTALS t0
ON ((t0.CAR_ID = t1.ID) AND (t0.CUSTOMER_ID = 123456))
As you can see from the generated statement the joined RENTALS are not part of the result set. Even if it would be part of the result set I'm not sure if JPA would use them to fill the rentals association.
Using a Fetch Join is not possible, as we cannot dynamically choose the join columns/conditions. However, when I uncomment the Fetch Join (see code) then I get the following native SQL statement that uses two JOINS which I don't want:
SELECT
t1.ID, t1.MANUFACTURER, t0.ID, t0.FROMDATE, t0.TODATE, t0.CAR_ID, t0.CUSTOMER_ID
FROM CAR t1
LEFT OUTER JOIN RENTALS t0 ON (t0.CAR_ID = t1.ID)
LEFT OUTER JOIN RENTALS t2 ON ((t2.CAR_ID = t1.ID) AND (t2.CUSTOMER_ID = 123456))
So the big question is how can I fill the rentals association by using "dynamic" join conditions? What am I doing wrong?

Grid produces too many hibernate queries - FetchMode.JOIN doesn't work

I'm developing a webapp using Spring MVC and Hibernate. Thing is, that I need to show all my customer's clients, and each client has another entity associated ("Cobrador", I don't know the english translation here, sorry), I'm using JQgrid for such goal. When I execute the grid, I see in the log:
Hibernate: select cliente0_.id as id1_0_, cliente0_.activo as activo2_0_, cliente0_.apellido as apellido3_0_, cliente0_.cobrador as cobrador8_0_, cliente0_.dni as dni4_0_, cliente0_.email as email5_0_, cliente0_.nombre as nombre6_0_, cliente0_.telefono as telefono7_0_ from clientes cliente0_ where cliente0_.activo=1
Hibernate: select cobrador0_.id as id1_1_0_, cobrador0_.activo as activo2_1_0_, cobrador0_.apellido as apellido3_1_0_, cobrador0_.dni as dni4_1_0_, cobrador0_.email as email5_1_0_, cobrador0_.nombre as nombre6_1_0_, cobrador0_.telefono as telefono7_1_0_ from cobradores cobrador0_ where cobrador0_.id=?
Hibernate: select cobrador0_.id as id1_1_0_, cobrador0_.activo as activo2_1_0_, cobrador0_.apellido as apellido3_1_0_, cobrador0_.dni as dni4_1_0_, cobrador0_.email as email5_1_0_, cobrador0_.nombre as nombre6_1_0_, cobrador0_.telefono as telefono7_1_0_ from cobradores cobrador0_ where cobrador0_.id=?
Hibernate: select cobrador0_.id as id1_1_0_, cobrador0_.activo as activo2_1_0_, cobrador0_.apellido as apellido3_1_0_, cobrador0_.dni as dni4_1_0_, cobrador0_.email as email5_1_0_, cobrador0_.nombre as nombre6_1_0_, cobrador0_.telefono as telefono7_1_0_ from cobradores cobrador0_ where cobrador0_.id=?
Basically getting the clients, and then, for each client go gets the associated "cobrador". My Client entity is configured as follow:
#Entity
#Table(name="clientes")
public class Cliente {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String apellido;
private String nombre;
private int dni;
private String telefono;
private String email;
private boolean activo;
#ManyToOne
#JoinColumn(name="cobrador")
#Fetch(FetchMode.JOIN)
private Cobrador cobrador;
//Contructors, getters and setters
}
BTW: The final hibernate execution is:
#Override
#Transactional
public List<T> getAllFiltering(String filter) {
Query q = sessionFactory.getCurrentSession().createQuery("from " + type.getSimpleName() + " " + filter);
return q.list();
}
Where is and filter is " where activo=true".
Is there anyway to configure this relationship in order to execute only 1 query when loading the grid?
Thanks in advance!
I know it is not very convenient but Hibernate will not use the fetch strategy if you are using an HQL query (I.E using the createQuery method). If you want to make it work, you must use the Criteria API or specify the join in the HQL query.
In your case the query might be something like this :
from Cliente c left join fetch c.cobrador
From the Hibernate documentation :
The fetch strategy defined in the mapping document affects:
retrieval via get() or load()
retrieval that happens implicitly when an association is navigated
Criteria queries
HQL queries if subselect fetching is used
As you can see, the fetch strategy defined doesn't affect HQL queries
if JOIN is the fetchMode.

one-to-many: making Hibernate select the reference's id instead of joining it

I have two classes stored in my database using Hibernate. Let's call them Container and Item. Item has a one-to-many relation to Container:
#entity(name = "containers")
public class Container {
#Id
#GeneratedValue
private long id;
}
#entity(name = "items")
public class Item {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "container_id")
private Container container;
}
I want to select select all for all items the tuple [ (long)item.id, (long)item.container_id ], but Hibernate seems to insist on retrieving [ (long)item.id, (Container)item.container ], introducing a useless (and expensive) join.
I tried that criteria query:
Criteria criteria = session.
createCriteria(Link.class).
add(Restrictions.isNotNull("container")).
setProjection(Projections.projectionList().
add(Projections.id()).
add(Projections.property("container")));
Is there a matching criteria query. Has to be possible without HQL queries or native SQL queries, hasn't it?
Edit 1: Working HQL query:
session.createQuery("SELECT item.id, item.container.id " +
"FROM items AS item " +
"WHERE item.container <> NULL")
Edit 2: FetchType.LAZY is not an option.
Criteria criteria = session.createCriteria(Link.class);
criteria.createAlias("container", "containerAlias");
criteria.add(Restrictions.isNotNull("containerAlias.id"));
criteria.setProjection(Projections.projectionList()
.add(Projections.id())
.add(Projections.property("containerAlias.id")));
It should be sufficient to add a fetch = FetchType.LAZY attribute to the #ManyToOne annotation. If you've annotated an ID column in Container, the test for whether item.container is null should not require a join.

Categories

Resources