Spring JPA projection problem with subquery - java

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...

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

How to add sub-select to select

I want to execute a query like this:
SELECT Table1.COL1,
Table1.COL2,
(SELECT SUM(Table2.COL3)
FROM Table2
WHERE Table2.UID = Table1.UID) SUMOF
FROM Table1;
How can I do it?
I usually create a Criteria add ProjectionList to it, to fill COL1 and COL2 only.
I have created a DetachedCriteria to calculate the sum...
Now, how to attach this detached criteria to the main one? My intuition says - it's some sort of Projection which needs to be added to the list, but I don't see how. Also, not sure how WHERE Table2.COL4 = Table1.COL5 of detached criteria will work.
Also, I'm sure this query might be written in different way, for example with join statement. It's still interesting if there's a way to run it like this.
DetachedCriteria and main Criteria
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Table2.class, "table2");
detachedCriteria
.setProjection(
Projections.projectionList()
.add(Projections.sum("table2.col3"), "sumCol3")
)
.add(Restrictions.eq("table2.uid", "table1.uid"))
;
Criteria criteria = session.createCriteria(Table1.class, "Table1");
criteria
.setProjection(
Projections.projectionList()
.add(Projections.property("Table1.col1"), "col1")
.add(Projections.property("Table1.col2"), "col2")
)
;
Entities (very short version)
#Entity
#Table(name = "Table1")
public class Table1 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col1")
public String getCol1();
#Column(name = "col2")
public String getCol2();
#Column(name = "col3")
public String getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
#Entity
#Table(name = "Table2")
public class Table2 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col3")
public BigDecimal getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
For a correlated subquery (like the one you presented above), you can use #Formula which can take an arbitrary SQL query. Then, you'll need to fetch the entity and the subquery will be executed.
However, a native SQL is more elegant if you only need this query for a single business requirement.
As for derived table queries (e.g. select from select), neither JPA nor Hibernate support derived table queries for a very good reason.
Entity queries (JPQL pr Criteria) are meant to fetch entities that you plan to modify.
For a derived table projection, native SQL is the way to go. Otherwise, why do you think EntityManager offers a createNativeQuery method?

Hibernate HQL to SQL

Given the following entity one-to-many model:
One Repository can be linked to many AuditRecords.
Many AuditRecords can all link to the same Repository
#Entity
class AuditRecordEntity {
private AuditRepositoryEntity auditRepository;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
public AuditRepositoryEntity getAuditRepository() {
return auditRepository;
}
...
}
#Entity
class AuditRepositoryEntity {
private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();
#OneToMany(mappedBy = "auditRepository")
public List<AuditRecordEntity> getAuditRecords() {
return auditRecords;
}
...
}
I have the following HQL query to get the latest (by accessTime) AuditRecord for each distinct Repository:
select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)
I would like to know the equivalent SQL for the above HQL?
(the reason for this is I'l like to add the query as an sql restriction using the criteria API, as I am having trouble translating the HQL above to use the criteria API - see Hibernate criteria implementation for this entity model (subquery, self-join))
There is one hibernate property named
hibernate.show_sql
You can set it true in your hibernate configuration file or property file. It will show you the equivalent sql query of your hql/criteria query.

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