Hibernate native query also gets JPA select - java

I have a two database tables, "A" and "B" with #OneToMany(mappedBy = "a") on a List<B> field in entity A, and a #ManyToOne on field B.a. I ran into the "N+1" problem when doing default queries on A, so I am trying a native query such as:
#Query(value="select * from A as a left join B as b " +
"on a.ID = b.b ",
nativeQuery=true)
This works in the sense that the data is mapped back to the entities as expected.
My problem is that I can see that Hibernate is doing a separate select for each B rather than using the results of the join. That is, I see in the console a sequence of:
The select that I specified
For each instance of A, another select for B using the ID from A
In other words, I've still got the "n+1" problem.
I figured that the #OneToMany and #ManyToOne annotations might be causing Hibernate to do these extra selects, but when I take them out, my IDE (IntelliJ) says:
'Basic' attribute should not be a container
... on the List property in A.
How can I get it to map the results back in a single select with join? Should I just give up on Hibernate and JPA?
I am using spring-boot-start-data-jpa.2.5.4

Native #Query doesn't have sufficient mapping power, so it seems that Hibernate native query must be needed.
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.hibernate.Session;
import org.hibernate.transform.BasicTransformerAdapter;
import org.springframework.stereotype.Repository;
// https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repositories.custom-implementations
#Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<A> getAll() {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#sql-entity-associations-query
final Session sess = (Session) entityManager.getDelegate();
final List<A> res = sess
// If no duplicate column names, original sql can be used, too.
.createNativeQuery("select {a.*},{b.*} from A as a left join B as b on a.ID = b.a ")
.addEntity("a", A.class)
.addJoin("b", "a.bs")
.setResultTransformer(DistinctResultTransformer.INSTANCE)
.list();
return res;
}
// https://stackoverflow.com/q/12071014/4506703
static class DistinctResultTransformer extends BasicTransformerAdapter {
private static final long serialVersionUID = 1L;
static final DistinctResultTransformer INSTANCE = new DistinctResultTransformer();
#Override
public List transformList(final List collection) {
final List<Object> res = new ArrayList<>();
for (final Object[] obj : (List<Object[]>) collection) {
if (!res.contains(obj[0])) {
res.add(obj[0]);
}
}
return res;
}
}
}
Above code executes 1 query:
select a.id as id1_0_0_, a.name as name2_0_0_,b.a as a3_1_0__, b.id as id1_1_0__, b.id as id1_1_1_, b.a as a3_1_1_, b.name as name2_1_1_
from A as a left join B as b on a.ID = b.a
full sample code
You can use some methods avoiding N+1 problem.
Using JPQL fetch, instead of native-query:
#Query("select distinct a from A a left join fetch a.bs")
List<A> getAllJpqlFetch();
Above code executes 1 query:
select distinct a0_.id as id1_0_0_, bs1_.id as id1_1_1_, a0_.name as name2_0_0_, bs1_.a as a3_1_1_, bs1_.name as name2_1_1_, bs1_.a as a3_1_0__, bs1_.id as id1_1_0__
from a a0_ left outer join b bs1_ on a0_.id=bs1_.a
diff
Using JPA Criteria fetch, is equivalent to above JPQL:
#Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<A> getAllCriteria() {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#criteria-from-fetch
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<A> criteria = builder.createQuery(A.class);
final Root<A> root = criteria.from(A.class);
root.fetch("bs", JoinType.LEFT);
criteria.select(root).distinct(true);
return entityManager.createQuery(criteria).getResultList();
}
Above code executes 1 query:
select distinct a0_.id as id1_0_0_, bs1_.id as id1_1_1_, a0_.name as name2_0_0_, bs1_.a as a3_1_1_, bs1_.name as name2_1_1_, bs1_.a as a3_1_0__, bs1_.id as id1_1_0__
from a a0_ left outer join b bs1_ on a0_.id=bs1_.a
diff
Using #Fetch(FetchMode.SUBSELECT):
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
// ...
#Entity
public class A {
#OneToMany(mappedBy = "a")
#Fetch(FetchMode.SUBSELECT)
private List<B> bs;
// ...
}
// findAll() method implementation is auto-generated by Spring Data JPA
// https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repositories.core-concepts
repository.findAll();
Above code executes 2 queries(root entities and their relational entities):
select a0_.id as id1_0_, a0_.name as name2_0_ from a a0_
select bs0_.a as a3_1_1_, bs0_.id as id1_1_1_, bs0_.id as id1_1_0_, bs0_.a as a3_1_0_, bs0_.name as name2_1_0_
from b bs0_ where bs0_.a in (select a0_.id from a a0_)
diff

I ended up using the following solution, given by DEWA Kazuyuki, above. I'm copying it here because DEWA suggested several answers and I thought it useful to identify the particular one that worked for me. Thanks, DEWA.
#Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<A> getAllCriteria() {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#criteria-from-fetch
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<A> criteria = builder.createQuery(A.class);
final Root<A> root = criteria.from(A.class);
root.fetch("bs", JoinType.LEFT);
criteria.select(root).distinct(true);
return entityManager.createQuery(criteria).getResultList();
}

Related

JPA CriteriaQuery Generated SQL with hardcoded parameter on nested entity id

I've been studying SpringData JPA and came across with this weird behavior when using CriteriaQuery to find a given entity by its child entity's Id, I've noticed a hardcoded child_id parameter in the generated SQL:
Hibernate: select parent0_.parent_id as parent_i1_4_, parent0_.parent_name as parent_n2_4_, parent0_.parent_type_parent_type_id as parent_t3_4_ from parent parent0_ inner join child children1_ on parent0_.parent_id=children1_.parent_parent_id where children1_.child_id=1
Hibernate: select parent0_.parent_id as parent_i1_4_, parent0_.parent_name as parent_n2_4_, parent0_.parent_type_parent_type_id as parent_t3_4_ from parent parent0_ inner join child children1_ on parent0_.parent_id=children1_.parent_parent_id where children1_.child_id=?
The java code:
Parent
#Data
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer parentId;
private String parentName;
#OneToMany(mappedBy = "parent")
private List<Child> children;
}
Child
#Builder
#Data
#Entity
public class Child {
#Id
private Integer childId;
private String childName;
#ManyToOne
private Parent parent;
}
ParentDAO
#Repository
public class ParentDAO {
private EntityManager em;
public ParentDAO(EntityManager em) {
this.em = em;
}
public List<Parent> findByChildId(Integer childId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Parent> cq = cb.createQuery(Parent.class);
Root<Parent> root = cq.from(Parent.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.join("children").get("childId"), childId));
cq.where(predicates.toArray(new Predicate[0]));
return em.createQuery(cq).getResultList();
}
public List<Parent> findByChild(Child child) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Parent> cq = cb.createQuery(Parent.class);
Root<Parent> root = cq.from(Parent.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.join("children"), child));
cq.where(predicates.toArray(new Predicate[0]));
return em.createQuery(cq).getResultList();
}
}
SpringDataApplication
#SpringBootApplication
public class SpringDataApplication implements CommandLineRunner {
private ParentDAO parentDAO;
public SpringDataApplication(ParentDAO parentDAO) {
this.parentDAO = parentDAO;
}
public static void main(String[] args) {
SpringApplication.run(SpringDataApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
parentDAO.findByChildId(1);
parentDAO.findByChild(Child.builder().childId(1).build());
}
}
It's not a big deal since the goal could be achieved with the findByChild method, I'm just curious about this situation. Best regards!
Because Strings can contain SQL and Integers cannot, there is no need to bind from a security aspect point of view (SQL injection).
From hibernate documentation of literal_handling_mode:
This enum defines how literals are handled by JPA Criteria. By default (AUTO), Criteria queries uses bind parameters for any literal that is not a numeric value. However, to increase the likelihood of JDBC statement caching, you might want to use bind parameters for numeric values too. The BIND mode will use bind variables for any literal value. The INLINE mode will inline literal values as-is. To prevent SQL injection, never use INLINE with String variables. Always use constants with the INLINE mode.
In issue HHH-9576 a new parameter was added to fix this issue, applicable since version 5.2.12
<property name="hibernate.criteria.literal_handling_mode" value="bind"/>
or in spring boot application.properties you can use
spring.jpa.properties.hibernate.criteria.literal_handling_mode=bind
After adding configuration your both query will look the same with the bind parameter.
select parent0_.parent_id as parent_i1_2_, parent0_.parent_name as parent_n2_2_ from parent parent0_ inner join child children1_ on parent0_.parent_id=children1_.parent_parent_id where children1_.child_id=?
binding parameter [1] as [INTEGER] - [1]
select parent0_.parent_id as parent_i1_2_, parent0_.parent_name as parent_n2_2_ from parent parent0_ inner join child children1_ on parent0_.parent_id=children1_.parent_parent_id where children1_.child_id=?
binding parameter [1] as [INTEGER] - [1]

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

Order by subclass-property in JPA InheritanceType.JOINED Tables

I'm using JPA with EclipseLink and a InheritanceType.JOINED Structure:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="PARENT")
#DiscriminatorColumn(name="TYPE")
public abstract class Parent { ... }
#Entity
#Table(name="CHILD_A")
#DiscriminatorValue("A")
public class ChildA extends Parent { ... }
#Entity
#Table(name="CHILD_B")
#DiscriminatorValue("B")
public class ChildB extends Parent { ... }
If I use Criteria-API to query data of the type Parent.class, the resulting SQL Statment looks like
SELECT t0.ID, ..., t1.ID, ..., t2.ID FROM
PARENT t0
LEFT OUTER JOIN CHILD_A t1 ON (t1.ID = t0.ID)
LEFT OUTER JOIN CHILD_B t2 ON (t2.ID = t0.ID)
WHERE ...
ORDER BY t0.PROPERTY ASC
Maybe it is a silly question, but is there any possibility to use a joined property (from CHILD_A or CHILD_B) in the ORDER clause via Criteria-API?
I did the following:
CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();
CriteriaQuery<Parent> q = cb.createQuery(Parent.class);
Root<Parent> c = q.from(Parent.class);
// FIXME: Don't know how to add orderBy of subclass property
TypedQuery<Parent> query = em.createQuery(q);
return query.getResultList();
Can anyone tell me, if this is the right approach to do the ordering and how it is done?

Spring Data 'Left Join Fetch' query returning null

My query looks like this:
#Query("SELECT p FROM Pilot p LEFT JOIN FETCH p.playerShips WHERE p.nickname = (:nickname)")
So far so good. I'm getting Pilot instance even when playerShips is empty.
Now, I want to fetch only inactive ships so I modified my query to look like this:
#Query("SELECT p FROM Pilot p LEFT JOIN FETCH p.playerShips s WHERE p.nickname = (:nickname) AND s.active = false")
and I'm getting null as a pilot so it clearly doesn't work. I'd be glad if someone could explain me how to create a JOIN FETCH query with WHERE clause that applies to the child elements. Thanks in advance.
Just move it to the join condition:
#Query("SELECT p FROM Pilot p LEFT JOIN FETCH p.playerShips s ON s.active = false WHERE p.nickname = (:nickname)")
The ON clause is defined in the JPA 2.1
After a lot of research, I decided to implement a custom repository so that I can use Hibernate Filters and now it works.
PilotRepository:
#Repository
public interface PilotRepository extends JpaRepository<Pilot, Long>, PilotRepositoryCustom{
/*spring data methods here*/
}
PilotRepositoryCustom:
public interface PilotRepositoryCustom {
public Pilot findByNicknameFetchInactiveShips(String nickname);
}
PilotRepositoryImpl:
public class PilotRepositoryImpl implements PilotRepositoryCustom{
#PersistenceContext
EntityManager entityManager;
#Override
public Pilot findByNicknameFetchInactiveShips(String nickname) {
Session session = entityManager.unwrap(Session.class);
session.enableFilter("active").setParameter("active", false);
Query query = entityManager.createQuery(
"SELECT p FROM Pilot p " +
"LEFT JOIN FETCH p.playerShips ps " +
"LEFT JOIN FETCH ps.ship s " +
"WHERE p.nickname = (:nickname)");
query.setParameter("nickname", nickname);
return (Pilot)query.getSingleResult();
}
}
That's it about spring data. Now the Filters configuration:
Pilot Entity:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pilot")
#Filter(name="active", condition = "active = :active")
private List<PlayerShip> playerShips;
PlayerShip Entity:
#Entity
#Table(name="player_ship")
#FilterDef(name="active", parameters=#ParamDef(name="active",type="java.lang.Boolean"))
public class PlayerShip {
/*...*/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pilot_id")
private Pilot pilot;
/*...*/
}
IMPORTANT :
If you use Boolean in #ParamDef, make sure to type java.lang.Boolean instead of just Boolean. I've spent more than one hour wondering why I keep getting parameter not found/not defined error.

JPA Native Query select and cast object

I have got an Object Admin which extends User. By default both Objects are in the table User_ of my Derby Database (included fields from Admin). Normally I'd select an User like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root user= query.from(User.class);
Predicate predicateId = cb.equal(category.get("id"), id);
query.select(user).where(predicateId);
return em.createQuery(query).getSingleResult();
However due to the complexity of my query I'm using a native query like this:
Query query = em.createNativeQuery("SELECT USER.* FROM USER_ AS USER WHERE ID = ?");
query.setParameter(1, id);
return (User) query.getSingleResult();
Though this throws a cast exception. I figure this is due to any fields from Admin.
My question is, how can I select a User using a native query with an equal result as the first example (including the same values for #LOB and #ManyToOne (et cetera) as the JPQL query would return)?
You might want to try one of the following ways:
Using the method createNativeQuery(sqlString, resultClass)
Native queries can also be defined dynamically using the EntityManager.createNativeQuery() API.
String sql = "SELECT USER.* FROM USER_ AS USER WHERE ID = ?";
Query query = em.createNativeQuery(sql, User.class);
query.setParameter(1, id);
User user = (User) query.getSingleResult();
Using the annotation #NamedNativeQuery
Native queries are defined through the #NamedNativeQuery and #NamedNativeQueries
annotations, or <named-native-query> XML element.
#NamedNativeQuery(
name="complexQuery",
query="SELECT USER.* FROM USER_ AS USER WHERE ID = ?",
resultClass=User.class
)
public class User { ... }
Query query = em.createNamedQuery("complexQuery", User.class);
query.setParameter(1, id);
User user = (User) query.getSingleResult();
You can read more in the excellent open book Java Persistence (available in PDF).
───────
NOTE: With regard to use of getSingleResult(), see Why you should never use getSingleResult() in JPA.
The accepted answer is incorrect.
createNativeQuery will always return a Query:
public Query createNativeQuery(String sqlString, Class resultClass);
Calling getResultList on a Query returns List:
List getResultList()
When assigning (or casting) to List<MyEntity>, an unchecked assignment warning is produced.
Whereas, createQuery will return a TypedQuery:
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass);
Calling getResultList on a TypedQuery returns List<X>.
List<X> getResultList();
This is properly typed and will not give a warning.
With createNativeQuery, using ObjectMapper seems to be the only way to get rid of the warning. Personally, I choose to suppress the warning, as I see this as a deficiency in the library and not something I should have to worry about.
When your native query is based on joins, in that case you can get the result as list of objects and process it.
one simple example.
#Autowired
EntityManager em;
String nativeQuery = "select name,age from users where id=?";
Query query = em.createNativeQuery(nativeQuery);
query.setParameter(1,id);
List<Object[]> list = query.getResultList();
for(Object[] q1 : list){
String name = q1[0].toString();
//..
//do something more on
}
Please refer JPA : How to convert a native query result set to POJO class collection
For Postgres 9.4,
List<String> list = em.createNativeQuery("select cast(row_to_json(u) as text) from myschema.USER_ u WHERE ID = ?")
.setParameter(1, id).getResultList();
User map = new ObjectMapper().readValue(list.get(0), User.class);
The best solution I found is using Interface projection .
At the beginning, I created a DTO class but it just didn't work, replacing the class with an interface like this works great:
#Query(value = "SELECT vat as vatRate, SUM(...) as amount from ...", nativeQuery = true)
List<VatReportLine> getSalesVats();
public interface VatReportLine {
double getVatRate();
long getAmount();
}
First of all create a model POJO
import javax.persistence.*;
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
//getter,setter
}
Controller
import com.example.demo.models.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import java.util.List;
#RestController
public class HomeController {
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/")
public List<StdUser> actionIndex() {
EntityManager em = emf.createEntityManager(); // Without parameter
List<StdUser> arr_cust = (List<StdUser>)em
.createQuery("SELECT c FROM StdUser c")
.getResultList();
return arr_cust;
}
#GetMapping("/paramter")
public List actionJoin() {
int id = 3;
String userName = "Suresh Shrestha";
EntityManager em = emf.createEntityManager(); // With parameter
List arr_cust = em
.createQuery("SELECT c FROM StdUser c WHERE c.classId = :Id ANd c.userName = :UserName")
.setParameter("Id",id)
.setParameter("UserName",userName)
.getResultList();
return arr_cust;
}
}

Categories

Resources