Map resultset to non-entity Spring data Jpa - spring-boot

I have a Spring Batch application and need to make a select from 4 tables. The problem, that i cant use relations, and will write enrichers to select full data.
Currently i have few problems:
Is it possible to map non entity (POJO) from JpaRepository in simple way. I seen examples with #SqlResultSetMapping. I am looking for auto mapping (Maybe JPA, not Spring Data). Column names i can make exacly with result, but it is not working.
It is no problem to mark POJO as Entity, but there is problem. Entity requires #Id. And this is problem for me, as my enrich data not suitable for this.
Maybe someone will give me tips, which better to use.
My perfect purpose is do something like this.
#Query(value = "SELECT id as main_id FROM table where name = ?1",
nativeQuery = true)
List<Data> getAll(String name);
And get objects mapped to my POJO.

You can use JPA constructor expression:
Example:
public class CallStatistics {
private final long count;
private final long total;
private final int min;
private final int max;
private final double avg;
public CallStatistics(long count, long total, int min, int max, double avg) {
this.count = count;
this.total = total;
this.min = min;
this.max = max;
this.avg = avg;
}
//Getters and setters omitted for brevity
}
CallStatistics callStatistics = entityManager.createQuery(
"select new org.hibernate.userguide.hql.CallStatistics(" +
" count(c), " +
" sum(c.duration), " +
" min(c.duration), " +
" max(c.duration), " +
" avg(c.duration)" +
") " +
"from Call c ", CallStatistics.class)
.getSingleResult();
The importants part are:
You must have a class with a matching constructor
You must use NEW followed by the fully qualified class name of the class
From:
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql-select-clause

Related

HQL Expects java.time.duration even though method-head and usage suggest LocalDateTime

I am writing a query within a JpaRepository which takes a String and 3 LocalDateTimes as parameters. Within the query I first compare the String like an Id and afterwards I use a different Column of the corresponding Entity to create LocalDateTimes using appropriate operators for Hibernate 6.
The Application starts up normal but when i call the query I get the following Error:
Argument [2023-01-23T11:43:59] of type [java.time.LocalDateTime] did not match parameter type [java.time.Duration (n/a)]
The Argument obviously got parsed correctly by the Restcontroller but Hibernate does not seem to create the query as expected.
The following is the Code for the Repository and the query in question:
public interface ExchangePairRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT ep.unit FROM MyEntity ep WHERE ep.id= :id AND ((ep.context = 'start' AND ((ep.unit = 'day' AND (:start+ ep.duration day) > :now) "
+ "OR (ep.unit = 'month' AND (:start+ ep.duration month) > :now))) OR (ep.context = 'end' AND ((ep.unit = 'day' AND (:now + ep.duration day) > :end) "
+ "OR (ep.unit = 'month' AND (:now + ep.duration month) > :end)) ))")
List<String> findViable(#Param("matNrOrig") String id, #Param("start") LocalDateTime start, #Param("end") LocalDateTime end,
#Param("now") LocalDateTime now);
}
Below is the Entity which i being used for the query:
#Entity
#Table(name = "my_entity")
#Data
public class MyEntity{
(...)
#Column(name = "id_orig")
private String idOrig;
ยด
#Column(name = "id_target")
private String idTarget;
#Column(name = "context")
private String context;
#Column(name = "duration")
private int duration;
#Column(name = "unit")
private String unit;
}
Is this a bug or am I doing something wrong? Any help is much appreciated.
I am using Hibernate 6.1.6.Final and Spring Boot 3.0.1 with Java 17
Edit:
Casting the parameters within the query solved the problem for now, though it does not look very pretty. I will just wait for the bug being fixed in one of the next releases.
This is a bug in the new parameter type inference logic. I can reproduce with just this query:
session.createQuery("select :dt + 1 day")
.setParameter("dt", LocalDateTime.now())
.getSingleResult();
I have opened an issue for you here: https://hibernate.atlassian.net/browse/HHH-16102
UPDATE
The following workaround is very ugly, but works:
session.createQuery("select cast(:dt as LocalDateTime) + 1 day")
.setParameter("dt", LocalDateTime.now())
.getSingleResult();

Spring OneToMany - how to limit list of objects to list of one field from that object

I wonder if it's possible to fetch List of some specific field of objects instead of list of whole objects from relation #OneToMany:
#Entity
public class Template
...
private Driver driver;
prvate boolean isIpen;
#OneToMany(
mappedBy = "template"
)
private List<Warehouse> warehouses = new ArrayList<>();
I want to fetch list of Template objects with list of Warehouse.name (List<String>) instead of List<Warehouse>. Is it possible?
My repository:
#QueryHints(value = {
#QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")
})
#Query("SELECT at FROM Template at " +
"WHERE at.driver.id = :companyId " +
"AND at.isOpen = true")
#EntityGraph(attributePaths = {"warehouses"})
List<Template> findAllOpenByCompanyId(Long companyId, Pageable pageable);
I wanto to reduce the number of queries to database
I would try using an #ElementCollection with #CollectionTable instead of the #OneToMany.
So it would turn like this:
#Entity
public class Template
...
private Driver driver;
prvate boolean isIpen;
#ElementCollection
#CollectionTable(
name="the name of the warehouse table",
joinColumns=#JoinColumn(name="warehouse id column")
)
#Column(name="warehouse name column in warehouse table")
private List<String> warehouseNames = new ArrayList<>();
I'm unable to test this at the moment, but hopefully it helps.

spring boot data #query to DTO

I want to assign the result of a query to a DTO object. The DTO looks like this:
#Getter
#Setter
#NoArgsConstructor
public class Metric {
private int share;
private int shareholder;
public Metric(int share, int shareholder) {
this.share = share;
this.shareholder = shareholder;
}
}
And the query looks like the following:
#RepositoryRestResource(collectionResourceRel = "shareholders", path = "shareholders")
public interface ShareholderRepository extends PagingAndSortingRepository<Shareholder, Integer> {
#Query(value = "SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM shareholders s WHERE s.attend=true")
Metric getMetrics();
}
However, this didn't work, as I got the following exception:
Caused by:org.hibernate.QueryException: could not resolve property: no_of_shares of:com.company.shareholders.sh.Shareholder[SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM com.company.shareholders.sh.Shareholder s WHERE s.attend=true]
In my project I've used projections to this like shown below:
#Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
List<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId);
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
Page<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId, Pageable pageable);
}
The interface to which the result is projected:
public interface PeopleDTO {
String getName();
Long getCount();
}
The fields from the projected interface must match the fields in this entity. Otherwise field mapping might break.
Also if you use SELECT table.column notation always define aliases matching names from entity as shown in example.
In your case change #Query like shown below:
#Query(value = "SELECT new " +
"SUM(s.no_of_shares) AS sum,COUNT(*) AS count FROM " +
"shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDTO getMetrics();
And create interface MetricDTO like shown below:
public interface MetricDTO {
Integer getSum();
Long getCount();
}
Also make sure the return type of getSum() and getCount() is correct this may vary based not database.
First, you can have a look at the Spring Data JPA documentation, you can find some help at this section : Class-based Projections (DTOs).
There is also a paragraph titled Avoid boilerplate code for projection DTOs, where they advise you to use Lombok's #Value annotation, to produce an immutable DTO. This is similar to Lombok's #Data annotation, but immutable.
If you apply it to your example, the source will look like :
#Value
public class MetricDto {
private int share;
private int shareholder;
}
Then, as your query is a NativeQuery, specifiy it in your Spring Data Repository.
You can find help in the documentation : Native Queries.
You will need something like :
#Query(value = "SELECT new
com.company.shareholders.sh.MetricDto(SUM(s.no_of_shares),COUNT(*)) FROM
shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDto getMetrics();
Query query = sessionFactory.getCurrentSession()
.createNativeQuery(stringQuery).unwrap(org.hibernate.query.Query.class);
((NativeQueryImpl) query).setResultTransformer(new AliasToBeanResultTransformer(DtoClass.class));
You are writing a mixed query of native and jpql; no_of_shares is your column name in the database, but jpa is expecting you to provide not native syntax so try to replace no_of_shares with the corresponding field in your entity class. Or just add nativeQuery = true to make jpa understand it's a native query.

How do i get an object of aggregate values using findByNamedParam?

Am trying to get an object of count(*) and sum() through a hibernate query which actually uses another object. Here's what I'm doing :
String query = select new org.rangde.domain.AggregatedCount(count(*), sum(lps.loanAmount - lps.loanPledged - lps.loanRaised)) from LoanProfileSnapshot lps where lps.loanState in (:loanStates)
List<AggregatedCount> counts = getHibernateTemplate().findByNamedParam(query, params, values);
return counts.size() > 0 ? counts.get(0) : null;
and this is the class AggregatedCount (have removed getters and setters)
public class AggregatedCount {
private int id;
private BigInteger count;
private BigDecimal sum;
public AggregatedCount(){}
public AggregatedCount(BigInteger count, BigDecimal sum){
this.count = count;
this.setSum(sum);
}
}
This is the exception i get when i run the query.
Unable to locate appropriate constructor on class [org.domain.AggregatedCount] ... nested exception is org.hibernate.hql.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [org.domain.AggregatedCount] ...
I tried having the sum as Double and BigInteger and still I'm getting the same exception.
Any help would be appreciated. I'd prefer to stick to findByNamedParam for this because there are a lot of conditions that I'm checking within the code before the final query is generated. Thanks.
Although my answer seems a little bit too late maybe it will help others ..
The necessary datatype is Long for the count and sum depends on the datatype in the entity.
It also depends on the hibernate version you are using:
https://community.jboss.org/wiki/HibernateCoreMigrationGuide35?_sscc=t
public class AggregatedCount {
private Long count;
private Long sum;
public AggregatedCount(Long count, Long sum){
this.count = count;
this.setSum(sum);
}
}

Spring Data MongoDB: Accessing and updating sub documents

First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}

Resources