Hibernate JoinColumn annotation not restricting on named query where clause - java

I am playing around with hibernate and I have downloaded the following test DB https://dev.mysql.com/doc/employee/en/sakila-structure.html
I have a named query on the employee class of:
#NamedNativeQuery(
name="complexQuery",
query="select * from employees inner join salaries on employees.emp_no=salaries.emp_no where salaries.from_date < 19870101 " +
"AND employees.emp_no = 10064;",
resultClass=Employee.class
)
I have mapped the employees to salaries via:
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER
)
#JoinColumn(name = "emp_no", nullable = false, insertable=false, updatable=false)
private Set<Salary> salaries = new HashSet<>();
I expected that the following hibernate queries would include the where statement of
salaries.from_date < 19870101
however I noticed that actually the hibernate query for the salaries selects all rows for that employee id:
Hibernate:
select
*
from
employees
inner join
salaries
on employees.emp_no=salaries.emp_no
where
salaries.from_date < 19870101
AND employees.emp_no = 10064;
Hibernate:
select
salaries0_.emp_no as emp_no1_4_0_,
salaries0_.from_date as from_dat2_4_0_,
salaries0_.emp_no as emp_no1_4_1_,
salaries0_.from_date as from_dat2_4_1_,
salaries0_.salary as salary3_4_1_,
salaries0_.to_date as to_date4_4_1_
from
salaries salaries0_
where
salaries0_.emp_no=?
Is there anyway to have the auto generated salaries query also include
where
salaries0_.from_date < ?
AND salaries0_.emp_no=?
EDIT: I am also getting the issue for a named query:
#NamedQuery(
name="complexQuery",
query="select e " +
"from Employee e, Salary s " +
"where e.id = 10064 " +
"AND s.id.fromDate < 19900101" +
"AND s.id.empNo = 10064"
)

You are not joining Employee and Salary!
That's the correct statement
#NamedQuery(
name="complexQuery",
query="select e " +
"from Employee e join e.salaries s " +
"where e.id = 10064 " +
"AND s.id.fromDate < 19900101"
)

Related

Hibernate Native Query not populating object

I have the following bean:
#Entity(name = "Contact" )
public class Contact implements Serializable {
#OneToOne(cascade = CascadeType.ALL, targetEntity = Members.class)
#JoinColumn(name = "member_id")
private Members member;
private Employee employee;
}
The following native query is populating the Contact member variables, except for the Employee. I think Members is populated because it is a foreign key, but Employee is not. The reason I am using a native query, is to join to the employee table and populate the Employee object.
String query = "SELECT contact.*, employee.* "
+ "FROM contact "
+ "INNER JOIN members ON members.member_id = contact.member_id "
+ "INNER JOIN employee ON employee.member_id = contact.member_id "
+ "WHERE contact.email = :email";
Query q = em.createNativeQuery(query, Contact.class);
q.setParameter("email", email);
contacts = (List<Contact>)q.getResultList();
Question
How can I get the native query to populate the Employee?
Note:
All relationships are one-to-one between contact, member and employee.
Tables
**Contact**
contact_id
member_id
**Member**
member_id
**Employee**
employee_id
member_id

How to add condition for List type in TypedQuery

I have entity with following attributes:
#Table(name = "MY_TABLE")
public class MyTable {
......
#OneToMany(mappedBy = "mytable", cascade = ALL, orphanRemoval = true)
private List<MyAnotherTable> otherTableValues = new ArrayList<MyAnotherTable>();
......
}
Now I am trying to write HSQL as
TypedQuery<Share> q =
getEntityManager().createQuery("SELECT MyTable FROM MyTable AS mytable WHERE " +
"mytable.someField=:firstParam AND mytable.secondField IS NOT NULL AND " +
// "AND mytable.otherTableValues"
, Share.class);
q.setParameter(firstParam, firstVal);
return q.getResultList();
So not sure how to put consition on otherTableValues as it is list type. Any suggestion?
You have to join the collection:
SELECT DISTINCT mytable
FROM MyTable AS mytable join mytable.otherTableValues AS otv
WHERE mytable.someField = :firstParam
AND mytable.secondField IS NOT NULL
AND otv.someOtherField = :someOtherField

Retrieving object children while using Transformers.aliasToBean

I've got a question regarding children retrieval when using Transformers.aliasToBean function in Hibernate. I've wrote a query which populates my object, but when I try to populate children query doesn't work. I'll post sample code first and add generated SQL and exception below.
Parent
public class Parent {
#Id
#Column(name="id")
#GeneratedValue
private long id;
#Column(name="name")
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="parent_id")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Child> children;
#Transient
private long someCount;
// public constructor + getters and setters
}
Child
public class Child {
#Id
#Column(name="id")
#GeneratedValue
private long id;
// public constructor + getters and setters
}
Query
Query query = session.createQuery("SELECT p.id as id, p.name as name, "
+ " (SELECT COUNT(*) FROM stat WHERE parent_id=p.id) as someCount, "
+ " FROM " + Parent.class.getSimpleName() + " p ")
.setResultTransformer(Transformers.aliasToBean(Parent.class));
Basically when I try to include p.children as children right in the first line of query I get the following sql generated by hibernate:
select parent0_.id as col_0_0_, parent0_.name as col_1_0_, . as col_2_0_,
(select count(*) from stats stat3_ where parent_id=parent0_.id) as col_3_0_, child2_.id
as id1_2_1_ from inner join child child2_ on parent0_.id=child2_.parent_id
And the error is:
WARN: SQL Error: 0, SQLState: 42601
ERROR: ERROR: syntax error at or near "." at position 60
That position 60 corresponds to , . as col_2_0_, the position before the dot in the first line of the query. Basically it seems to retrieve the children, but generated SQL prevents it from successfully executing the query.
Any help appreciated!
Simply speaking, you can not add p.children to result columns of SELECT, because it's a collection, and only entities or scalars are allowed here. But you can try this alternative:
Query query = session.createQuery("SELECT p, "
+ " (SELECT COUNT(*) FROM stat WHERE parent_id=p.id) "
+ " FROM " + Parent.class.getSimpleName() + " p " +
+ " LEFT JOIN FETCH p.children ");
for (Object[] row : (List<Object[]>)query.list()) {
Parent p = (Parent)row[0];
p.setSomeCount(((Number)row[1]).longValue());
}
Please tell if it really works for you.

Query update using hibernate runs into QueryException : Unable to resolve path

I have following entities, and need to update a field that is in a specific field and its removedDate is null. But the following code returns exception.
#Entity
public class Cart implements Serializable {
#Id
#GeneratedValue
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
private List<CartItem> items;
public Cart() {
}
getters and setters
}
#Entity
public class CartItem {
#Id
#GeneratedValue
private long id;
#ManyToOne
private Product pro;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
private Date addedDate;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
private Date removedDate;
getters and setters
}
Hibernate Code 1
Query query = session.createQuery("UPDATE CartItem SET removedDate = :currentDateTime "
+ " WHERE CartItem.id IN (Select Cart.items.id From Cart"
+ " WHERE Cart.id = :cartId"
+ " AND Cart.items.pro.id = :pro"
+ " AND Cart.items.removedDate is null)");
query.setParameter("currentDateTime", dt.getCurrentDateTime());
query.setParameter("cartId", cartId);
query.setParameter("pro", proId);
int result = query.executeUpdate();
Exception of Code 1
SEVERE: org.hibernate.QueryException: Unable to resolve path [CartItem.id], unexpected
token [CartItem] [UPDATE com.myproject.CartItem SET removedDate =
:currentDateTime WHERE CartItem.id IN (Select Cart.items.id From
com.myproject.Cart WHERE Cart.id = :cartId AND cart.items.pro.id = :proId
AND Cart.items.removedDate is null))]
at org.hibernate.hql.internal.ast.tree.IdentNode.resolveAsNakedComponentPropertyRefLHS(IdentNode.java:245)
at org.hibernate.hql.internal.ast.tree.IdentNode.resolve(IdentNode.java:110)
at org.hibernate.hql.internal.ast.tree.DotNode.resolveFirstChild(DotNode.java:177)
at org.hibernate.hql.internal.ast.HqlSqlWalker.lookupProperty(HqlSqlWalker.java:577)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.addrExpr(HqlSqlBaseWalker.java:4719)
Hibernate Code 2
Query query = session.createQuery("UPDATE CartItem SET removedDate = :currentDateTime "
+ " WHERE id IN (Select items.id From Cart"
+ " WHERE id = :CartId"
+ " AND items.pro.id = :pro"
+ " AND items.removedDate is null)");
Exception of Code 2
SEVERE: org.hibernate.QueryException: illegal attempt to dereference collection
[{synthetic-alias}{non-qualified-property-ref}items] with element property
reference [id] [UPDATE com.myproject.CartItem SET removedDate =
:currentDateTime WHERE id IN (Select items.id From com.myproject.Cart WHERE
id = :cartId AND items.pro.id = :pro AND items.removedDate is null)]
at org.hibernate.hql.internal.ast.tree.DotNode$1.buildIllegalCollectionDereferenceException(DotNode.java:68)
at org.hibernate.hql.internal.ast.tree.DotNode.checkLhsIsNotCollection(DotNode.java:550)
at org.hibernate.hql.internal.ast.tree.DotNode.resolve(DotNode.java:246)
at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:118)
at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:114)
Why don't you make your association bidirectional?
Add this to your CartItem entity:
#ManyToOne
private Cart cart;
Set the mappedBy on your cartItem fied in Cart:
#OneToMany(cascade = CascadeType.ALL, mappedBy="cart")
#LazyCollection(LazyCollectionOption.FALSE)
private List<CartItem> items;
The resulting HQL would be much simpler (and should work):
"UPDATE CartItem c SET c.removedDate = :currentDateTime "
+ " WHERE c.cart.id = :cartId"
+ " AND c.pro.id = :pro"
+ " AND c.removedDate is null"
Try this by adding an alias in the inner select query.
Query query = session.createQuery("UPDATE CartItem SET removedDate = :currentDateTime "
+ " WHERE id IN (Select cart.items.id From Cart cart"
+ " WHERE cart.id = :CartId"
+ " AND cart.items.pro.id = :pro"
+ " AND cart.items.removedDate is null)");
EDIT 2
I did a bit of reading and found out that the object.collection.id works only for a 1:1 or an N:1 relation, not for a 1:N relation, which is what you have. Try this.
SELECT items.id
FROM Cart cart
LEFT JOIN cart.items items
WHERE cart.id = :CartId AND items.pro.id = :pro AND items.removedDate is null
Here is more info, info, info
Thanks to Hrishikesh's comment, I found the answer by providing the exact SQLQuery.
UPDATE cartItem SET removedDate = :currentDateTime"
+ " WHERE pro = :pro"
+ " AND removedDate IS NULL"
+ " AND id IN
( SELECT items_id from Cart_CartItem WHERE Cart_id = :CartId)
Try that:
Query query = session.createQuery("UPDATE CartItem SET removedDate = :currentDateTime "
+ " WHERE id IN (Select ci.id From Cart c inner join c.items ci"
+ " WHERE c.id = :cartId"
+ " AND ci.pro.id = :pro"
+ " AND ci.removedDate is null)");

Query with JOIN FETCH performance problem

I have problem with hibernate query performance which I can't figure out. In code snippet below I need select entities with at least one mapping and filtered mapping. I'm using FETCH JOIN for this to load only filtered mappings.
But in that case I have performance problems with query. Hibernate says warning log :
org.hibernate.hql.ast.QueryTranslatorImpl
- firstResult/maxResults specified with collection fetch; applying in
memory!
When I omit FETCH JOIN and left only JOIN query is nice fast. But in result I have all mappings loaded to entity which is not acceptable state for me. Is there a way to boost query performance? There are a lot rows in mapping table.
HQL query :
select distinct e from Entity
join fetch e.mappings as mapping
where e.deleted = 0 and e.mappings is not empty
and e = mapping.e and mapping.approval in (:approvals)
Entities :
#Entity
#Table(name="entity")
class Entity {
...
#OneToMany(mappedBy="entity", cascade=CascadeType.REMOVE, fetch=FetchType.LAZY)
#OrderBy("created")
private List<Mapping> mappings = new ArrayList<Mapping>();
...
}
#Entity
#Table(name="mapping")
class Mapping {
public static enum MappingApproval {
WAITING, // mapping is waiting for approval
APPROVED, // mapping was approved
DECLINED; // mapping was declined
}
...
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="entity_id", nullable=false)
private Entity entity;
#Enumerated(EnumType.STRING)
#Column(name="approval", length=20)
private MappingApproval approval;
...
}
Thanks
From the JPA-Specifications
The effect of applying setMaxResults or setFirstResult to a query
involving fetch joins over collections is undefined. (JPA "Enterprise
JavaBeans 3.0, Final Release", Kapitel 3.6.1 Query Interface)
Hibernate does the right thing, but executes a part of the query in memory, which is tremendously slower. In my case the difference is between 3-5 ms to 400-500 ms.
My solution was to implement the paging within the query itself. Works fast with the JOIN FETCH.
If you need a firstResult/maxResults with "fetch" you can split your query in 2 queries:
Query your entity ids with firstResult/maxResults but without the "fetch" on sub-tables:
select entity.id from entity (without fetch) where .... (with firstResult/maxResults)
Query your entities with the "fetch" on the ids returned by your first query:
select entity from entity fetch ... where id in <previous ids>
The reason is slow is because Hibernate executes the SQL query with no pagination at all and the restriction is done in memory.
However, if the join has to scan and fetch 100k records, while you are interested in just 100 results, then 99.9% of the work being done by the Extractor and all the I/O done over networking is just waste.
You can easily turn a JPQL query that uses both JOIN FETCH and pagination:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
where p.title like :title
order by p.id
""", Post.class)
.setParameter("title", titlePattern)
.setMaxResults(maxResults)
.getResultList();
into an SQL query that limits the result using DENSE_RANK by the parent identifier:
#NamedNativeQuery(
name = "PostWithCommentByRank",
query =
"SELECT * " +
"FROM ( " +
" SELECT *, dense_rank() OVER (ORDER BY \"p.created_on\", \"p.id\") rank " +
" FROM ( " +
" SELECT p.id AS \"p.id\", " +
" p.created_on AS \"p.created_on\", " +
" p.title AS \"p.title\", " +
" pc.id as \"pc.id\", " +
" pc.created_on AS \"pc.created_on\", " +
" pc.review AS \"pc.review\", " +
" pc.post_id AS \"pc.post_id\" " +
" FROM post p " +
" LEFT JOIN post_comment pc ON p.id = pc.post_id " +
" WHERE p.title LIKE :titlePattern " +
" ORDER BY p.created_on " +
" ) p_pc " +
") p_pc_r " +
"WHERE p_pc_r.rank <= :rank ",
resultSetMapping = "PostWithCommentByRankMapping"
)
#SqlResultSetMapping(
name = "PostWithCommentByRankMapping",
entities = {
#EntityResult(
entityClass = Post.class,
fields = {
#FieldResult(name = "id", column = "p.id"),
#FieldResult(name = "createdOn", column = "p.created_on"),
#FieldResult(name = "title", column = "p.title"),
}
),
#EntityResult(
entityClass = PostComment.class,
fields = {
#FieldResult(name = "id", column = "pc.id"),
#FieldResult(name = "createdOn", column = "pc.created_on"),
#FieldResult(name = "review", column = "pc.review"),
#FieldResult(name = "post", column = "pc.post_id"),
}
)
}
)
The query can be executed like this:
List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setParameter(
"rank",
5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
new DistinctPostResultTransformer(entityManager)
)
.getResultList();
To transform the tabular result set back into an entity graph, you need a ResultTransformer which looks as follows:
public class DistinctPostResultTransformer
extends BasicTransformerAdapter {
private final EntityManager entityManager;
public DistinctPostResultTransformer(
EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public List transformList(
List list) {
Map<Serializable, Identifiable> identifiableMap =
new LinkedHashMap<>(list.size());
for (Object entityArray : list) {
if (Object[].class.isAssignableFrom(entityArray.getClass())) {
Post post = null;
PostComment comment = null;
Object[] tuples = (Object[]) entityArray;
for (Object tuple : tuples) {
if(tuple instanceof Identifiable) {
entityManager.detach(tuple);
if (tuple instanceof Post) {
post = (Post) tuple;
}
else if (tuple instanceof PostComment) {
comment = (PostComment) tuple;
}
else {
throw new UnsupportedOperationException(
"Tuple " + tuple.getClass() + " is not supported!"
);
}
}
}
if (post != null) {
if (!identifiableMap.containsKey(post.getId())) {
identifiableMap.put(post.getId(), post);
post.setComments(new ArrayList<>());
}
if (comment != null) {
post.addComment(comment);
}
}
}
}
return new ArrayList<>(identifiableMap.values());
}
}
That's it!
after increasing memory for JVM things goes much better. After all I end with not using FETCH in queries.

Categories