Variables in Spring Data JPA native query - java

Using Spring Dat JPA, I need to query my database and return a range of OrderEntitys based on a startAmt and a endAmt of amounts. I'm not sure if I should map these two variables to entity OrderEntity, as fields in some type of separate class/entity/model, or simply declare them in my native query. Perhaps I should be using a service that implements EntityManager.createNativeQuery()?
Would like to do something like :
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN startAmt AND endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);
}
If I were to use EntityManager.createNativeQuery() in a service, perhaps something like below :
#Service
public class OrderRangeService {
#Autowired
EntityManager entityManager;
public List<OrderEntity> findAmountsBetween() {
List<OrderEntity> amountsBetween = entityManager.createNativeQuery("SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND 2?;")
.setParameter(1, "startAmt")
.setParameter(2, "endAmt")
.getResultList();
return amountsBetween;
}
}

You can achieve this with Spring Data JPA without defining a native query.
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
List<OrderEntity> findByAmountBetween(int startAmt, int endAmt);
}
If you want to use the native query change it to
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
You can invoke the query in a service by doing
#Service
public class OrderRangeService {
#Autowired
OrderRangeRepository orderRangeRepository ;
public List<OrderEntity> findAmountsBetween(int startAmt, int endAmt) {
List<OrderEntity> amountsBetween = orderRangeRepository.findByAmountBetween(startAmt, endAmt);
return amountsBetween;
}
}
Finally, from your controller, you should autowire the OrderRangeService and invoke the findAmountsBetween service method
#Autowired
OrderRangeService orderRangeService;
#GetMapping("/amountsFromAndTo")
#ResponseBody
public String getAmounts(#RequestParam int startAmt, #RequestParam int endAmt) {
List<OrderEntity> orderEntityL = orderRangeService.findAmountsBetween(startAmt, endAmt);
return orderEntityL.toString();
}

1. Named Parameters
Each parameter annotated with #Param must have a value string matching
the corresponding JPQL or SQL query parameter name. A query with named
parameters is easier to read and is less error-prone in case the query
needs to be refactored.
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
}
2. Indexed Query Parameters
Spring Data will pass method parameters to the query in the same order
they appear in the method declaration
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND ?2;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);

Related

Returned object from Spring Data Jpa query has null values

I'm trying to get object of custom type from JPA Repository
VisitRepository.java
#Repository
public interface VisitRepository extends JpaRepository<Visit, Long>, JpaSpecificationExecutor<Visit> {
#Query(value = "select client_id , count(*) from visit where (DATE(jhi_date) between :startDate and :endDate) group by client_id",nativeQuery = true)
List<IIntegerReportData> findByDate(#Param("startDate") String startDate, #Param("endDate") String endDate);
IIntegerReportData.java
package com.mycompany.hiptest.repository;
public interface IIntegerReportData {
Long getId();
Integer getValue();
}
ClientRating.java
public List<ClientsRatingDTO> findAllSorted(String startDate, String endDate, Long fieldNum) {
List<IIntegerReportData> visitReport = visitRepository.findByDate(startDate, endDate);
log.debug("visitReport:" + visitReport.size());
for (IIntegerReportData visit : visitReport
) {
log.debug("value: " + visit.getValue());
}
In debug i get visitReport.size() = 27 (that is correct records count), but
visit.getValue() is NULL for each rows, although there are not null values in this field for each rows.
What's wrong?
You could use NativeQuery Annotation:
Have a look at:
https://www.baeldung.com/spring-data-jpa-query
When returning a custom object from a native query, the result column names must match the names of the custom interface, otherwise they'll just have null values. E.g.:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query(value = "SELECT "\"id\" FROM \"my_entity\"", nativeQuery = true)
List<IdNative> findAllIdNative();
interface IdNative {
Long getEntityId();
}
}
Here, getEntityId() will always return null because the result table of the query has no entityId column. To fix, either change the query to match the method:
SELECT "id" AS "entityId" FROM "my_entity"
Or, change the interface method to match the column name:
Long getId();

Spring boot: Optional parameter query in Query method

I am new to Spring boot and hibernate. Here I am trying run a search based optional parameter query Where i can search by name, country etc. If I kept this field null then query should all list. But the problem is my method is returning all data ignoring my search parameter. my model class look like
#Entity(name="MLFM_ORDER_OWNER")
public class ModelOrderOwner {
#Id #GenericGenerator(name = "custom_sequence", strategy =
"com.biziitech.mlfm.IdGenerator")
#GeneratedValue(generator = "custom_sequence")
#Column(name="ORDER_OWNER_ID")
private Long orderOwnerId;
#Column(name="OWNER_NAME")
private String ownerName;
#OneToOne
#JoinColumn(name="BUSINESS_TYPE_ID")
private ModelBusinessType businessTypeId;
#Column(name="SHORT_CODE")
private String shortCode;
#ManyToOne
#JoinColumn(name="OWNER_COUNTRY")
private ModelCountry ownerCountry;
// getter setter..
My Repository interface looks like
public interface OrderOwnerRepository extends
JpaRepository<ModelOrderOwner,Long>{
#Query("select a from MLFM_ORDER_OWNER a where a.businessTypeId.typeId=coalsec(:typeId,a.businessTypeId.typeId) and a.ownerCountry.countryId=coalsec(:countryId,a.ownerCountry.countryId) and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(#Param("typeId")Long typeId,#Param("countryId")Long countryId,#Param("name")String name,#Param("code")String code);
}
And here is my method in controller
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,#RequestParam("owner_name") String name,#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,#RequestParam("countryName") Long countryId,
#RequestParam("businessType") Long typeId
) {
model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code));
return "data_list";
}
Can Any one help me in this regard? please?
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below:
In my case my controller was like:
#RestController
#RequestMapping("/order")
public class OrderController {
private final IOrderService service;
public OrderController(IOrderService service) {
this.service = service;
}
#RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
#RequestHeader Map<String, String> requestHeaders,
#RequestParam(required=false) Long id,
#RequestParam(required=false) Long flags,
#RequestParam(required=true) Long offset,
#RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}
As you can see, I have Username as #PathVariable and length and offset which are my required parameters, but I accept id and flags for filtering search result, so they are my optional parameters and are not necessary for calling the REST service.
Now in my repository layer I have just created my #Query as below:
#Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);
And that's it, you can see that I checked my optional arguments with (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag) and I think it needs to be emphasized that I checked my argument with is null condition not my columns data, so if client send Id and flags parameters for me I will filter the Result with them otherwise I just query with username which was my #PathVariable.
Don't know how but below code is working for me:
#Query("select a from MLFM_ORDER_OWNER a
where a.businessTypeId.typeId=COALESCE(:typeId,a.businessTypeId.typeId)
and a.ownerCountry.countryId=COALESCE(:countryId,a.ownerCountry.countryId)
and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(
#Param("typeId")Long typeId,
#Param("countryId")Long countryId,
#Param("name")String name,
#Param("code")String code);
and in my controller class:
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,
#RequestParam("owner_name") String name,
#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,
#RequestParam("countryName") Long countryId,
#RequestParam(value = "active", required = false) String active, #RequestParam("businessType") Long typeId) {
if(typeId==0)
typeId=null;
if(countryId==0)
countryId=null; model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code, status));
return "data_list";
}
JPQL doesn't support optional parameters.
There is no easy way of doing this in JPQL. You will have to write multiple WHERE clauses with OR operator.
Refer these answers to similar questions: Answer 1 & Answer 2
PS: You might want to look into Query by Example for your use case. It supports handling of null parameters.
Use JpaSpecificationExecutor //import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Step 1: Implement JpaSpecificationExecutor in your JPA Repository
Ex:
public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder
Ex:
public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));
if (gameId != null) {
predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)
ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll

AopInvocationException: in #Autowieired in JUnit

In my java spring mvc application, i am using the below code:
The repository:
#Repository
public interface TransactionRepository extends JpaRepository<Transactions ,Long >{
#Query(value = "SELECT sum( value) FROM Transactions inner join Customer on Transactions.customer_id=Customer.id where merchant_id= ?1 and age_class= ?2 ", nativeQuery=true)
public double getOverAllValue(String merchantID,String ageGroup);
}
and then i made a serivce which works based on this #Repository:
#Service
public class IncomeDataService {
#Autowired
TransactionRepository transactionRepo;
public ArrayList<Double> dataCollector(int merchantID){
ArrayList<Double> data= new ArrayList<Double>();
for(int i=0;i<10;i++)
data.add( transactionRepo.getOverAllValue(Integer.toString(merchantID),Integer.toString(i)));
return data;
}
and then i am trying to test the written service using the junit:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SEBApplication.class)
public class IncomeDataServiceTest {
#Autowired
IncomeDataService incomeDataService;
#Test
public void testDataCollector() {
System.out.println(incomeDataService.dataCollector(1));
}
}
But, when i run my test it complains with:
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract double ee.seb.domain.repository.TransactionRepository.getOverAllValue(java.lang.String,java.lang.String)
Update your Query's return type from primitive data type double to Double Because your query can return null value which will be accepted by wrapper class and not by primitive, so your final Query will be like this:
#Query(value = "SELECT sum( value) FROM Transactions inner join Customer on Transactions.customer_id=Customer.id where merchant_id= ?1 and age_class= ?2 ", nativeQuery=true)
public Double getOverAllValue(String merchantID,String ageGroup);

Call database function with Spring

I need to query a function of the database, by SQL, something so simple as
#Query("SELECT random()")
how to implement a domain/repository or service method that do it?
There are various ways how to bind stored procedures with Spring Data JPA (using JPA 2.1).
Simplest example:
#Procedure("random")
Integer randomStoredProcedure();
In case, you don't have any Entity/DTO (non #Entity) objects and wondering how to map returned table from the function into your custom object.
#Repository
public class YourRepository {
private final EntityManager entityManager;
public(EntityManager entityManager) {
this.entityManager = entityManager;
}
public List<YourObject> findObjects() {
List<Object[]> columns = entityManager
.createStoredProcedureQuery("your_sql_func")
.getResultList();
return columns.stream().map(this::toYourObject).collect(Collectors.toList());
}
private YourObject toYourObject(Object[] columns) {
return new YourObject((int)columns[0], (String)columns[1]);
}
}
class YourObject {
private int number;
private String name;
public YourObject(int number, String name) {
this.number = number;
this.name = name;
}
// getters/setters or use Lombok
}
If you have any parameters in your function then your query should look like this,
List<Object[]> columns = entityManager
.createStoredProcedureQuery("your_sql_func")
.registerStoredProcedureParameter("param1Name", String.class, ParameterMode.IN)
.setParameter("param1Name", param1Value)
.getResultList();
You could use native query as well,
List<Object[]> columns = entityManager
.createNativeQuery("SELECT * FROM your_sql_func(:param1Name)")
.setParameter("param1Name", param1Value)
.getResultList();

JPA NAMED Query issue

Getting below database excpetion, help required
service() for servlet catalogservice threw exception: java.lang.IllegalArgumentException: Named query not found: SELECT OMX_PLAN_ID, PLAN_ID,DECODE(plan_id,0,ser_input_total_amount,first_payment) first_paymenmt From (SELECT OMX_PLAN_ID, PLAN_ID,(SELECT DECODE(fraction,0,fixed_payment_amount, (( fraction/100) * :useinput_total_amount)) From TFN.VW_OMX_PAYMENT_PLAN_DETAILS i WHERE o.OMX_PLAN_ID=i.OMX_PLAN_ID AND i.OMX_PLAN_ID=:omxPlanId AND i.PAYMENT_ID=1) first_payment FM TFN.VW_OMX_PAYMENT_PLANS o ) WHERE OMX_PLAN_ID=:omxPlanId ORDER by 1
at org.hibernate.ejb.AbstractEntityManagerImpl.createNamedQuery(AbstractEntityManagerImpl.java:704) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Fin
DAO Method
public TermPayment findFirstPaymentByTotalAndPlanId(int planId, double totalAmount) {
TypedQuery<TermPayment> query = entityManager.createNamedQuery("SELECT OMX_PLAN_ID, PLAN_ID,DECODE(plan_id,0,:user_input_total_amount,first_payment) first_paymenmt From (SELECT OMX_PLAN_ID, PLAN_ID,(SELECT DECODE(fraction,0,fixed_payment_amount, (( fraction/100) * :user_input_total_amount)) From TFN.VW_OMX_PAYMENT_PLAN_DETAILS i WHERE o.OMX_PLAN_ID=i.OMX_PLAN_ID AND i.OMX_PLAN_ID=:omxPlanId AND i.PAYMENT_ID=1) first_payment FROM TFN.VW_OMX_PAYMENT_PLANS o ) WHERE OMX_PLAN_ID=:omxPlanId ORDER by 1", TermPayment.class);
query.setParameter("omxPlanId", planId);
query.setParameter("user_input_total_amount", totalAmount);
return query.getSingleResult();
}
Return class
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class TermPayment {
#Id
#Column(name = "OMX_PLAN_ID")
Integer omxPlanId;
#Column(name = "PLAN_ID")
Integer planId;
#Column(name = "FIRST_PAYMENT")
Double firstPayment;
public Integer getOmxPlanId() {
return omxPlanId;
}
public void setOmxPlanId(Integer omxPlanId) {
this.omxPlanId = omxPlanId;
}
public Integer getPlanId() {
return planId;
}
public void setPlanId(Integer planId) {
this.planId = planId;
}
public Double getFirstPayment() {
return firstPayment;
}
public void setFirstPayment(Double firstPayment) {
this.firstPayment = firstPayment;
}
}
From the java.persistence.EntityManager javadoc:
/**
* Create an instance of <code>Query</code> for executing a named query
* (in the Java Persistence query language or in native SQL).
* #param name the name of a query defined in metadata
* #return the new query instance
* #throws IllegalArgumentException if a query has not been
* defined with the given name or if the query string is
* found to be invalid
*/
public Query createNamedQuery(String name);
so you should create your named query and only refer to it using createNamedQuery.
If you want to create a query from a string, you can use the createQuery(String query, Class type) method.
You can replace the method used in your DAO:
entityManager.createNamedQuery(...)
For this one:
entityManager.createQuery("select OMX_PLAN_ID, PLAN_ID ...", TermPayment.class)
Alternatively, you can create a NamedQuery adding a NamedQuery annotation in your Entity class or using xml. Afterthat, you could use the NamedQuery passing the NamedQuery name to the createNamedQuery() method.
#NamedQuery(name="MyQuery", query="select OMX_PLAN_ID, PLAN_ID ...")
entityManager.createNamedQuery(MyQuery, TermPayment.class);
You are trying to use NamedQuery api for creating dynamic queries which is wrong.
From doc
createNamedQuery method is used to create static queries, or queries that are defined in metadata by using the javax.persistence.NamedQuery annotation. The name element of #NamedQuery specifies the name of the query that will be used with the createNamedQuery method. The query element of #NamedQuery is the query:
#NamedQuery(
name="findAllCustomersWithName",
query="SELECT c FROM Customer c WHERE c.name LIKE :custName"
)
Here’s an example of createNamedQuery, which uses the #NamedQuery:
#PersistenceContext
public EntityManager em;
...
customers = em.createNamedQuery("findAllCustomersWithName")
.setParameter("custName", "Smith")
.getResultList();
You need to do something like,
entityManager.createQuery(
"SELECT c FROM Customer c WHERE c.name LIKE :custName")
.setParameter("custName", name)

Categories