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.
Related
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"
)
I am running a real life scenario in our database and thus I have no room to alter its structure in case that this comes around as a suggestion. Here is the Structure in question:
EntityA {
#Id
.......
#OneToMany(mappedBy = "xxxx", fetch = FetchType.LAZY)
private Set<EntityB> entityB;
#Column(name = "partition", columnDefinition = "nchar", length = 3)
private String partitionKey;
}
EntityB {
#Id
..........
#Column(name = "partition")
private String partitionKey;
#ManyToOne(fetch = FetchType.LAZY) //EAGER is the default for the to-one
#JoinColumn(name = "bbbb")
private EntityA entityA;
#OneToMany(mappedBy = "EntityCPk.entityB", fetch = FetchType.LAZY)
private Set<EntityC> entityC;
#OneToOne(mappedBy = "cccc", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private EntityD entityD;
}
EntityC {
#EmbeddedId
private EntityCPk entityCPk;
#Embeddable
public static class EntityCPk implements Serializable {
#Column( name = "partition")
private String partitionKey;
#ManyToOne
#JoinColumn(name = "xxxx")
private EntityB entityB;
#ManyToOne
#JoinColumn(name = "xxxx")
private EntityE entityE;
}
}
EntityD {
#id
.........
#MapsId
#OneToOne
#JoinColumn(name = "zzzz", columnDefinition = "nchar")
#PrimaryKeyJoinColumn
private EntityB entityB;
}
EntityE {
#id
.........
#OneToMany(mappedBy = "yyyPk.entityE")
private Set<EntityC> entityC;
}
Now the requirement is to run a query in one go with joins and avoid 1+N scenarios. I assume as far as I have seen that the .LAZY or EAGER annotations are "overwritten" when using the Query annotation within the repository along with the FETCH option. So here is what I have achieved so much (entityXXX and entityYYY do not interfear with our case so I just mention them):
First Attempt with FetchType.LAZY in EntityB.entityC property:
"select a" +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD latest " +
"where a.aProp in ( :props ) " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
Results, as expected. I get 1 query BUT I get no collection returned in EntityB.entityC due to the lazy annotation and of course it is not present in the query. If I change the the EntityB.entityC to FetchType.EAGER then I get as expected 3 queries. One is the main and N per entityC in Set. So I guess the next step is to join entityC:
Second Attempt:
"select a " +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD as latest " +
"join bs.entityC as cs " + //please note I am not using FETCH yet
"where a.aProp in ( :props ) " +
"and c.entityCPk.partitionKey = a.partitionKey " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
The result is unexpected and I think it has been reported here as well. What I get now is multiples of a(s) all references to the same object equals to the sum of the amount of bs.entityC. So if for example a-1 -> has 1-bs -> has 17 cs and a2 -> has 1-bs -> has 67 cs then I end up with a result set of 84 a objects all the same! This is question one. Why is this happening?
Question 2 is that if I use the FETCH in my new join then I am still not getting my 1 query and now I am not getting exactly multiple instances of A but multiple instances of some kind of Wrappers with a handler property that has references to A makred as EntityA_$$_jvstbc0_4#.
Just to give some insight to the database structure, I am more than sure that this schema started as a many-to-many relationship with a lookup table being EntityB between EntityA and EntityC. I may try to tackle the issue using JoinTable on EntityC joining on partitionKey and id of EntityB while EntityA has the partitionkey and its Id to map on EntityB. However i am not very hopeful of this solution as EntityB has been contaminated with other columns over time which needs to be selected uppon and I am not sure how can I do this.
UPDATE 1: I can see that when join FETCH is used for cs it is augmenting the resultant SQL select with the columns that are necessary i.e. to populate the cs children. Running the query manually I am getting correctly the sum of children as rows. Which makes sense SQL wise but hibernate should have been able to aggregate the additional rows based on their properties. Right enough without the join FETCH I am getting only rows equals to the amount of a. So my second though is that somehow I need to instruct Hibernate to aggregate manually(?)
UPDATE 2: Change of strategy. Instead of starting following an SQL logic, we better have to answer to the following question: Which Class/Entity will give us the granularity we are looking for. In the previous examples we were starting from EntityA trying to limit its children to fit our expected results. However as it has been pointed, this is effectively a corruption of the objects. You cannot "limit" the children cause they all belong to the Entity and fetching a subset of them you run the risk of deleting(?) data. So the approach must be to get the children objects we are interested that point to the parent entities. That we don't alter the data. So here is a query that returns the correct amount of object. No distinct or inexplicable multiplicities:
"select c " +
"from EntityC c " +
"inner join c.EntityCPk.EntityB.EntityD latest " +
"join latest.EntityB.EntityXXX xxx " +
"join latest.EntityB.EntityYYY yyy " +
"join fetch c.EntityCPk.EntityB " +
"where latest.EntityB.EntityA.Id in ( :param ) " +
"and latest.EntityB.aField between :paramA and paramB "
So this seems to answer the issue of the multiplicity of the previous examples as every row is based on the "finer" child object that resolves its parent via the -ToOne relationship. Also there are no more dangerous aliases in the join fetch. There is only one more issue. It introduces a 1+N query problem for EntityB that I cannot get rid off.
I have this structure:
public enum SaleItemType {
CRUISE,
DAILY_HOSTING
}
public class Estimate {
...
private List<SaleItemType> interestedSaleItemTypes;
#Column(name = "sale_item_type")
#CollectionTable(name = "estimate_sale_item_type", joinColumns = #JoinColumn(name = "estimate_id"))
#ElementCollection(targetClass = SaleItemType.class)
#Enumerated(EnumType.STRING)
public List<SaleItemType> getInterestedSaleItemTypes() {
return interestedSaleItemTypes;
}
}
And i'm trying to do a simple query:
String q = "FROM " + Estimate.class.getSimpleName() + " e" + " WHERE e.interestedSaleItemTypes IN :a";
TypedQuery<Estimate> query1 = getEm().createQuery(q, Estimate.class);
query1.setParameter("a", EnumSet.of(SaleItemType.CRUISE));
query1.getResultList();
I'm getting this query(and error) on the log:
DEBUG SQL:92 - select estimate0_.id as id1_25_, estimate0_.average_ticket as average_2_25_, estimate0_.description as descript3_25_, estimate0_.end_date as end_date4_25_, estimate0_.pax_quantity as pax_quan5_25_, estimate0_.start_date as start_da6_25_ from estimate estimate0_ cross join estimate_sale_item_type interested1_ where estimate0_.id=interested1_.estimate_id and (. in (?))
DEBUG SqlExceptionHelper:124 - could not extract ResultSet [n/a]
org.postgresql.util.PSQLException: No value specified for parameter 1.
Why hibernate is doing this query?
Im using Hibernate 5.1 Final
The IN expression can be used to test if a value is in a collection but interestedSaleItemTypes is not a simple value but itself a collection. Therefore use MEMBER OF:
String q = "FROM Estimate e WHERE :a MEMBER OF e.interestedSaleItemTypes";
TypedQuery<Estimate> query1 = getEm().createQuery(q, Estimate.class);
query1.setParameter("a", SaleItemType.CRUISE);
Did you try to put parenthesis in your IN clause?
I don't know if it's required, but in all tutorials that I found, always had the parenthesis. http://www.postgresqltutorial.com/postgresql-in/
Also, as the IN clause is expecting a list of values you can use the setParameterList instead of setParameter.
Try this:
String q = "FROM " + Estimate.class.getSimpleName() + " e" + " WHERE e.interestedSaleItemTypes IN (:a)";
TypedQuery<Estimate> query1 = getEm().createQuery(q, Estimate.class);
query1.setParameterList("a", EnumSet.of(SaleItemType.CRUISE));
query1.getResultList();
My entity Caddiecontains a #OneToMany relation with its Product
#Entity
public class Caddie {
#Id
#GeneratedValue
int id;
#OneToMany
#OrderBy("price")
List<Product> products = new ArrayList<>();
}
#Entity
public class Product{
#Id
#GeneratedValue
int id;
#Column(nullable=false)
String name;
}
I want all products from my caddie that are more expensive than 2$.
I have tried many things including this one :
String query = " SELECT p FROM Product p "
+ "JOIN Caddie c "
+ "WHERE c.id = 1 "
+ "AND p MEMBER OF c.products AND p.price > :price";
Unfortunately, I have two Path there. Trying :
String query = " SELECT c.products FROM Caddie c "
+ "JOIN c.products prods "
+ "WHERE c.id = 1 AND prods.price > :price";
I don't have the objects I wanted.
I could create an Entity named CaddieProduct, but I would like something cleaner.
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)");