JPA sorting query result after querying with Pageable - spring-boot

I am having an #Entity like this:
import java.time.LocalDateTime;
import javax.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.AbstractPersistable;
#Entity
#Data#NoArgsConstructor#AllArgsConstructor
public class Message extends AbstractPersistable<Long> {
private LocalDateTime messageDate = LocalDateTime.now();
private String message;
}
And a repository like this:
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MessageRepository extends JpaRepository<Message, Long> {
//List<Message> findAllByOrderByMessageDateAsc(Pageable pageable);
// With this I am trying to re-sort what I get
}
And a #Controller
#GetMapping("/messages")
public String list(Model model) {
Pageable limit = PageRequest.of(0, 5, Sort.by("messageDate").descending());
model.addAttribute("messages", messageRepository.findAll(limit));
//model.addAttribute("messages", messageRepository.findAllByOrderByMessageDateAsc(limit));
return "messages";
}
I get five latest messages in descending order. But how do I get them in ascending order?

What you need is the last 5 messages in ascending order by massage date.
Two ways to solve it.
Using Custom implementation of Pageable
You can't use offset properly for PageRequest. So,
you need to use a custom implementation of Pageable for offset.
You can use custom implementation OffsetBasedPageRequest. Then use it this way.
int totalCount = (int)serviceRepository.count();
Pageable pageable = new OffsetBasedPageRequest(totalCount - limit, limit, Sort.by("messageDate"));
messageRepository.findAll(pageable);
After fetching Sort Page data
You can get the list from Page<T> using page.getContent() then sort manually the list.
Pageable pageable = PageRequest.of(0, limit, Sort.by("messageDate").descending());
List<Message> list = messageRepository.findAll(pageable ).getContent();
List<Message> sorted =list.stream().sorted(Comparator.comparing(r -> r.getMessageDate())).collect(Collectors.toList());
Then again you have to create Page<Massage> if you want.

Related

Spring Boot JPA Query modify dynamically

Using Spring boot,I am working on one business use case where i need to modify the JPA query generated at runtime based on configuration.
For Example .. if query that JPA generates is
select * from customers where id=1234
I want to modify it in runtime like based on user's logged in context. (Context has one attribute business unit) like given below ..
select * from customers where id=1234 and ***business_unit='BU001'***
Due to certain business use case restrictions i can't have statically typed query.
Using Spring boot and Postgres SQL.
Try JPA criteria builder , it let you to create dynamics query programmatically.
Take look in this post
What is stopping you to extract the business unit from the context and pass it to the query?
If you have this Entity
#Entity
CustomerEntity {
Long id;
String businessUnit;
//geters + setters
}
you can add this query to your JPA Repository interface:
CustomerEntity findByIdAndBusinessUnit(Long id, String businessUnit)
This will generate the following "where" clause:
… where x.id=?1 and x.businessUnit=?2
for complete documentation check Spring Data Jpa Query creation guide.
you would do something like this, this lets you dynamically define additional predicates you need in your query. if you don't want to have all the conditions in your query with #Query
The below example just adds a single predicate.
import java.util.ArrayList;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import study.spring.data.jpa.models.TicketPrice;
#Component
public class TricketPriceCriteriaRepository {
#Autowired
TicketPriceJpaRepository ticketPriceJpaRepository;
public List<TicketPrice> findByCriteria(int price) {
return ticketPriceJpaRepository.findAll(new Specification<TicketPrice>() {
#Override
public Predicate toPredicate(Root<TicketPrice> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (price > 0) {
predicates.add(
criteriaBuilder.and(criteriaBuilder.greaterThan(root.get("basePrice"), price)));
}
// Add other predicates here based on your inputs
// Your session based predicate
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}
}
Your base repository would be like
// Other imports
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TicketPriceJpaRepository
extends JpaRepository<TicketPrice, Long>, JpaSpecificationExecutor<TicketPrice> {}
the model consists basePrice
#Column(name = "base_price")
private BigDecimal basePrice;

SpringData JPA - No Dialect mapping for JDBC type: 2003

Am getting the below error
No Dialect mapping for JDBC type: 2003; nested exception is org.hibernate.MappingException: No Dialect mapping for JDBC type: 2
Repo Code is as follows
String chrPackageId = "select\n" +
"\tcombination_hr_id as \"chRuleId\",\n" +
"\tcombination_hr_name as \"chRuleName\", \n" +
"\tholdingrule_list as \"selectedRules\"\n" +
"from\n" +
"\tcombination_holding_rule chr\n" +
"where\n" +
"\tpackage_id =:packageId";
#Query(value=chrPackageId,nativeQuery = true)
List<CHRfromPackageIdDTO> repoCHRFromPackageId(int packageId);
DTO object is as below
public interface CHRfromPackageIdDTO {
int getChRuleId();
String getChRuleName();
Integer[] getSelectedRules();
}
We use Postgres DB, there is some issue in getting the Integer[] value actually.
The other answers in Stackoverflow are Hibernate specific. but we use spring-data-jpa.
Entity Class
import com.vladmihalcea.hibernate.type.array.IntArrayType;
import com.vladmihalcea.hibernate.type.array.StringArrayType;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import com.vladmihalcea.hibernate.type.json.JsonStringType;
import lombok.*;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
),
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
#Entity
#Table(name="combination_holding_rule")
public class CombHoldingRule {
#Id
#Column(name="combination_hr_id")//Checked
private Integer combHoldingRuleId;
#Column(name="combination_hr_name")//Checked
private String combHoldingRuleName;
#Column(name="jurisdiction_id")//Checked
private Integer jurisdictionId;
#Column(name="function_group_id")//Checked
private Integer functionGroupId;
#Column(name="overall_netting_type")//Checked
private String overallNettingType;
#Column(name="package_id")//Checked
private Integer packageId;
#Type(type = "int-array")
#Column(
name = "holdingrule_list",
columnDefinition = "integer[]"
)
private int[] holdingRuleList;
}
In Repository
#Query(value="from CombHoldingRule where packageId=:packageId")
List<CombHoldingRule> repoCHRFromPackageId(#Param("packageId") int packageId);
I took the result from JPAQuery into the Entity , then did the below in the Service Layer
public List<CHRfromPackageIdDTO> getCHRFromPackageIdService(int packageId) {
List<CombHoldingRule> combHoldingRuleList = combinationHRrepo.
repoCHRFromPackageId(packageId);
List<CHRfromPackageIdDTO> combDTO = new ArrayList<>();
for ( CombHoldingRule combHoldingRule : combHoldingRuleList) {
CHRfromPackageIdDTO temp = new CHRfromPackageIdDTO(combHoldingRule.getCombHoldingRuleId(),
combHoldingRule.getCombHoldingRuleName(),
combHoldingRule.getHoldingRuleList());
combDTO.add(temp);
}
return combDTO;
}
Also pls check HERE
Note: This is kind of work around I believe, really not sure , how to directly take value from a native query into a custom Pojo instead of an entity class. I really appreciate if anyone post the answer for that. I would accept that as the Answer.

Same Generic commit object getting saved from different instances

I am using Javers version 5.1.2, with jdk 11, in my application, where I am committing Generic Object T and saving into mongodb. The Generic commit objects are actually created from generic rest service, where user can pass any Json.
Every thing is going fine on single instance. Whenever any re commit is sent with same request, Javers commit.getChanges().isEmpty() method returns true.
Issues:
1) Whenever same request to sent to different instance, commit.getChanges().isEmpty() method returns false.
2) If I commit one request, and restart the instance and then again commit, commit.getChanges().isEmpty() again returns false. Instead of true.
As a result of above issue, new version is getting created if request goes to different new instance or instance is restarted.
Could you please let me know, how we can handle this issue.
I will extract code from the project and will create a sample running project and share.
Right now, I can share few classes, please see, if these help:
//---------------------Entitiy Class:
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ClientEntity<T> {
#Getter
#Setter
private String entityId;
#Getter
#Setter
private T commitObj;
#Getter
#Setter
private String authorName;
#Getter
#Setter
private boolean major;
#Getter
#Setter
private Map<String, String> commitProperties;
}
//--------DataIntegrator
#Service
public class DataIntegrator {
private final Javers javers;
private IVersionRepository versionDao;
private IdGenerator idGenerator;
#Inject
public DataIntegrator(Javers javers, IVersionRepository versionDao, IdGenerator idGenerator) {
this.javers = javers;
this.versionDao = versionDao;
this.idGenerator = idGenerator;
}
public <T> String commit(ClientEntity<T> clientObject) {
CommitEntity commitEntity = new CommitEntity();
commitEntity.setEntityId(clientObject.getEntityId());
commitEntity.setEntityObject(clientObject.getCommitObj());
Map<String, String> commitProperties = new HashMap<>();
commitProperties.putAll(clientObject.getCommitProperties());
commitProperties.put(commit_id_property_key, clientObject.getEntityId());
commitProperties.putAll(idGenerator.getEntityVersions(clientObject.getEntityId(), clientObject.isMajor()));
Commit commit = javers.commit(clientObject.getAuthorName(), commitEntity, commitProperties);
if (commit.getChanges().isEmpty()) {
return "No Changes Found";
}
versionDao.save(
new VersionHead(clientObject.getEntityId(), Long.parseLong(commitProperties.get(major_version_id_key)),
Long.parseLong(commitProperties.get(minor_version_id_key))));
return commit.getProperties().get(major_version_id_key) + ":"
+ commit.getProperties().get(minor_version_id_key);
}
}
1) commitObj is a Generic object, in ClientEntity, which holds Json coming from the Rest webService. The JSON can be any valid json. Can have nested structure also.
2) After calling javers.commit method, we are checking if it is existing entity or there is any change using commit.getChanges().isEmpty().
If same second request goes to same instance, it returns true for change, as expected
If same second request goes to different instance, under load balancer, it takes it as different request and commit.getChanges().isEmpty() returns false. Expected response should be true, as it is same version.
If after first request, I restart instance, and make a same request, it returns false, instead of true, which means, getChanges method taking the same request as same.

Add Two Conditions in JpaRepository

i am trying to do a POC - using JpaRepository filter out the data by adding two conditions.
I have written a code like below
public interface TemplateRepository extends JpaRepository<Template, Long> {
List<Template> findByTemplateNameContains(String templateName);//This is Working Fine
List<Template> findByTemplateNameAndActiveFlagContains(String templateName, String activeFlag);// My POC
}
templateName column is a VARCHAR2 and activeFlag is a Char in the Oracle Database. I am trying to filter the data with both templatename
and activeFlag.
I pass the input object in SoapUI app (POST) request.
{
"netting":"karu_test",
"activeFlag": "Y"
}
but I get the below error
"Parameter value [%Y%] did not match expected type [java.lang.Character (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [%Y%] did not match expected type [java.lang.Character (n/a)]"
I understand this error like, the ACTIVE_FLAG column is CHAR(1) so type mismatch happend. But how to achieve the same functionality ?
More over .. how to use multiple table joins and condition in JpaRepository
I changed the type of activeFlag to Char still i get the same error.
Template class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Table(name="TEMPLATE_DEF")
#Getter
#Setter
public class Template {
#Column(name="TEMPLATE_ID")
#Id
private String nettingTemplateId;
#Column(name="TEMPLATE_NAME")
private String templateName;
#Column(name="LAST_UPDATE")
private Date lastUpdate;
#Column(name="UPDATE_USER_ID")
private Integer updUsrId;
#Column(name="ACTIVE_FLAG")
private char activeFlag;
#Column(name="VERSION")
private Integer Version;
#Column(name="CREATION_DATE")
private Date creationDate;
#Column(name="CREATE_USER_ID")
private Integer createUsrId;
}
Please try the below JPA Query
List<Template> findByTemplateNameContainingAndActiveFlagContaining(String templateName, Character activeFlag);
Your Active flag is a char so no point in putting containing for activeFlag rather do a exact match, change method signature to
List<Template> findByTemplateNameContainsAndActiveFlag(String templateName, char activeFlag);// My POC
I have tested it it will match name with like and activeFlag based on value of it

#Query does not give desired result when native query is used

iam using spring data jpa in my project
package com.mf.acrs.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
#Data
#Entity(name= "mv_garage_asset_mapping")
public class GarageAssetMapping implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2535545189473989744L;
#Id
#Column(name="GARAGE_CODE")
private String garageCode;
#Column(name="GARAGE_NAME")
private String garageName;
#Column(name="GARAGE_ADDRESS")
private String garageAddress;
#Column(name="GARAGE_BRANCH")
private String garageBranch;
#Column(name="CONTRACT_NUMBER")
private String contractNumber;
}
this is my entity object
package com.mf.acrs.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.mf.acrs.model.GarageAssetMapping;
public interface GarageAssetMappingRepository extends JpaRepository<GarageAssetMapping, String> {
// #Query(name="select u.CONTRACT_NUMBER from mv_garage_asset_mapping u where u.GARAGE_CODE = ?1", nativeQuery = true) //**QUERY 1**
#Query("select u.contractNumber from mv_garage_asset_mapping u where u.garageCode = ?1") // **QUERY 2**
List<String> findByGarageCode(String garageCode);
}
this is my repository interface
when i use the QUERY 1 in my application the query fired by spring data jpa is
Hibernate: select garageasse0_.garage_code as garage_code1_2_, garageasse0_.contract_number as contract_number2_2_, garageasse0_.garage_address as garage_address3_2_, garageasse0_.garage_branch as garage_branch4_2_, garageasse0_.garage_name as garage_name5_2_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
but when i use QUERY 2 the query fired is
Hibernate: select garageasse0_.contract_number as col_0_0_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
QUERY 2 gives me desired result.
but my question is why spring data jpa fires a incorrect query in 1st case.
in QUERY 1 hibernate tries to pull all the data fields despite the fact i have explicitly written in query that i want to fetch only one field.
What mistake iam doing in this case?
The method defined in the controller which calls the method is below:
#PostMapping("/searchAssetsAjax")
#ResponseBody
public String searchAssetsAjax(#RequestBody SearchAssetData searchAssetData) throws IOException{
System.out.println("iam in the searchAssetsAjax "+searchAssetData);
System.out.println("iam in the searchAssetsAjax "+searchAssetData.toString());
// System.out.println("throwing exceptions" ); throw new IOException();
System.out.println("hitting the db "+searchAssetData.getGarageCode());
// List<String> contractNums = garageAssetMapRepository.findContractNumberByGarageCode(searchAssetData.getGarageCode());
List<String> contractNums = garageAssetMapRepository.findByGarageCode(searchAssetData.getGarageCode());
System.out.println("############contract num size is "+contractNums.size());
for(String contract: contractNums) {
System.out.println("contract nums are "+contract);
}
return "success";
}

Resources