Data Rest Repository Custom Annotated Query - spring

I'm working my way through the Spring Data-Rest guide and struggling writing a custom annotated query and not sure if it's even possible, here's the code:
CategoryRepository
package com.example.repositories
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.data.rest.core.annotation.RepositoryRestResource
import org.springframework.data.rest.core.annotation.RestResource
import com.example.entities.Category
import com.example.entities.InventoryDetail
#RepositoryRestResource(collectionResourceRel="categories", path="categories")
interface CategoryRepository extends JpaRepository<Category, Long> {
#RestResource(path="inventoryByCategory",rel="inventoryByCategory")
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
Page<InventoryDetail> queryByCategoryStartsWithIgnoreCase(#Param("name") String name, Pageable pageable)
}
The query above is the one that I'm struggling with, not sure how to properly do this. I searched for hours looking for a solution but could not find one.
Category Entity
package com.example.entities
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.OneToMany
import javax.persistence.Table
#Entity
#Table(name="categories")
class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
#Column
String name
#Column
String description
#OneToMany(targetEntity=Inventory.class, fetch=FetchType.LAZY)
#JoinColumn(name="category")
List<Inventory> inventory
}
Inventory Entity
package com.example.entities
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Index
import javax.persistence.Table
#Entity
#Table(name="inventory", indexes=[ #Index(columnList="category", unique=false) ])
class Inventory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
#Column
long category
#Column
String item
#Column
String description
#Column
long price
#Column
long onHand
}
InventoryDetail
package com.example.entities
import javax.persistence.Column;
class InventoryDetail {
long id
String item
String name
InventoryDetail(long id, String item, String name) {
this.id = id
this.item = item
this.name = name
}
}
If I want to select specific fields from both entities, do I need to have a custom DTO like the one above? Is it possible to just use a new map(...) instead? Either way, the query runs and I see it in the console, but in the HAL browser it gives me a 500 error, I'm sure I am overlooking something, but not sure what it is.
I appreciate your help in advance!
EDIT
Here is the output from the Hibernate query:
Hibernate: select count(category0_.id) as col_0_0_ from categories category0_ inner join inventory inventory1_ on category0_.id=inventory1_.category where upper(category0_.name) like upper(?+'%')
Hibernate: select inventory1_.id as col_0_0_, inventory1_.item as col_1_0_, category0_.name as col_2_0_ from categories category0_ inner join inventory inventory1_ on category0_.id=inventory1_.category where upper(category0_.name) like upper(?+'%') limit ?

After countless hours of testing, I decided to throw the code in a controller and access it via the EntityManager, and it worked. After getting it to work from the controller I realized that JPA/Hibernate is expecting an Entity and not an Object/DTO.
I was able to do this...
List<Object> list(String name) {
def qry = "select new map(i.id as id, i.item as item, c.name as category) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')"
List<Object> results = em.createQuery(qry).setParameter('name',name).getResultList()
return results
}

Your Query :
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
In your custom query no join mapping for Category and Inventory. Repace this line of code by following where Category and Inventory mapped by their id in join:
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where c.id=i.category upper(c.name) like upper(:name+'%')")
Note: by default on jpql join means inner join

I'm not sure if this is the cause of your 500 error, but I see something wrong with your query, particularly with how you concatenate the paramater name with the wildcard character %.
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
You must remove the plus sign and the single quotes.
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name%)")
See sample implementation here.

Related

How to persist object in db with existing primary key in Spring boot JPA?

Scenario:
I do have JSON object as Menus.
//suppose this as
Menus menus = new Menus();
menus.setId(61);
menus.setUrl("test_url");
menus.setName("menu name");
repository.save(menus);
Current case:
It is working fine if db has menu row with id 61 as a result object gets updated.
While row with id=61 does not exists in db then this menu object gets persisted but with new id. ie. a new row is created with new auto generated ID.
Expected:
If menu where id = 61 does not exists in db then menus should be inserted in db with id=61
package com.rasello.auth.entity;
import lombok.*;
import org.hibernate.annotations.Type;
import javax.persistence.*;
#Entity
#Data
#NoArgsConstructor
#Table(name = "menus")
#Getter
#Setter
public class Menus {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
private String name;
private String url;
}
Remove this line #GeneratedValue(strategy = GenerationType.TABLE) and try again. The #GeneratedValue does what the words say. Generates value for the ID and in case you do not need that then you should not use it.
You can read more about how to set up your Ids in this article https://www.baeldung.com/hibernate-identifiers

How to handle this Type MisMatch found List required Entity spring Boot JPA

I create a some operations in my controller class ,I want to store
the results what i get from the operation but when I store these list
of things it show me an error like Below
Type mismatch.
Required:
DepositMaterial!
Found:
List<Result>
Here is my controller class
#PatchMapping("/pendingValue")
fun pend(#ModelAttribute request:ReqFindPending):ResponseEntity<*>{
val existingWithdraw = depositRepository.findPendingByWithDrawId(request.withDrawId)
if (existingWithdraw != null){
val upPending = depositRepository.getPending(withDrawId = request.withDrawId)
depositRepository.save(upPending)
return ResponseEntity(ResMessage("Pending update successfull"),HttpStatus.OK)
}else{
return ResponseEntity(ResMessage(" id getting null value"),HttpStatus.NOT_ACCEPTABLE)
}
}
My repository
package com.nilmani.workload.repository
import com.nilmani.workload.entity.DepositMaterial
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.RequestParam
interface DepositRepository : JpaRepository<DepositMaterial, Long> {
#Query("SELECT wm.quantity ,dd.totalDeposit,wm.quantity -dd.totalDeposit AS pending FROM WithdrawMaterial wm INNER JOIN DepositMaterial dd ON wm.id = dd.withDrawId ")
fun getPending(#Param("withDrawId")withDrawId: Long?):List<Result>
}
Here is my result Model
data class Result(
val pending: Long,
val quantity: Long,
val totalDeposit: Long
)
DepositMaterial Entity Class
package com.nilmani.workload.entity
import com.nilmani.workload.enum.Material
import java.time.LocalDateTime
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
#Entity
data class DepositMaterial (
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
val id:Long=-1,
val withDrawId:Long=-1,
val totalDeposit:Long=-1,
val material:Int= Material.NONE.type,
val depositTime:LocalDateTime = LocalDateTime.now(),
val pending:Long = -1,
val isDeposited:Boolean=false,
)
What is the reason for this issue,I want to only return these three
things, and store the result subtraction result of totalDeposit and
quantity in pending column to update the table but , It give me error
to store the depositMaterial
You are returning a list of Results from getPending, not an individual DepositMaterial. But the save method requires a single object to save. Try only requesting one object from getPending by changing the return signature.
depositRepository.getPending
returns a list of Entities.
depositRepository.save(upPending)
takes a single Entity to save in the database.
Solution:
Either change your save(upPending) method to saveAll(upPending), or update your getPending repo method to return a unique Entity object.

JPA sorting query result after querying with Pageable

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.

#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";
}

Spring Boot repository save does not work (only shows a select)

I'm facing for hours with a strange proceeding in Spring Boot when try to save a mapped entity.
The entity class with a composite key that must all be set by the user is as follows:
package model
import javax.persistence.*
#Entity
#Table(name = 'MY_TABLE')
#IdClass(MyIdClass.class)
class MyClass implements Serializable{
#Id
#Column(name = "MY_COLUMN_1")
Long column1
#Id
#Column(name = "MY_COLUMN_2")
Long column2
#Id
#Column(name = "MY_COLUMN_3")
String column3
#Id
#Column(name = "MY_COLUMN_4")
Date date1
#Column(name = "MY_COLUMN_5")
Date date2
#Column(name = "MY_COLUMN_6")
BigDecimal column6
}
#Embeddable
class MyIdClass implements Serializable{
Long column1
Long column2
String column3
Date date1;
}
The corresponding repository is:
package repository
import org.springframework.data.repository.CrudRepository
interface MyRepository extends CrudRepository<MyClass, Long>{
}
My service is:
package service
import model.MyClass
import repository.MyRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
#Service
class MyService {
#Autowired
MyRepository repository
void save(MyClass myClass) {
repository.save(myClass)
}
}
My controller mounts a MyClass object with all data set, including the composite key. When it calls the service save method the object is not inserted in the database. I saw the logs and checked that there is a SELECT in MY_TABLE instead of INSERT. I tried not to inform the composite key in the object and then the save method did an INSERT with error due to null values in the primary key.
I really don't understand why the insertion is not done when the composite key has values. How can I solve it?
I've already tried with #Transactional in service class and didn't work. I didn't do any Transaction configuration in the project since Spring Boot delivers it as default.
Thanks.
It seems you are using MyIdClass as the Id for MyClass. So, the Repository should be:
interface MyRepository extends CrudRepository<MyClass, MyIdClass>{
}
Hope this help.
I take your code sample and tried it on a sample Spring Boot project, where I was able to save to H2 DB (In memory) with #Embeddable & #EmbeddedId annotations. If you would like to verify, you can clone the GitHub repo and run the BootJpaApplication.java as a Java Application.
After execution access the H2 console with the below link from local where table details can be verified.
http://localhost:8080/h2-console
https://github.com/sujittripathy/springboot-sample.git
Hope the detail helps:)

Resources