How to use Spring boot JPA native query with join unrelated entities - spring-boot

I have a custom model for order and item that will hold minimum data than the actual entity class
Models
class OrderMinimalModel {   
long id;   
String comment;   
List<ItemMinimalModel> items;
}
class ItemMinimalModel{   
long id;   
String name;
}
Query I am looking for
#Query( value = "SELECT O.id as orderId, O.comment as orderComment, I.id as itemId, I.name as itemName FROM order O "
+ " left join item I on I.order_id = O.id"
+ " WHERE O.status = 1 ",nativeQuery = true)
List<OrderMinimalModel > findAllOrderMinimal();
But I am getting bellow error
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.model.OrderMinimalModel]
Maybe I am doing wrong.

Related

Spring JPA projection - selecting specific columns in nested object list

Why I get the following error:
query specified join fetching, but the owner of the fetched
association was not present in the select list
when I try to get List of ids?
#Query("SELECT at.id FROM Template at " +
"WHERE at.shipper.id = :companyId " +
"AND at.isActive = true")
#EntityGraph(attributePaths = {"shipper"})
List<Long> findTemplateIdsByCompanyId2(Long companyId, Pageable pageable);
but when I want to get list of objects - everything is OK?
#Query("SELECT at FROM Template at " +
"WHERE at.shipper.id = :companyId " +
"AND at.isActive = true")
#EntityGraph(attributePaths = {"shipper"})
List<Template > findTemplateIdsByCompanyId2(Long companyId, Pageable pageable);
Template entity has OneToOne relationship with shipper field and OneToMany relationship with warehouse field
You need to join in the query if you are not fetching the entity, something like this should do it:
SELECT at.id FROM Template at JOIN at.Shipper s WHERE s.id = :companyId and at.isActive = true

The #SqlResultSetMapping usage cause the schema validation problem

I have to extract data for statistic purpose. I've created a native query and used #SqlResultSetMapping to map the resultset to an object.
Hibernate needs to declare this class (Elaboration) as #Entity BUT IS NOT A TABLE, and I don't want a table because I have only to extract data on the fly when needed.
The code works fine but the gitlab pipeline fails during validation with
schemaManagementException: Schema-validation: missing table [elaboration].
Here my code so far:
SqlResultSetMapping(name="ValueMapping",
classes={
#ConstructorResult(
targetClass=Elaboration.class,
columns={
#ColumnResult(name="areadesc", type=String.class),
#ColumnResult(name="subsectordesc", type=String.class),
#ColumnResult(name="eurovalue", type=BigDecimal.class),
#ColumnResult(name="eurotch", type=BigDecimal.class),
}
)
})
#Entity
public class Elaboration{
#Id
private Long id;
private String areadesc;
private String subsectordesc;
private Integer dossiercount;
private BigDecimal eurovalue;
private BigDecimal eurotch;
....
and the custom query:
String statisticValueQuery = "select a.mdescr as areadesc, s.mdescr as subsectordesc, sum(euro_value) as eurovalue,
sum(euro_value_tch) as eurotch " +
"from dossier d " +
"join dossier_document dd on d.id = dd.dossier_id " +
"join dossier_country dc on d.id = dc.dossier_id " +
"join country c on dc.country_id = c.id " +
"join area a on c.area_id = a.id " +
"join dossier_subsector ds on d.id = ds.dossier_id " +
"join subsector s on ds.subsector_id = s.id " +
"where dd.document_id = :document " +
"and d.submission_date >= :startdate and d.submission_date <= :enddate " +
"group by s.id, a.id;";
public List<Elaboration> getValueElaboration(ElaborationRequestDTO elaborationRequestDTO){
Query resultMapping = em.createNativeQuery(statisticValueQuery, "ValueMapping");
resultMapping.setParameter("startdate", elaborationRequestDTO.getElaborateFromEquals());
resultMapping.setParameter("enddate", elaborationRequestDTO.getElaborateToEquals());
resultMapping.setParameter("document", elaborationRequestDTO.getDocumentIdEquals());
return resultMapping.getResultList();
Is there a way to pass the validation test?
Thanks
This is wrong statement.
Hibernate needs to declare this class (Elaboration) as #Entity
You should just put your #SqlResultSetMapping declaration above some #Entity but it can be some other entity not related to the Elaboration.
#SqlResultSetMapping(name="ValueMapping",
classes={
#ConstructorResult(
targetClass=Elaboration.class,
columns={
#ColumnResult(name="areadesc", type=String.class),
#ColumnResult(name="subsectordesc", type=String.class),
#ColumnResult(name="eurovalue", type=BigDecimal.class),
#ColumnResult(name="eurotch", type=BigDecimal.class),
}
)
})
#Entity
public class SomeEntity {
}
And if Elaboration is not an entity you should not annotate it as such.

How to solve SQLException - Data conversion error in Spring Boot

I have two tables that are connected via class name (1:n).
Domain: Product (1)
Domain: HistoryPrice (n)
Product
#Entity
#Table
public class Product extends AbstractBaseDomain<Long> {
#NotBlank
#Size(min = 2, max = 50)
#Column(name ="name", unique = true)
private String name;
HistoryPrice
#Entity
#Table(name = "historyPrice")
public class HistoryPrice extends AbstractBaseDomain<Long> {
#NotNull
#ManyToOne
#JoinColumn(name ="product")
private Product product;
This is my repository
#Repository
public interface HistoryPriceRepository extends JpaRepository<HistoryPrice, Long> {
#Query(value = "SELECT h.product " +
"FROM history_price h " +
"INNER JOIN product p ON h.product = p.name " +
"WHERE p.name = :name", nativeQuery = true)
List<?> findProductByName(#Param("name") String name);
}
This is my Controller
#PostMapping(value = "/historyPrice")
public String searchForProducts(Model model, #RequestParam String namePart) {
List<?> productList = historyPriceService.findProductName(namePart);
model.addAttribute(HISTORYPRICE_VIEW, productList);
return HISTORYPRICE_VIEW;
}
This is my SQL output of my table creation:
2019-04-11 18:39:20 DEBUG org.hibernate.SQL - create table history_price (id bigint not null, version integer, price decimal(19,2) not null, valid_since timestamp not null, product bigint not null, primary key (id))
2019-04-11 18:39:20 DEBUG org.hibernate.SQL - create table product (id bigint not null, version integer, current_price decimal(19,2) not null, manufacturer varchar(50), name varchar(50), primary key (id))
This is my shortened error that I always get:
Caused by: org.hibernate.exception.DataException: could not extract ResultSet
Caused by: org.h2.jdbc.JdbcSQLException: Datenumwandlungsfehler beim Umwandeln von "HAMMER"
Data conversion error converting "HAMMER"; SQL statement:
SELECT h.product FROM history_price h INNER JOIN product p ON h.product = p.name WHERE p.name = ? [22018-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:168)
Caused by: java.lang.NumberFormatException: For input string: "HAMMER"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
I do not know whether my problem is in my repository or somewhere else.
Maybe someone can give me a the right solution or a good hint.
Thank you very much.
The problem indicated by your stacktrace is your join. You try to join h.product which is the id of the product object internally to h.product.name which is a string. Spring tries to parse the string as number afterwards thus resulting in the NumberFormatException.
I assume you want to get the HistoryPrice objects. Thus you have three options in your repository:
Use native query as you do now but fix tablenames and join, I assume this could work:
"SELECT h.* " +
"FROM historyPrice h " +
"INNER JOIN product p ON h.product = p.id " +
"WHERE p.name = :name"
Use a JPQL query:
"SELECT h " +
"FROM historyPrice h " +
"INNER JOIN product p " +
"WHERE p.name = :name"
Use the method name to let spring data generate your queries:
List<HistoryPrice> findAllByProductName(String name);
do you have any stack ?
Can you copy the error ?
In your log stack you should see some caused by label which will give you the place where the exception is throwed
It seems in the native query you are trying to equate a product object with a string name.
#Query(value = "SELECT h.product " +
"FROM history_price h " +
"INNER JOIN product p ON h.product.name = p.name " +
"WHERE p.name = :name", nativeQuery = true)
List<?> findProductByName(#Param("name") String name);
If the Product Entity contains a name variable, then the above query might execute.

JPA native query with same column twice

I am a JPA newbie and wanted to have a JPA native query for a single table (below) which I would like to fetch in my #Entity based class called TestRequest. It has a column 'RequestTime' that is fetched with DAYNAME() and then with DATEDIFF() functions.
SELECT TestRequest.Id AS Id
, TestRequest.RequestTime AS RequestTime
, DAYNAME(TestRequest.RequestTime) AS RequestDay
, TestRequest.StatusMessage AS StatusMessage
, DATEDIFF(CURDATE(), TestRequest.RequestTime) AS HowLongAgo
FROM TestRequest
LEFT JOIN TestRun
ON TestRequest.TestRunId = TestRun.Id
WHERE Requestor = '[NAME]'
ORDER BY Id DESC
Is there any way in which the column (fetched second time as HowLongAgo) be set into a property which is not mapped to a table column within the TestRequest class? Are there any field level annotations for this?
You need to use Interface-based projections:
You will need to create an interface that define the getters for each field in your projection like:
public interface RequestJoinRunProjection {
int getId();
LocalDate getRequestTime();
String getMessage();
String getRequestDay();
Long getHowLongAgo();
}
Then you define a method on your Repository that has the native query you want to run:
public interface TestRequestRepository extends CrudRepository<TestRequest, Long> {
// Any other custom method for TestRequest entity
#Query(value = "SELECT trq.Id AS id " +
" , trq.RequestTime AS requestTime " +
" , DAYNAME(trq.RequestTime) AS requestDay " +
" , trq.StatusMessage AS statusMessage " +
" , DATEDIFF(YEAR, CURDATE(), trq.RequestTime) AS howLongAgo " +
"FROM TestRequest trq " +
" LEFT JOIN TestRun tr " +
" ON trq.TestRunId = tr.Id " +
"WHERE Requestor = ?1 ORDER BY Id DESC"), nativeQuery = true)
List<RequestJoinRunProjection> findTestSumary(String name);
}
Notice query must be native since you are using database functions, also the column names must match the setters of your projection interface(following bean rules), so use AS in order to change the names in your query.
I strongly suggest you test your query on h2 before injecting into #Query annotation. DATEDIFF requires 3 parameters.

spring data jpa custom query fails to recognize class type

Not able to use custom POJO classes for my spring data jpa queries. Repeatedly fails with the following exception
"org.hibernate.MappingException: Unknown entity:
com.app.mycompany.AgileCenterServices.entities.ComponentDetailedInfo"*
Tried replacing the custom ComponentDetailedInfo.class and not mentioning anything during the call to entityManager.createNativeQuery(componentQuery.toString()), but then Object List returned fails to be converted to the specific POJO class after the query.
#Override
public ComponentListResponsePaginated findComponentByProjectId(String projectId, Pageable pageable) {
logger.info(" Inside findComponentByProjectId() API in IssueComponentServiceImpl");
String componentQuery = "select c.*, u.fullname "
+ "from issue_component c "
+ "left join user u on c.component_lead = u.username "
+ "where "
+ "upper(c.project_id) = upper(" + projectId + ")";
List<ComponentDetailedInfo> compList = new ArrayList<ComponentDetailedInfo>();
try {
logger.info(" ************* Printing query ******************************* ");
logger.info(componentQuery.toString());
compList = entityManager.createNativeQuery(componentQuery.toString(), ComponentDetailedInfo.class) .setFirstResult(pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
}
}
Also tried the following
List<? extends Object> objList = null;
objList = entityManager.createNativeQuery(componentQuery.toString()) .setFirstResult(pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
if(objList != null && objList.size() > 0) {
for(Object rec: objList) {
logger.info(" Printing Object ::: " + rec.toString());
compList.add((ComponentDetailedInfo)rec);
}
}
However the compList fails with the
java.lang.ClassCastException
The custom query returned should get typecast to the specific class type passed to the entityManager.createNativeQuery. However, I am facing the exception as mentioned above when I pass the class to createNativeQuery().
Even tried by totally removed the class in the createNativeQuery...
You have to define a constructor result mapping if you want to use a POJO as a result of a native query.
Here is an example query:
Query q = em.createNativeQuery(
"SELECT c.id, c.name, COUNT(o) as orderCount, AVG(o.price) AS avgOrder " +
"FROM Customer c " +
"JOIN Orders o ON o.cid = c.id " +
"GROUP BY c.id, c.name",
"CustomerDetailsResult");
And that's the mapping you have to add to your Entity:
#SqlResultSetMapping(name="CustomerDetailsResult",
classes={
#ConstructorResult(targetClass=com.acme.CustomerDetails.class,
columns={
#ColumnResult(name="id"),
#ColumnResult(name="name"),
#ColumnResult(name="orderCount"),
#ColumnResult(name="avgOrder", type=Double.class)})
})
If you don't like that approach you could use QLRM. Learn more about it here: https://github.com/simasch/qlrm

Resources