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

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.

Related

Hibernate query with #Formula-annotated attribute in order by clause

I have 2 entities:
#Entity
class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Contract> contracts= new HashSet<>();
#Formula("(select count(m.ORDER_ID) from myschema.ORDER_CONTRACTS m where m.ORDER_ID = id)")
private Integer numberOfContracts; // this is basically contracts.size()
}
and
#Entity
class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String client;
// some other properties
}
When I want to get my orders ordered by numberOfContracts, hibernate generates this query for me
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY (SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) DESC
and fails with
com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-206, SQLSTATE=42703, SQLERRMC=ORDER0_.ID, DRIVER=4.27.25
When I replace the select in the ORDER BY with formula1_ like this:
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY formula1_ DESC
I get the expected result.
Is there a way to tell hibernate to use the generated alias (formula1_) instead of replicating the formula in the order by?
EDIT:
How I get my query:
I'm using an org.springframework.web.bind.annotation.RestController. This controller offers a endpoint to get all Orders by a method like this:
#GetMapping("orders")
public List<Order> getOrders(Pageable pageable);
When I send a request like http://localhost:8080/api/orders/sort=numberOfContracts,desc&size=100&page=0
to the endpoint, the pageable contains the information about ordering. My contoller then calls my
public interface OrderRepository extends PagingAndSortingRepository<Order, Integer>
witch provides this method:
Page<Order> findAll(Pageable page);
After this point spring and hibernate do their magic.
What kind of HQL query are you using. Hibernate will just do what you tell it to do. You will have to use the HQL alias as well in the order by clause if you want the SQL alias to be used:
SELECT o.id, o.numberOfContracts as num
FROM Order o
ORDER BY num desc

Spring JPA projection problem with subquery

I'm using JPA projections but it fails when my query contains subquery.
For exemple :
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private Long id;
private String name;
}
Here is the interface for projection :
public interface StudentProjection {
Integer getId();
}
And repository :
#Query("select s from Student s where s.id = 88831")
List<StudentProjection> findProjections();
With this code everything works fine, and repo returns a list of EleveProjection.
But if I change query to this (i.e. fetching id by subselect - just for example):
#Query("select s from Student s where s.id = (select max(88831) from Student)")
List<StudentProjection> findProjections();
... method findProjections() returns a list of org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap
And obviously it's not possible to execute getId() on this object (exception thrown).
It looks like a bug in Spring data JPA.
I'm currently using version 2.2.4.RELEASE.
Any idea on how to use subqueries with JPA projections ?
Thank you
The content of #Query respect Jpql spec so it is not about spring data.
Try this
#Query("select s from Student s where s.id in (select max(88831) from Student)")
List<StudentProjection> findProjections();
AFAIK you can use clauses exists and in with subqueries in jpql...

JPA Many To Many remove entity

I use Hibernate (via JPA). There is a method to remove an entity:
public void delete(final ID id) { entityManager.createQuery(String.format("delete from %s e where e.id = :id", entityClass.getSimpleName()))
.setParameter("id", id).executeUpdate();
}
I remove the entity (with many2many relation):
Hibernate logs:
Hibernate: delete from author_to_book where (author_id) in (select id from author where id=?)
Hibernate: delete from author where id=?
Who is responsible for removing associations from the binding table? After all, my code specifies the removal from the main table only.
How it works?
Mapping:
#Entity
public class Author extends BaseEntity implements IAuthor {
#Column
private String name;
#JoinTable(name = "author_to_book",
joinColumns = {#JoinColumn(name = "author_id")} ,
inverseJoinColumns = {#JoinColumn(name = "book_id")}
)
#ManyToMany(targetEntity = Book.class, fetch = FetchType.LAZY)
#OrderBy("title ASC")
private Set<IBook> books = new HashSet<>();
Book entity does not have a mapping to Author entity
Using string to construct a Query SQL is not a good idea for JPA. JPQL or Criteria API should be used. Usually the owning side of an association is responsible for removing rows from join table. For bi-directional relations, the rows in join tables can be deleted from the other side too. It really depends on JPA implementation. As far as I know, CMobileCom JPA can manage join tables from both sides.
Disclaimer: I am a developer of CMobileCom JPA, a light-weight JPA implementation for both Android and Java JDBC.

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?

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