I have a JPA query written like this:
public interface MyDataRepository extends CrudRepository<MyData, String> {
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where c.date = :date")
List<MyData> getMyDataOfDate(#NotNull LocalDate date);
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where c.name = :name")
List<MyData> getMyDataOfName(#NotNull String name);
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where md.type = :type")
List<MyData> getMyDataOfType(#NotNull String type);
}
Class MyData and Child are defined as:
class MyData {
String id;
String type;
#ManyToOne
#JoinColumn(name = "CHILD_ID", referencedColumnName = "ID", nullable = false, updatable = false)
Child child;
}
class Child {
String id;
String name;
LocalDate date;
}
The problem is that whenever I call the getMyDataOfDate method or getMyDataOfName method, they always return ALL rows rather than the rows that matches the where condition, as if the where clause never exists.
However, the getMyDataOfType method works fine. The difference of this method is that the where condition is on a property of md, not c.
What did I do wrong?
JPA does not allow filtering on join fetches. Reasons being is that when you specify a join fetch, you are telling JPA to fetch the parent and all its children defined by that relationship in the managed entities it returns. If filtering were allowed, the list of children, the relationship in the parent, might not reflect what is actually in the database. Take the case of Parent with many children
"Select parents from Parent p fetch join p.children c where c.firstName = 'Bob'"
For such a query, when you get a list of parents and calling getChildren on them, do you expect to see all their children or a list that only contains children named Bob? If the later (which is the only way to do so), how should JPA handle changes to a parents children list, and know what to do with the not-fetched children?
This is why JPA doesn't allow filtering over fetch joins, and they restrict it across all relationships to be consistent. If you want the parents who have children with the firstName of 'Bob', it would look like:
"Select parents from Parent p join p.children c where c.firstName = 'Bob'"
Every parent returned will be a complete representation of its state in the database based on its mappings; so accessing parent.getChildren will return the current state of its children list and not something affected by the way it was fetched.
I'm using Quarkus and Hibernate / Panache.
For this example, I have 3 tables (table_a, table_b, table_c) that I am joining together using a native query. In the project I'm working on, it's around 5 JOIN tables to retrieve the information I'm looking for.
table_b is purely a mapping / join table for table_a and table_c:
SELECT
a.id,
a.name,
c.login_date
FROM
table_a a
JOIN table_b b ON b.a_id = a.id
JOIN table_c c ON b.c_id = c.id
WHERE
c.login_date > '01-MAY-21'
I'm porting the above to HQL. I've mapped all my #Entity classes with their respective #Table, along with their #Column names. We're good in that department.
SELECT
a.id,
a.name,
c.loginDate
FROM
TableA a
JOIN TableA b ON b.aId = a.id
JOIN TableB c ON b.cId = c.id
WHERE
c.loginDate > '01-MAY-21'
I'm only looking for name and login_date. There is a bunch of other information stored in table_a and table_c that I don't want for this specific query. So I created an entity for this call:
#Entity
#IdClass(LoginDetailsPk.class)
#NamedQuery(
name = "LoginDetails.findFromDate",
query = "FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
)
public class LoginDetails extends PanacheEntityBase {
#Id
private int id;
#Id
private String name;
#Id
private String loginDate;
public static List<LoginDetails> findFromDate(String fromDate) {
// Eventually pass fromDate into find()
return find("#LoginDetails.findFromDate").list();
}
}
I'm having a hard time trying to understand why the return even works. When I invoke LoginDetails.findFromDate(...) and store it in a List<LoginDetails>, it works fine. However, when I try to access the list, I get a ClassCastException error.
List<LoginDetails> details = LoginDetails.findFromDate(null);
for(LoginDetails detail : details) { // <------ Throws a class cast exception
//...
}
After debugging, I'm noticing that generic type stored in my List isn't even my LoginDetails class; rather, it's an array of objects (List<Object[]>) with all my #Entities and the irrelevant information I'm not looking for.
I'm lost. Would it make more sense to move back to a native query?
Your HQL is creating a Object[] for every row in the result, because you are not specifying any SELECT, and by default all the objects in the FROM clause are included in that Object array. If you want to return a LoginDetails object you need to create a constructor with all the attributes:
public LoginDetails(int id, String name, String loginDate) {
this.id = id;
this.name = name;
this.loginDate = loginDate;
}
And then change the query to:
query = "SELECT new LoginDetails(a.id, a.name, c.loginDate) "
"FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
See https://docs.jboss.org/hibernate/core/3.5/reference/en/html/queryhql.html#queryhql-select
I have these 3 independent table i.e Student, Teacher and Subject. Independent here refers that there is no relation in these tables.
I want the count of all these tables . SQL query looks like -
SELECT
(SELECT COUNT(*) FROM Student as ST,
(SELECT COUNT(*) FROM Teacher as TE,
(SELECT COUNT(*) FROM Subject as SU
Now I want to map this result into dto .
The DTO looks like
public class CountDto{
Integer student;
Integer teacher;
Integer subject;
}
The repository call looks like -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public CountDto getCount();
While calling this function I get following error stating
"message": "Failed to convert from type [java.lang.Object[]] to type [com.rbl.mdm.dto.CountDto ] for value '{16, 16 , 34}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Integer] to type [com.rbl.mdm.dto.CountDto]"
How should I convert my response to desired DTO ?
You can declare CountDto as a public interface and it should work. It's called a Projection in terms of Spring. Or you can use SqlResultSetMapping or ConstructorResult along with your class.
you don't have to implement it by any entity class, just create it like an independent interface even within the repository file:
public interface StudentRepository extends CrudRepository<Student, Long> {
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
Counts getCount();
public static interface Counts {
Integer getST();
Integer getTE();
Integer getSU();
}
}
So here is the answer to do -
The DTO will look like
public CountDto{
private Integer studentTotal;
private Integer teacherTota;
private Integer subjectTotal;
}
The repository call -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public Map<String,Integer> getCount();
Finally the serviceImpl--
public CountDto getCount{
CountDto CountValue = new CountDto();
Map<String,Integer> map = repository.getCount();
for (Map.Entry<String,Integer> entry : map.entrySet()) {
if(entry.getKey().equals("ST"))
CountValue.setStudentTotal( entry.getValue());
if(entry.getKey().equals("TE"))
CountValue.setTeacherTotal( entry.getValue());
if(entry.getKey().equals("SU"))
CountValue.setSubjectTotal( entry.getValue());
}
return CountValue ;
}
But the solution seems quite complex to me. Any simpler approach required.
In pure Hibernate/JPA usage this is a simple dynamic-instantiation query (what JPA calls a "constructor result"):
select new CountDTO(
(SELECT COUNT(*) FROM Student) as ST,
...
)
No idea here about Spring, though a word of warning.. in trying to be useful, it often "gets in the way". Not sure that is the case here... Have you tried straight Hibernate/JPA?
Hi what I am trying to achieve is to get SQL native query result map into my DTO in java spring jpa repository, how do I do this properly? I try several code, but it does not work, here is what I tried:
First try :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
once I execute the function, I got this error:
No converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap]
to type [com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto]
Second try :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT new com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto(stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir) "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
in second here is the error:
could not extract ResultSet; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not extract
ResultSet
below is my DTO:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class StockAkhirDto {
private Long productId;
private String productCode;
private Integer stockAkhir;
}
How should I correct my code? So, can I get the result into my DTO?
You can define the following named native query with appropriate sql result set mapping:
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.ConstructorResult;
import javax.persistence.ColumnResult;
#Entity
#NamedNativeQuery(
name = "find_stock_akhir_dto",
query =
"SELECT " +
" stock_akhir.product_id AS productId, " +
" stock_akhir.product_code AS productCode, " +
" SUM(stock_akhir.qty) as stockAkhir " +
"FROM book_stock stock_akhir " +
"where warehouse_code = :warehouseCode " +
" AND product_code IN :productCodes " +
"GROUP BY product_id, product_code, warehouse_id, warehouse_code",
resultSetMapping = "stock_akhir_dto"
)
#SqlResultSetMapping(
name = "stock_akhir_dto",
classes = #ConstructorResult(
targetClass = StockAkhirDto.class,
columns = {
#ColumnResult(name = "productId", type = Long.class),
#ColumnResult(name = "productCode", type = String.class),
#ColumnResult(name = "stockAkhir", type = Integer.class)
}
)
)
public class SomeEntity
{
}
and then use it:
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(name = "find_stock_akhir_dto", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(
#Param("warehouseCode") String warehouseCode,
#Param("productCodes") Set<String> productCode
);
}
i find a way which is not usual, but i find data type called "Tuple" when i try to use QueryDsl to solved this problem, but i won't recommend QueryDsl if you are just getting started just like me. Lets focus on how i do it with "Tuple"
i changed my return type to Tuple, here is how my repository looked like :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<Tuple> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
and then here in my service class, since it's returned as Tuple, i have to map the column one by one manually, here is my service function looked like :
public List<StockTotalResponseDto> findStocktotal() {
List<Tuple> stockTotalTuples = stockRepository.findStocktotal();
List<StockTotalResponseDto> stockTotalDto = stockTotalTuples.stream()
.map(t -> new StockTotalResponseDto(
t.get(0, String.class),
t.get(1, String.class),
t.get(2, BigInteger.class)
))
.collect(Collectors.toList());
return stockTotalDto;
}
the column field start with 0, in this way i can keep my query neat at Repository level. But i will accept SternK answer as the accepted answer because that way worked too, i will keep my answer here if someone need something like this
Create standard native #Query
#Query(value = "select id, age, name FROM Person WHERE age=?1", nativeQuery=true)
List<PersonView> getPersonsByAge(int age);
and an interface
public interface PersonView {
Long getId();
Integer getAge();
String getName();
}
columns are matched by order (not by names).
In this way, you have a native query, no entities and not too much boiler plate code (aka many annotations).
However, resulting views (Jdk proxy etc.) are very slow in access, i had some code doing some grouping over a stream, and it's 10x !! slower than with standard DTO/Pojos !
so at the end, I don't use nativeQuery anymore, but:
SELECT new com.my_project.myDTO(p.id, p.age, p.name) .....
The second variant is pretty close. You just have to remove the aliases for the constructor expression:
new com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto(
stock_akhir.product_id,
stock_akhir.product_code,
SUM(stock_akhir.qty)
)
should work.
Another valid option based on Sternk answer woul be as follows
You can define the following named native query with appropriate sql result set mapping:
resources/META-INF/orm.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
<named-native-query name="find_stock_akhir_dto" result-class="com.fullyqualified.name.SomeEntity"
result-set-mapping="stock_akhir_dto">
<query><![CDATA[
SELECT
stock_akhir.product_id AS productId,
stock_akhir.product_code AS productCode,
SUM(stock_akhir.qty) as stockAkhir
FROM book_stock stock_akhir
where warehouse_code = :warehouseCode
AND product_code IN :productCodes
GROUP BY product_id, product_code, warehouse_id, warehouse_code]]></query>
</named-native-query>
</entity-mappings>
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.ConstructorResult;
import javax.persistence.ColumnResult;
#Entity
#SqlResultSetMapping(
name = "stock_akhir_dto",
classes = #ConstructorResult(
targetClass = StockAkhirDto.class,
columns = {
#ColumnResult(name = "productId", type = Long.class),
#ColumnResult(name = "productCode", type = String.class),
#ColumnResult(name = "stockAkhir", type = Integer.class)
}
)
)
public class SomeEntity
{
}
and then use it:
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(name = "find_stock_akhir_dto", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(
#Param("warehouseCode") String warehouseCode,
#Param("productCodes") Set<String> productCode
);
}
How can I read list of #Embeddable objects from MongoDB with Hibernate OGM after aggregation.
I have entity like this
#javax.persistence.Entity
public class MySession implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Type(type = "objectid")
private String id;
private Date start;
private Date end;
#ElementCollection
private List<MySessionEvent> events;
}
and #Embeddable object
#javax.persistence.Embeddable
public class MySessionEvent implements Serializable {
private Long time;
private String name;
}
I stuck with mapping Embeddable objects from native query
String queryString = "db.MySession.aggregate([" +
" { '$match': { 'events.name': { '$regex': 'Abrakadabra'} }}, " +
" { '$unwind': '$events' }, " +
" { '$replaceRoot': { 'newRoot': '$events'}} " +
"])";
List<MySessionEvent> objects = em.createNativeQuery(queryString, MySessionEvent.class).getResultList();
I get an error Caused by: org.hibernate.MappingException: Unknown entity
Copying your comment here because it adds some details:
I have data like this [ {id:'s1', events: [{name: 'one'},{name:
'two'}]}, {id:'s2', events: [{name: 'three'},{name: 'four'}]} ] and I
want result like this [{name: 'one'},{name: 'two'},{name:
'three'},{name: 'four'}]
The query you are running returns the following type of results if I run it on MongoDB natively (I populated it with some random data):
{ "name" : "Event 3", "time" : NumberLong(3) }
{ "name" : "Abrakadabra", "time" : NumberLong(5) }
This is not enough to rebuild an entity and that's the reason you are seeing the exception.
Considering that you only want the list of events, this should work:
List<Object[]> poems = em.createNativeQuery( queryString ).getResultList();
Hibernate OGM will convert the previous result in a list of arrays.
Each element of the List is an array where the firs value of the array is the name of the event and the second one is the time.
For supported cases like this I think HQL queries are better. You could rewrite the same example with the following:
String queryString =
"SELECT e.name " +
"FROM MySession s JOIN s.events e " +
"WHERE e.name LIKE 'Abrakadabra'";
List<Object[]> events = em.createQuery( queryString ).getResultList();
Note that I decided not to return the time because in your comment you didn't request it, but this will work as well:
String queryString =
"SELECT e.time, e.name " +
"FROM MySession s JOIN s.events e " +
"WHERE e.name LIKE 'Abrakadabra'";
List<Object[]> events = em.createQuery( queryString ).getResultList();
It is not recognizing the entity, make sure all your entities are in persistence.xml Embeddable objects too
<class>org.example.package.MySessionEvent</class>