#NamedNativeQuery #SqlResultSetMapping wrong mapping COUNT(*) - java

I'm having a strange error using #SqlResultSetMapping and #NamedNativeQuery.
My Entity has:
#NamedNativeQuery(
name = "example_query",
query = "SELECT table_A.id, " +
"COUNT(*) AS numberOfA " +
"FROM table_A table_A " +
" JOIN example example ON example.id = :idExample " +
"GROUP BY id " +
"ORDER BY numberOfA DESC;",
resultSetMapping = "myMapping"
)
#SqlResultSetMapping(
name = "myMapping",
classes = #ConstructorResult(
targetClass = ExampleDTO.class,
columns = {
#ColumnResult(name = "id", type = Integer.class),
#ColumnResult(name = "numberOfA", type = Integer.class)
}
)
)
public class Entity implements Serializable { /////// }
My DTO is like:
public class ExampleDTO {
private Integer id;
private Integer numberOfA;
}
My Repository:
public interface Entity extends JpaRepository<Entity,Integer> {
#Query(name = "example_query", nativeQuery = true)
List<ExampleDTO> findExampleQuery(#Param("id") Integer id);
}
On the database the same query return:
id | numberofa
1 11
2 5
and the mapping return an object with:
id | numberofa
1 154
2 70
How is that possible?

Related

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

Filtering the Products of my Caddie with JPQL

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.

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)");

Problems with scalar values in native hibernate/jpa query using annotations

I'm trying to run a simple SQL query containig a group by clause.
HistoryEntity:
#Entity
#NamedNativeQueries(value = {
#NamedNativeQuery(name = HistoryEntity.FIND_ALL_BY_REFERENCE,
query = "SELECT h.id, h.reference, h.lrn "
+ "FROM dataHistory h "
+ "WHERE h.reference = :referenceNumber OR h.lrn = :referenceNumber",
resultSetMapping = HistoryEntity.FIND_ALL_BY_REFERENCE_MAPPING),
#NamedNativeQuery(name = HistoryEntity.FIND_REFERENCE_BY_LRN,
query = "SELECT h.reference as reference "
+ "FROM dataHistory h "
+ "WHERE h.lrn = :lrn "
+ "GROUP BY h.reference",
resultSetMapping = "resultMapping")
})
#SqlResultSetMappings(value = {
#SqlResultSetMapping(name = HistoryEntity.FIND_ALL_BY_REFERENCE_MAPPING, entities = {
#EntityResult(entityClass = HistoryEntity.class, fields = {
#FieldResult(name = "id", column = "id"),
#FieldResult(name = "reference", column = "reference"),
#FieldResult(name = "lrn", column = "lrn")
})
}),
#SqlResultSetMapping(name = "resultMapping", columns = {
#ColumnResult(name = "reference")
})
})
public class HistoryEntity implements Serializable {
/**
* #param referenceNumber Referenz (LRN oder BRN)
* #param brnList Liste von BRNs
*/
public static final String FIND_ALL_BY_REFERENCE = "HistoryEntity.findAllByReference";
public static final String FIND_ALL_BY_REFERENCE_MAPPING = "HistoryEntity.findAllByReferenceMapping";
private Integer id;
private String reference;
private String lrn;
public HistoryEntity() {}
#Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference= reference;
}
public String getLrn() {
return lrn;
}
public void setLrn(String lrn) {
this.lrn = lrn;
}
In the service class, I execute the query as follow:
Query query = entityManager.createNamedQuery("myQuery");
query.setParameter("lrn", lrn);
List resultList = query.getResultList();
Depending on the result of the query, the list contains java.lang.Character's:
Case 1:
SQL-Result (if I'm running the sql in a sql client):
| Reference |
| 123456780678MMM |
| 123456781678MMM |
| 123456782678MMM |
| 123456783678MMM |
Java-List-Result (in Java Debug View):
[1, 1, 1, 1]
Case 2:
SQL-Result:
| Reference |
| 123456780678MMM |
Java-List-Result:
[1]
I'm looking for a way to run a simple sql query with scalar values (using hibernate/jpa) to get as result a list with the values of the result.
Is there any way to do this without using the criteria api?
If you need more information, let me know!
Many thanks in advance for your help.
Marco
The Problem is a bug in hibernate, that casts the result to java.lang.Character insteat of java.lang.String. Only the first character of each row was returned in the result.
There is another question with a more detailed description of the solution:
Hibernate native query - char(3) column
If I use the cast-function in my query as follow, it works:
#NamedNativeQuery(name = HistoryEntity.FIND_REFERENCE_BY_LRN,
query = "SELECT cast(h.reference as VARCHAR(27)) as reference "
+ "FROM dataHistory h "
+ "WHERE h.lrn = :lrn "
+ "GROUP BY h.reference",
resultSetMapping = "resultMapping")
PS: I found the referenced question to late. Sorry for that!

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