Spring JPA Projection - Queried Data from jpa repository - java

I don't know if this is possible but I am trying to project data queried from JPA repository into a DTO
I have the following query:
#Query(value =
"SELECT crop.id, count(*) as total " +
"FROM xxx.crop_sub_plot " +
"join crop on crop.id = crop_sub_plot.crop_id " +
"join sub_plot on sub_plot.id = crop_sub_plot.sub_plot_id " +
"where sub_plot.enabled = true " +
"group by crop_id " +
"order by total DESC;", nativeQuery = true)
List<CropUsedView> findCropsInUseOrderByDesc();
and the DTO:
public class CropUsedView implements Serializable{
private BigInteger id;
private BigInteger total;
public CropUsedView() {
}
public CropUsedView(BigInteger id, BigInteger total) {
this.id = id;
this.total = total;
}
//getters && setters
I'm getting the error:
No converter found capable of converting from type [java.math.BigInteger] to type [net.xxx.crop.CropUsedView]
I don't really know if this is possible, any suggestion?
EDIT: this is how the data is returning when I run the query on MySql and is how I want to be converted to a DTO:

Your query is returning two values: the id and a count (both can be mapped in a long or BigDecimal). But Hibernate, as it's not mapped directly into an object, is just returning BigDecimal[].
To solve this, you should use a custom mapper: UserType (https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/usertype/UserType.html). This allows you to map whatever response into an object with a manual parsing.

Related

How to generate entity class without create table automatic

I created some reports for my system, and that report is made up of many tables. For this, I create a Domain class with an #Entity annotation and implement a JpaRepository repository, I'm using the native query with #Query, as shown below.
My problem is that for each domain class a table is being created by hibernate, how do I stop it?
My Domain class:
#Entity
#Immutable
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#IdClass(WidgetDailyReportCompositeKey.class)
public class WidgetDailyReportDomain{
#Id
#Column(updatable = false, insertable = false)
private UUID id_construction;
#Id
#Column(updatable = false, insertable = false)
private String name;
#Id
#Column(updatable = false, insertable = false)
private Date dt_cost;
#Column(updatable = false, insertable = false)
private Double total;
}
My Repository:
public interface WidgetRepository extends JpaRepository<WidgetDailyReportDomain, UUID>{
#Query(value = " SELECT ct.id AS id_construction, " +
" ct.name, " +
" sm.dt_service AS dt_cost, " +
" sum(smi.nu_value * stiv.amount) AS total " +
" FROM service_measurement sm " +
" INNER JOIN service_measurement_item smi ON smi.id_service_measurement = sm.id " +
" INNER JOIN service s ON s.id = sm.id_service " +
" INNER JOIN service_type_item_service stiv ON stiv.id_service = sm.id_service " +
" AND stiv.id_service_type_item = smi.id_service_item " +
" INNER JOIN construction ct ON ct.id = s.id_construction " +
" WHERE s.id_construction IN ( " +
" select s.id_construction " +
" from service_measurement sm " +
" INNER JOIN service_measurement_item smi ON smi.id_service_measurement = sm.id " +
" INNER JOIN service s ON s.id = sm.id_service " +
" INNER JOIN service_type_item_service stiv ON stiv.id_service = sm.id_service " +
" AND stiv.id_service_type_item = smi.id_service_item " +
" INNER JOIN construction ct on ct.id = s.id_construction " +
" WHERE sm.dt_service BETWEEN :minDate AND :maxDate " +
" GROUP BY s.id_construction " +
" ORDER BY sum(smi.nu_value * stiv.value) DESC " +
" limit :limit " +
" ) " +
" AND sm.dt_service BETWEEN :minDate AND :maxDate " +
" GROUP BY ct.id, sm.dt_service " +
" HAVING sum(smi.nu_value * stiv.amount) > 0 " +
" ORDER BY sm.dt_service;", nativeQuery = true)
List<WidgetDailyReportDomain> findTopExpensiveConstruction(#Param("minDate") Date minDate, #Param("maxDate") Date maxDate, #Param("limit") int limit);
//....
Your WidgetDailyReportDomain is actually projection. You don't need to mark it as #Entity.
And your #Query could belong to any other really existing repository.
You can remove all the javax.persistence annotations like #Column, #Id, #Entity. These annotations represent properties of a table, which you seem to not want it to be.
Then you can use the WidgetDailyReportDomain object as a DTO to be your projection and not have it attached to the EntityManager:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
EDIT: Also, do not forget to build a constructor for that object so that Spring JPA loads the values into the object (like described on the documentation).
If you don't want to build a constructor, maybe you can change it into an interface and use it as your projection: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.interfaces
It looks like you're using Spring due to the JpaRepository in your question.
If you're using Spring Boot, then you can add
spring:
jpa:
hibernate:
ddl-auto: none
to your application.yml file, or
spring.jpa.hibernate.ddl-auto=none
to your application.properties file.
If you're using a persistence.xml file, you could add a property to disable it there, too:
<property name="hibernate.hbm2ddl.auto" value="none"/>
Disabling the generation of the schema tables like this means that you'll have to make sure they're created by some other means before your application will work, though.
After #Zagarh answer, i did a lith of search about DTO, and i came up with a not very elegant solution, but that is working:
The Domain class :
public class WidgetDailyReportDomain{
private UUID id_construction;
private String name;
private Date dt_cost;
private Double total;
}
I have to create a custom result mapping, for the JPA be able of mapping de result, i use the annotation #SqlResultSetMapping. But for some reason she is only identify in a class that is annotated with # Entity. For not to get disorganized, i create a class exclusive to annotation with # SqlResultSetMapping, because i gona have a lot of mapping to do. The class looked like this:
#MappedSuperclass
#SqlResultSetMapping(
name = "widget_daily_mapping",
classes = {
#ConstructorResult(
targetClass = WidgetDailyReportDomain.class,
columns = {
#ColumnResult(name="id_construction", type = UUID.class),
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "dt_cost", type = Date.class),
#ColumnResult(name = "total", type = Double.class)
}
)
}
)
public abstract class ResultSetMappingConfig {
}
And then i create a custom implementation of Jpa Repository
public interface WidgetRepositoryCustom {
List<WidgetDailyReportDomain> findTopExpensiveConstruction(Date minDate, Date maxDate, int limit);
}
#Repository
#Transactional(readOnly = true)
public class AR_serviceRepositoryImpl implements AR_serviceRepositoryCustom{
#PersistenceContext
private EntityManager em;
#Override
public List<AR_serviceDomain> getListOfService(UUID id_construction) {
Query query = em.createNativeQuery("
//Native Query Here...
", "widget_daily_mapping");// Put the result mapping Here
query.setParameter(1, id_construction //..Parameters Here);
return query.getResultList();
}
}
Ps: 1) If any one have a better solution please let me know. 2) Sorry for my english, i'm using google translate.

Unable to pass a collection to a constructor via #Query

I am getting an SQL exception
java.sql.SQLException: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'as col_7_0_ from locales offerlocal0_ cross join offers offer2_ inner join offer' at line 1
While calling the repository method
#Query("SELECT DISTINCT new com.greenflamingo.staticplus.model.catalog.dto.OfferGet(ol.root.id,ol.title "
+ ",ol.description,dl.name,ol.root.price,ol.root.currency,ol.root.visible,ol.root.images) "
+ "FROM OfferLocale ol,DescriptorLocale dl "
+ "WHERE ol.root.webfront.id = (:webId) AND ol.culture.languageCode = (:langCode) "
+ "AND dl.culture.languageCode = (:langCode) "
+ "AND ol.root.category = dl.root")
Page<OfferGet> findAllWebfrontLocalized(#Param("webId")int webfrontId,#Param("langCode")String langCode,Pageable pageable );
I have narrowed the issue down to the Collection i am trying to pass to constructor (ol.root.images) . Tried with List (it gave me a constructor missmatch) and with Set (had the same error as shown here)
This is the bean i am using
public class OfferGet implements Serializable{
private static final long serialVersionUID = 6942049862208633335L;
private int id;
private String title;
private String shortDescription;
private String price;
private String category;
private boolean visible;
private List<Image> images;
public OfferGet(String title, String category) {
super();
..........
}
public OfferGet() {
super();
}
public OfferGet(int id, String title, String description
, BigDecimal price
,String currency,
boolean visible) {
.........
}
public OfferGet(int id, String title, String description,String category
, BigDecimal price
,String currency,
boolean visible,
Collection<Image> images) {
..........
}
}
I am using java 11, mariaDb and Springboot 2.0.5
Does anyone know why is this happening and if there is any way around it? Any help would be much appreciated, mustache gracias! :D
It's not possible to create an object with the constructor expression that takes a collection as argument.
The result of a SQL query is always a table.
The reason is that identification variables such that they represent instances, not collections.
Additionally you cannot return root.images you must join the OneToMany relationship and then you no longer have a collection but each attribute.
The result of the query will be cartesian product.
This is a correct query:
#Query("SELECT DISTINCT new com.greenflamingo.staticplus.model.catalog.dto.OfferGet(ol.root.id,ol.title "
+ ",ol.description,dl.name,ol.root.price,ol.root.currency,ol.root.visible, image) "
+ "FROM OfferLocale ol,DescriptorLocale dl "
+ "JOIN ol.root.images image
+ "WHERE ol.root.webfront.id = (:webId) AND ol.culture.languageCode = (:langCode) "
+ "AND dl.culture.languageCode = (:langCode) "
+ "AND ol.root.category = dl.root")

Hibernate OGM mapping #Embeddable objects of native query

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>

Using Spring JPA to setup a Aggregate Function for STRING_AGG with Postgresql for a Group_By

I am trying to setup a repository call to retrieve the ID's of a list of test results ids used in the GROUP_BY. I can get this to work using createNativeQuery but I am unable to get this to work using Spring's JPA with the FUNCTION call.
FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ',')) as ids
I am using Spring Boot 1.4, hibernate and PostgreSQL.
Question
If someone can please help me out to setup the proper function call
shown below in the JPA example it would be much appreciated.
Update 1
After implementing the custom dialect it looks like its trying to cast the function to a long. Is the Function code correct?
FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ','))
Update 2
After looking into the dialect further it looks like you need to register the return type for your function otherwise it will default to a long. See below for a solution.
Here is my code:
DTO
#Data
#NoArgsConstructor
#AllArgsConstructor
public class TestScriptErrorAnalysisDto {
private String testScriptName;
private String testScriptVersion;
private String checkpointName;
private String actionName;
private String errorMessage;
private Long count;
private String testResultIds;
}
Controller
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<Set<TestScriptErrorAnalysisDto>> getTestScriptErrorsByExecutionId(#RequestParam("executionId") Long executionId) throws Exception {
return new ResponseEntity<Set<TestScriptErrorAnalysisDto>>(testScriptErrorAnalysisRepository.findTestScriptErrorsByExecutionId(executionId), HttpStatus.OK);
}
Repository trying to use Function Not working
#Query(value = "SELECT new com.dto.TestScriptErrorAnalysisDto(r.testScriptName, r.testScriptVersion, c.name, ac.name, ac.errorMessage, count(*) as ec, FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ',')) "
+ "FROM Action ac, Checkpoint c, TestResult r " + "WHERE ac.status = 'Failed' " + "AND ac.checkpoint = c.id " + "AND r.id = c.testResult " + "AND r.testRunExecutionLogId = :executionId "
+ "GROUP by r.testScriptName, r.testScriptVersion, c.name, ac.name, ac.errorMessage " + "ORDER by ec desc")
Set<TestScriptErrorAnalysisDto> findTestScriptErrorsByExecutionId(#Param("executionId") Long executionId);
Repository using createNativeQuery working
List<Object[]> errorObjects = entityManager.createNativeQuery(
"SELECT r.test_script_name, r.test_script_version, c.name as checkpoint_name, ac.name as action_name, ac.error_message, count(*) as ec, string_agg(to_char(r.id, '999999999999'), ',') as test_result_ids "
+ "FROM action ac, checkpoint c, test_result r " + "WHERE ac.status = 'Failed' " + "AND ac.checkpoint_id = c.id "
+ "AND r.id = c.test_result_id " + "AND r.test_run_execution_log_id = ? "
+ "GROUP by r.test_script_name, r.test_script_version, c.name, ac.name, ac.error_message " + "ORDER by ec desc")
.setParameter(1, test_run_execution_log_id).getResultList();
for (Object[] obj : errorObjects) {
for (Object ind : obj) {
log.debug("Value: " + ind.toString());
log.debug("Value: " + ind.getClass());
}
}
Here was the documents I found on FUNCTION
4.6.17.3 Invocation of Predefined and User-defined Database Functions
The invocation of functions other than the built-in functions of the Java Persistence query language is supported by means of the function_invocation syntax. This includes the invocation of predefined database functions and user-defined database functions.
function_invocation::= FUNCTION(function_name {, function_arg}*)
function_arg ::=
literal |
state_valued_path_expression |
input_parameter |
scalar_expression
The function_name argument is a string that denotes the database function that is to be invoked. The arguments must be suitable for the database function that is to be invoked. The result of the function must be suitable for the invocation context.
The function may be a database-defined function or a user-defined function. The function may be a scalar function or an aggregate function.
Applications that use the function_invocation syntax will not be portable across databases.
Example:
SELECT c
FROM Customer c
WHERE FUNCTION(‘hasGoodCredit’, c.balance, c.creditLimit)
In the end the main piece which was missing was defining the functions by creating a new class to extend the PostgreSQL94Dialect. Since these functions were not defined for the dialect they were not processed in the call.
public class MCBPostgreSQL9Dialect extends PostgreSQL94Dialect {
public MCBPostgreSQL9Dialect() {
super();
registerFunction("string_agg", new StandardSQLFunction("string_agg", new org.hibernate.type.StringType()));
registerFunction("to_char", new StandardSQLFunction("to_char"));
registerFunction("trim", new StandardSQLFunction("trim"));
}
}
The other issue was that a type needed to be set for the return type of the function on registration. I was getting a long back because by default registerFunction returns a long even though string_agg would return a string in a sql query in postgres.
After updating that with new org.hibernate.type.StringType() it worked.
registerFunction("string_agg", new StandardSQLFunction("string_agg", new org.hibernate.type.StringType()));

JPA #OneToMany: Returning only some values from map in a query

I'm trying to run a JPA query to return only specific fields from my entity, rather than the entire entity (for performance reasons).
Within this entity is this:
#OneToMany(cascade = { CascadeType.ALL }, mappedBy = "helper", fetch = FetchType.EAGER)
#MapKeyColumn(name = "year")
public Map<Integer, DutyHistory> getDutyHistoryList() {
return dutyHistoryList;
}
I'd like, within my query, to return multiple values from this map e.g. fields from the DutyHistory object for the last 3 years.
My question is, what's the query syntax for this? I'm mapping the returned values to a POJO as below:
#Query(value = "SELECT new com.castlemon.helpers.dto.ReportHelper(h.helperId, h.firstName, h.secondName"
+ ", h.sex, h.service, h.dateOfBirth, h.schoolGroup, h.orientationRequired, h.notes as adminNotes "
+ ", h.primaryDuty.dutyName as primaryDuty, h.secondDuty, h.secondaryDuty.dutyName as secondaryDuty "
+ " WHERE h.travelling = 1")
public List<ReportHelper> getTravellingHelperDetails();
You should create another query with your "year" parameter
#Query(value = "SELECT new com.castlemon.helpers.dto.ReportHelper(h.helperId, h.firstName, h.secondName"
+ ", h.sex, h.service, h.dateOfBirth, h.schoolGroup, h.orientationRequired, h.notes as adminNotes "
+ ", h.primaryDuty.dutyName as primaryDuty, h.secondDuty, h.secondaryDuty.dutyName as secondaryDuty "
+ " WHERE h.travelling = 1 AND h.year >= :yearLimit")
public List<ReportHelper> getTravellingHelperDetailsUntilYear(String yearLimit);

Categories

Resources