Delete Query in Spring Boot with Optional Parameter - java

I am trying to implement Delete query in Spring Boot, however the parameters are optional. How do I write JPA query for same.
Here is how I have implemented for mandate Request Params:
#Transactional
#Repository
public interface ABCRepo extends CrudRepository<ABC, Long>{
public List<ABC> findByABCIdAndStartYrAndStartMonth(String pilotId, int startYr, int startMonth);
public long deleteABCByABCId(String pilotId);
}
Controller.class
#RequestMapping(value="", method= RequestMethod.DELETE)
public Response delete(#PathVariable("abc-id")String pilotId)
{
LOGGER.info("Trying to delete pilot bank using abc id : "+ abcId);
long deletedRecords=abcBiz.deleteABCByABCId(abcId);
if(deletedRecords==0)
{
throw new PilotNotFoundException("Entity not found "+abcId);
}
return Response.status(Response.Status.NO_CONTENT).entity(deletedRecords).build();
}
My new Controller.class after adding optional params
#RequestMapping(value="", method= RequestMethod.DELETE)
public Response delete(#PathVariable("abc-id")String abcId, #RequestParam(name = "bid-yr", required = false)
int bidYr, #RequestParam(name = "bid-month", required = false) int bidMonth)
{
LOGGER.info("Trying to delete pilot bank using abc id : "+ abcId);
long deletedRecords=abcBiz.deleteABCByABCId(a);bcId
if(deletedRecords==0)
{
throw new PilotNotFoundException("Entity not found "+abcId);
}
return Response.status(Response.Status.NO_CONTENT).entity(deletedRecords).build();
}
How do I handle this at JPA?

For optional parameters, you need to write the query. Something like below:
#Modifying
#Query("DELETE FROM ABC WHERE abcId=:pilotId AND (:otherOptionalParam IS NULL OR otherField=:otherOptionalParam)")
public long deleteABCByABCId(String pilotId, String otherOptionalParam);
If you want to create a complex query, with lot of optional parameters, then you can create custom repository, and develop native queries. Here I have already answered to how we can create custom repositories in Spring data JPA - https://stackoverflow.com/a/68721142/3709922

On Top of what Jignesh has said, don't forget to mark your parameters with Param annotation. Also jpa modification will return int/Integer but not long so I had to change return type too.
#Modifying
#Query("DELETE FROM ABC WHERE abcId=:pilotId AND (:otherOptionalParam IS NULL OR
otherField=:otherOptionalParam)")
public long deleteABCByABCId(#Param("pilotId")String pilotId, #Param("otherOptionalParam")String
otherOptionalParam);

Related

Hibernate GetbyID : java.lang.NoSuchMethodException Entity error

Hibernate and trying to build simple feature where we can search Product by Id. Hibernate has inbuit function to search an entity by its id. I tried the same but i am getting "java.lang.NoSuchMethodException" .
MyController.java :
#GetMapping(value = "/getProducts/{id}" , produces ="application/json")
public ResponseEntity<Product> display(#PathVariable int id) {
Product products = productServiceImp.getAllProducts(id);
return ResponseEntity.ok(products);
MyProductServiceImp:
#Override
public Product getAllProducts(int product_id ) {
return productRepository.getById(product_id );
}
MyProductRepository:
#Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Schema of Product table : (product_id, desciption,display_name, qty, amount)
When i try to invoke API by postman
curl --location --request GET 'http://localhost:8080/admin/getProducts/1.
I see it is Caused by: java.lang.NoSuchMethodException: com.Project.OrderProcessing.OrderProcessing.Entity.Product$HibernateProxy$zAdAYVvM.<init>().I am unable to understand reason behind it
Try findById since getById is deprecated. Untested, but something like:
MyProductServiceImp:
#Override
public Optional<Product> findById(Integer productId) {
return productRepository.findById(productId);
}
Product.java
#Entity //make sure this is present
#Getter //from Lombok
#Setter //from Lombok
public class Product {
#Id //need this
#GeneratedValue //need this to auto-generate
private Integer id;
private String description;
//the rest: displayName, quantity, amount...
}
Your #Repository interface looks fine. There are different variations for your controller depending on what you need to do. But for now, just try calling your service method so you know you get the result back from the DB and work from there.
Camel-case your variables in general for consistency. Then you can use the Spring conventions in interfaces for repositories so your method could look like findAllByDisplayName() instead of findAllByDisplay_Name() and Spring will handle the query for you.
Also note that presumably, you're not getting all products with one product ID, right? So it should just be called getProduct or findProduct or getProductById or findProductById.
MyControllerClass:
#RequestMapping("/admin")
#RestController
public class ProductController {
#GetMapping(value = "/getProducts/{id}" , produces ="application/json")
public Optional<Product> display(#PathVariable int id) {
Optional<Product> products = productServiceImp.getProductDetailsbyID(id);
return products;
}
}
MyProductServiceImp :
#Override
public Optional<Product> getProductDetailsbyID(int product_id ) {
Optional<Product> prodresult=productRepository.findById(product_id);
return prodresult;
}
I have used FindbyID in place of GetById and it worked !!

How to allow some User only access his own data in endpoint in Spring Boot / Spring Security with pagination?

I have a question related to the limiting of the products list to specific User in my application. Ive got an API: "/api/v1/{userId}/products" and I want to use pagination in my UserRestController which I have already used in AdminRestController:
#GetMapping
public Response<Page<Product>> getProductPage(#PageableDefault(sort = "id") Pageable pageable) {
return Response.ok(productService.findAll(pageable));
}
I have read some threads and find some solutions with "#PreAuthorize("#userId == authentication.principal.id")". Now, I want to implement pagination in my endpoint in UserRestController which should return only the products list related to the specific User (not the list of all products). I have tried to use the following:
#GetMapping("/api/v1/{userId}/products")
#PreAuthorize("#userId == authentication.principal.id")
public Response<Page<Product>> getProductPage(#PageableDefault(sort = "id") Pageable pageable) {
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return Response.ok(productService.findAll(pageable));
}
But I have got the access problem, could you help me to figure out?
Thanks in advance!
It is already implemented into Spring-Secutiry and Spring-Data.
In config, you need to add a #Bean to provide your principal into the queriing :
#Configuration
public class Conf{
// `principal` provider for the Spring-Data JPQL requests
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}
After that you'll be abble to write things like that :
#RepositoryRestResource(path = "datas", exported = true)
public interface DataRepository extends PagingAndSortingRepository<Data, Long> {
#Override
#Query(value = "Select d From Data d Where d.ownerId = ?#{principal?.username}")
Page<Data> findAll(Pageable pageable);
}
Also, read the official doc : https://docs.spring.io/spring-security/reference/features/integrations/data.html

Using a Hibernate filter with Spring Boot JPA

I have found the need to limit the size of a child collection by a property in the child class.
I have the following after following this guide:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="date" ) )
public class SystemNode implements Serializable {
#Getter
#Setter
#Builder.Default
// "startTime" is a property in HealthHistory
#Filter(name = "dateFilter", condition = "startTime >= :fromDate")
#OneToMany(mappedBy = "system", targetEntity = HealthHistory.class, fetch = FetchType.LAZY)
private Set<HealthHistory> healthHistory = new HashSet<HealthHistory>();
public void addHealthHistory(HealthHistory health) {
this.healthHistory.add(health);
health.setSystem(this);
}
}
However, I don't really understand how to toggle this filter when using Spring Data JPA. I am fetching my parent entity like this:
public SystemNode getSystem(UUID uuid) {
return systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
}
And this method in turn calls the Spring supported repository interface:
public interface SystemRepository extends CrudRepository<SystemNode, UUID> {
Optional<SystemNode> findByUuid(UUID uuid);
}
How can I make this filter play nicely together with Spring? I would like to activate it programatically when I need it, not globally. There are scenarios where it would be viable to disregard the filter.
I am using Spring Boot 1.3.5.RELEASE, I cannot update this at the moment.
Update and solution
I tried the following as suggested to me in the comments above.
#Autowired
private EntityManager entityManager;
public SystemNode getSystemWithHistoryFrom(UUID uuid) {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("dateFilter");
filter.setParameter("fromDate", new DateTime().minusHours(4).toDate());
SystemNode systemNode = systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
session.disableFilter("dateFilter");
return systemNode;
}
I also had the wrong type in the FilterDef annotation:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="timestamp" ) )
I changed from date to timestamp.
This returns the correct number of objects, verified against the database.
Thank you!

Update single field using spring data jpa

I'm using spring-data's repositories - very convenient thing but I faced an issue. I easily can update whole entity but I believe it's pointless when I need to update only a single field:
#Entity
#Table(schema = "processors", name = "ear_attachment")
public class EARAttachment {
private Long id;
private String originalName;
private String uniqueName;//yyyy-mm-dd-GUID-originalName
private long size;
private EARAttachmentStatus status;
to update I just call method save. In log I see the followwing:
batching 1 statements: 1: update processors.ear_attachment set message_id=100,
original_name='40022530424.dat',
size=506,
status=2,
unique_name='2014-12-16-8cf74a74-e7f3-40d8-a1fb-393c2a806847-40022530424.dat'
where id=1
I would like to see some thing like this:
batching 1 statements: 1: update processors.ear_attachment set status=2 where id=1
Spring's repositories have a lot of facilities to select something using name conventions, maybe there is something similar for update like updateForStatus(int status);
You can try something like this on your repository interface:
#Modifying
#Query("update EARAttachment ear set ear.status = ?1 where ear.id = ?2")
int setStatusForEARAttachment(Integer status, Long id);
You can also use named params, like this:
#Modifying
#Query("update EARAttachment ear set ear.status = :status where ear.id = :id")
int setStatusForEARAttachment(#Param("status") Integer status, #Param("id") Long id);
The int return value is the number of rows that where updated. You may also use void return.
See more in reference documentation.
Hibernate offers the #DynamicUpdate annotation. All we need to do is to add this annotation at the entity level:
#Entity(name = "EARAttachment ")
#Table(name = "EARAttachment ")
#DynamicUpdate
public class EARAttachment {
//Code omitted for brevity
}
Now, when you use EARAttachment.setStatus(value) and executing "CrudRepository" save(S entity), it will update only the particular field. e.g. the following UPDATE statement is executed:
UPDATE EARAttachment
SET status = 12,
WHERE id = 1
You can update use databind to map #PathVariable T entity and #RequestBody Map body. And them update body -> entity.
public static void applyChanges(Object entity, Map<String, Object> map, String[] ignoreFields) {
map.forEach((key, value) -> {
if(!Arrays.asList(ignoreFields).contains(key)) {
try {
Method getMethod = entity.getClass().getMethod(getMethodNameByPrefix("get", key));
Method setMethod = entity.getClass().getMethod(getMethodNameByPrefix("set", key), getMethod.getReturnType());
setMethod.invoke(entity, value);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
}

Filtering database rows with spring-data-jpa and spring-mvc

I have a spring-mvc project that is using spring-data-jpa for data access. I have a domain object called Travel which I want to allow the end-user to apply a number of filters to it.
For that, I've implemented the following controller:
#Autowired
private TravelRepository travelRep;
#RequestMapping("/search")
public ModelAndView search(
#RequestParam(required= false, defaultValue="") String lastName,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
Page<Travel> travels = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
mav.addObject("page", page);
mav.addObject("lastName", lastName);
return mav;
}
This works fine: The user has a form with a lastName input box which can be used to filter the Travels.
Beyond lastName, my Travel domain object has a lot more attributes by which I'd like to filter. I think that if these attributes were all strings then I could add them as #RequestParams and add a spring-data-jpa method to query by these. For instance I'd add a method findByLastNameLikeAndFirstNameLikeAndShipNameLike.
However, I don't know how should I do it when I need to filter for foreign keys. So my Travel has a period attribute that is a foreign key to the Period domain object, which I need to have it as a dropdown for the user to select the Period.
What I want to do is when the period is null I want to retrieve all travels filtered by the lastName and when the period is not null I want to retrieve all travels for this period filtered by the lastName.
I know that this can be done if I implement two methods in my repository and use an if to my controller:
public ModelAndView search(
#RequestParam(required= false, defaultValue="") String lastName,
#RequestParam(required= false, defaultValue=null) Period period,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
Page travels = null;
if(period==null) {
travels = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
} else {
travels = travelRep.findByPeriodAndLastNameLike(period,"%"+lastName+"%", pageable);
}
mav.addObject("page", page);
mav.addObject("period", period);
mav.addObject("lastName", lastName);
return mav;
}
Is there a way to do this without using the if ? My Travel has not only the period but also other attributes that need to be filtered using dropdowns !! As you can understand, the complexity would be exponentially increased when I need to use more dropdowns because all the combinations'd need to be considered :(
Update 03/12/13: Continuing from M. Deinum's excelent answer, and after actually implementing it, I'd like to provide some comments for completeness of the question/asnwer:
Instead of implementing JpaSpecificationExecutor you should implement JpaSpecificationExecutor<Travel> to avoid type check warnings.
Please take a look at kostja's excellent answer to this question
Really dynamic JPA CriteriaBuilder
since you will need to implement this if you want to have correct filters.
The best documentation I was able to find for the Criteria API was http://www.ibm.com/developerworks/library/j-typesafejpa/. This is a rather long read but I totally recommend it - after reading it most of my questions for Root and CriteriaBuilder were answered :)
Reusing the Travel object was not possible because it contained various other objects (who also contained other objects) which I needed to search for using Like - instead I used a TravelSearch object that contained the fields I needed to search for.
Update 10/05/15: As per #priyank's request, here's how I implemented the TravelSearch object:
public class TravelSearch {
private String lastName;
private School school;
private Period period;
private String companyName;
private TravelTypeEnum travelType;
private TravelStatusEnum travelStatus;
// Setters + Getters
}
This object was used by TravelSpecification (most of the code is domain specific but I'm leaving it there as an example):
public class TravelSpecification implements Specification<Travel> {
private TravelSearch criteria;
public TravelSpecification(TravelSearch ts) {
criteria= ts;
}
#Override
public Predicate toPredicate(Root<Travel> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Join<Travel, Candidacy> o = root.join(Travel_.candidacy);
Path<Candidacy> candidacy = root.get(Travel_.candidacy);
Path<Student> student = candidacy.get(Candidacy_.student);
Path<String> lastName = student.get(Student_.lastName);
Path<School> school = student.get(Student_.school);
Path<Period> period = candidacy.get(Candidacy_.period);
Path<TravelStatusEnum> travelStatus = root.get(Travel_.travelStatus);
Path<TravelTypeEnum> travelType = root.get(Travel_.travelType);
Path<Company> company = root.get(Travel_.company);
Path<String> companyName = company.get(Company_.name);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getSchool()!=null) {
predicates.add(cb.equal(school, criteria.getSchool()));
}
if(criteria.getCompanyName()!=null) {
predicates.add(cb.like(companyName, "%"+criteria.getCompanyName()+"%"));
}
if(criteria.getPeriod()!=null) {
predicates.add(cb.equal(period, criteria.getPeriod()));
}
if(criteria.getTravelStatus()!=null) {
predicates.add(cb.equal(travelStatus, criteria.getTravelStatus()));
}
if(criteria.getTravelType()!=null) {
predicates.add(cb.equal(travelType, criteria.getTravelType()));
}
if(criteria.getLastName()!=null ) {
predicates.add(cb.like(lastName, "%"+criteria.getLastName()+"%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Finally, here's my search method:
#RequestMapping("/search")
public ModelAndView search(
#ModelAttribute TravelSearch travelSearch,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
TravelSpecification tspec = new TravelSpecification(travelSearch);
Page<Travel> travels = travelRep.findAll(tspec, pageable);
PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
mav.addObject(travelSearch);
mav.addObject("page", page);
mav.addObject("schools", schoolRep.findAll() );
mav.addObject("periods", periodRep.findAll() );
mav.addObject("travelTypes", TravelTypeEnum.values());
mav.addObject("travelStatuses", TravelStatusEnum.values());
return mav;
}
Hope I helped!
For starters you should stop using #RequestParam and put all your search fields in an object (maybe reuse the Travel object for that). Then you have 2 options which you could use to dynamically build a query
Use the JpaSpecificationExecutor and write a Specification
Use the QueryDslPredicateExecutor and use QueryDSL to write a predicate.
Using JpaSpecificationExecutor
First add the JpaSpecificationExecutor to your TravelRepository this will give you a findAll(Specification) method and you can remove your custom finder methods.
public interface TravelRepository extends JpaRepository<Travel, Long>, JpaSpecificationExecutor<Travel> {}
Then you can create a method in your repository which uses a Specification which basically builds the query. See the Spring Data JPA documentation for this.
The only thing you need to do is create a class which implements Specification and which builds the query based on the fields which are available. The query is build using the JPA Criteria API link.
public class TravelSpecification implements Specification<Travel> {
private final Travel criteria;
public TravelSpecification(Travel criteria) {
this.criteria=criteria;
}
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// create query/predicate here.
}
}
And finally you need to modify your controller to use the new findAll method (I took the liberty to clean it up a little).
#RequestMapping("/search")
public String search(#ModelAttribute Travel search, Pageable pageable, Model model) {
Specification<Travel> spec = new TravelSpecification(search);
Page<Travel> travels = travelRep.findAll(spec, pageable);
model.addObject("page", new PageWrapper(travels, "/search"));
return "travels/list";
}
Using QueryDslPredicateExecutor
First add the QueryDslPredicateExecutor to your TravelRepository this will give you a findAll(Predicate) method and you can remove your custom finder methods.
public interface TravelRepository extends JpaRepository<Travel, Long>, QueryDslPredicateExecutor<Travel> {}
Next you would implement a service method which would use the Travel object to build a predicate using QueryDSL.
#Service
#Transactional
public class TravelService {
private final TravelRepository travels;
public TravelService(TravelRepository travels) {
this.travels=travels;
}
public Iterable<Travel> search(Travel criteria) {
BooleanExpression predicate = QTravel.travel...
return travels.findAll(predicate);
}
}
See also this bog post.

Categories

Resources