Hibernate validation no validation - java

Hi i am following an example I found
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/
The problem is that no errors are found in my profile that I post. I should be. Why can this happend?
#Test
#Ignore
public void anotherTest() {
Profile profile = ProfileUtil.getProfile();
profile.setEmail("user#mail.com");
profile.setSex("dafjsgkkdsfa");
BindingResult bindingResult = new BeanPropertyBindingResult(profile, "profile");
userController.postUser(new ModelMap(), profile, bindingResult);
if (bindingResult.hasErrors()) {
System.out.println("errors");
}
assertTrue(bindingResult.hasErrors());
profileService.deleteProfile(profile);
}
#RequestMapping(value = "/", method = RequestMethod.POST)
public View postUser(ModelMap data, #Valid Profile profile, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println("No errors");
return dummyDataView;
}
data.put(DummyDataView.DATA_TO_SEND, "users/user-1.json");
profileService.save(profile);
return dummyDataView;
}
Edit:
This is the Profile. I am testing the sex now so I guess thats what is important.
package no.tine.web.tinetips.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import no.tine.web.tinetips.util.CommonRegularExpressions;
import org.hibernate.validator.constraints.NotBlank;
#Entity
public class Profile {
#Id
#GeneratedValue
private Long id;
#NotNull(message = "profile.email.null")
#NotBlank(message = "profile.email.blank")
#Size(max = 60, message = "profile.email.maxlength")
#Pattern(regexp = CommonRegularExpressions.EMAIL, message = "profile.email.regex")
#Column(name = "Email", unique = true)
private String email;
#Pattern(regexp = "^[M|F]{1}$", message = "profile.sex.regex")
#Size(max = 1, message = "profile.sex.maxlength")
private String sex;
}

Basically you instantiated a POJO with this.userController = new UserController(), then called its method this.controller.postUser(...). Just simple Java with a simple object, without any relation to Spring and Spring MVC : #Valid is not taken into account.
If you want to make it work, you will have to give your test class some Spring information, with #RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(...). Then, for the Spring MVC part, you will have to mock a request call on your Controller through some Spring MVC facilites. It is done differently if you use Spring MVC 3.0- or 3.1+. For more information and actual code, see this post and its answers, for example.

Related

Spring Kafka OpenAPI v3 website generation + spec

Small question for Spring Kafka and OpenAPI V3 please.
In the paste, we had a Spring Web MVC web app, very straightforward, type https://interviewnoodle.com/documenting-a-springboot-rest-api-with-openapi-3-7b2bc1605f
package com.openapi.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
#Schema(description = "Book object")
#Entity
#Table(name="books")
public class Book {
#JsonProperty(value="id", required=true, index = 10)
#Schema(description = "Unique identifier of the Book.",
example = "1", required = true)
private long id;
#JsonProperty(value="title", required=true, index = 20)
#Schema(description = "Name of the title.",
example = "Java", required = true)
#NotBlank
#Size(min = 0, max = 20)
private String title;
#JsonProperty(value="author", required=true, index = 30)
#Schema(description = "Name of the author.",
example = "Max Abi", required = true)
#NotBlank
#Size(min = 0, max = 30)
private String author;
public Book() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "title", nullable = false)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Column(name = "author", nullable = false)
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package com.openapi.controller;
import com.openapi.model.Book;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collection;
#Tag(name = "book", description = "the book API")
#RequestMapping("/api/v1/books")
public interface BookApi {
#Operation(summary = "Create book", description = "This can only be done by the logged in book.", tags = { "book" })
#ApiResponses(value = { #ApiResponse(description = "successful operation", content = { #Content(mediaType = "application/json", schema = #Schema(implementation = Book.class)), #Content(mediaType = "application/xml", schema = #Schema(implementation = Book.class)) }) })
#PostMapping(value = "/", consumes = { "application/json", "application/xml", "application/x-www-form-urlencoded" })
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Book> postBook(
#NotNull
#Parameter(description = "Created book object", required = true)
#Valid #RequestBody Book body,
#NotNull #Parameter(description = "select which kind of data to fetch", required = true)
#Valid #RequestHeader(value="bookAuthorization", required = true) String bookAuthorization)
throws Exception;
}
For each and every client applications wanting to interact with us, i.e sending the http request with a Book object, we first jumped into a call, wrote a long mail explaning what the Book looks like.
Then, we discovered <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> which changed the game.
We would just do the annotations, generate the Open API v3 doc as we build our project, no extra step.
We would then just send the spec.yml via email, and tell them "please go to http://the-cool-host:8080/v3/api-docs/, and you will be able to see what you should be sending us".
This worked like a charm.
For organizational reason, we changed the http based application to a Kafka based approach.
With that, we lost ability to generate the doc, and also, host it on a website.
What I tried:
I tried leaving the annotations, and running the maven clean install command to generate the doc, but it is not generating anymore.
May I ask what did I miss please?
What are the possibilities to generate the Open API v3 doc on the go, and host it, but for Spring Kafka please?
Thank you

#Valid not working on nested objects (Java / Spring Boot)

I've been trying for days to find a similar problem online and can't seem to find anything so I am asking my question here.
I have a controller:
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Validated
#RestController
#RequestMapping("/data")
public class TheController {
private final TheService theService;
#Autowired
public TheController(TheService theService) {
this.theService = theService;
}
#PostMapping(path = "/data", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<String> saveData(#Valid #RequestBody Data data) {
subscriptionDataFeedService.sendData(data.getDataList());
return ResponseEntity.ok()
.body("Data successful.");
}
}
I have the request body class:
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Data {
#NotEmpty(message = "Data list cannot be empty.")
#JsonProperty(value = "dataArray")
List<#Valid DataOne> dataList;
}
I have the DataOne class:
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DataOne {
private #NotBlank String currency;
private #NotBlank String accountNumber;
private #NotBlank String finCode;
private String poNumber;
private #NotBlank String invoiceNumber;
private #NotNull Address billTo;
private #NotNull Address soldTo;
private #NotNull LocalDate invoiceDate;
private #NotBlank String billingPeriod;
private #NotNull LocalDate paymentDueDate;
private #NotNull BigDecimal amountDue;
#JsonProperty(value = "activitySummary")
private #NotNull List<#Valid ProductSummary> productSummaryList;
#JsonProperty(value = "accountSummary")
private #NotNull List<#Valid AccountSummary> accountSummaryList;
#JsonProperty(value = "transactions")
private #NotNull List<#Valid Transaction> transactionList;
private #NotNull PaymentByACH paymentByACH;
private #NotNull Address paymentByCheck;
private #NotNull CustomerServiceContact customerServiceContact;
}
And I will include the Address class:
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Address {
private #NotBlank String name;
private #NotBlank String address1;
private String address2;
private #NotBlank String city;
private #NotBlank String state;
private #NotBlank String postalCode;
}
I omitted some of the other classes because they aren't needed for my question.
So the problem I am having is that the #Valid annotation is able to validate everything except for the nested classes inside DataOne that aren't a list. In other words, it cannot validate the fields inside Address, PaymentByACH, etc. However, it is able to validate that those objects are #NotNull but is unable to validate the fields inside those classes.
The #Valid is unable to validate the name, address 1, city, etc fields inside of Address. Whenever I add an #Valid tag in front of the Address field inside DataOne I get an HV000028: Unexpected exception during isValid call exception.
How can I validate the nested fields inside of the Address object or any of the nested objects?
TL;DR: The objects that are a list, such as List<#Valid Transaction> transactionList; does validate the fields inside of Transaction but the code does not validate the fields inside of Address.
Great question.
I think you're slightly misusing the #Valid annotation.
How can I validate the nested fields inside of the Address object or
any of the nested objects?
#Valid shouldn't be prefixed to fields you want to validate. That tool is used specifically for validating arguments in #Controller endpoint methods (and sometimes #Service methods). According to docs.spring.io:
"Spring MVC has the ability to automatically validate #Controller
inputs."
It offers the following example,
#Controller
public class MyController {
#RequestMapping("/foo", method=RequestMethod.POST)
public void processFoo(#Valid Foo foo) { /* ... */ }
}
The only reason you should use #Valid anywhere besides in the parameters of a controller (or service) method is to annotate complex types, like lists of objects (ie: DataOne: productSummaryList, accountSummaryList, transactionList). These docs have details for implementing your own validation policy if you'd like.
For your practical needs, you should probably only be using #Valid on controller level methods and the complex types for models referenced by that method. Then use field-level constraints to ensure you don't get things like negative age. For example:
#Data
...
public class Person {
...
#Positive
#Max(value = 117)
private int age;
...
}
Check out this list of constraints you can use from the spring docs. You're already using the #NotNull constraint, so this shouldn't be too foreign. You can validate emails, credit cards, dates, decimals, ranges, negative or positive values, and many other constraints.

Spring boot JPA no returning existing result using findById

I have created a pretty small and simple Spring Boot app using the Oracle database and some JPA queries.
This is the code snippet which is not returning data, which is actually exists in database.
letterRecipientNonOas = letterRecipientNonOasRepository
.findById(Long.valueOf(letterRecipientDTO.getNonOas().getId()))
.orElseThrow(() -> new EntityNotFoundException(LetterRecipientNonOas.class,
Constant.MESSAGE_ENTITY_NOT_FOUND));
here findById is returning empty result set.
this is my repository
package com.care.document.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.care.document.model.LetterRecipientNonOas;
/**
* The Interface LetterRecipientNonOasRepository.
*/
#Repository
public interface LetterRecipientNonOasRepository extends PagingAndSortingRepository<LetterRecipientNonOas, Long> {
Optional<LetterRecipientNonOas> findByLetterId(Long id);
Optional<LetterRecipientNonOas> findByTitleIgnoreCase(String title);
List<LetterRecipientNonOas> findByTitleContainingIgnoreCase(String title);
List<LetterRecipientNonOas> findAllByTitleIgnoreCaseAndIdNot(String title, Long recipientId);
List<LetterRecipientNonOas> findAllByIdAndLetterId(long id, long letterId);
}
and this is my model class:
package com.care.document.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import org.springframework.lang.Nullable;
import com.care.admin.model.BaseEntity;
import com.care.admin.util.CommonUtil;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
#Table(name = "letter_recipient_non_oas")
public class LetterRecipientNonOas extends BaseEntity implements Serializable {
#Id
Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "letter_id")
Letter letter;
Integer recipientType; // Action/Info
//byte recipientSubType; // Internal/External/NonOAS
byte recipientCategory; //Internal/External
int orderNo;
String title;
#Nullable
String remarks;
String address;
#PrePersist
private void prePersist() {
this.id = CommonUtil.generateID(this.atRegion);
}
}
I tested, tried different ways but of no use.
There are a couple of scenarios how one might get this impression:
You are looking at the wrong database.
The data isn't there yet when you try to load it, but is when you check.
JPAs caches are known to create such scenarios rather efficiently.
The data looks a little different than you think. This could be caused by invisible or easy to miss content like spaces or even control characters.
You check the database within the transaction that created the data or with a session that allows dirty reads and the insert that created the data wasn't committed yet.

Junit Testing #getmappings with h2 in memory database

I'm having issues unit testing the DbRequest controller. I have one unit test working, but I'm unable to achieve a unit test for the DBRequest controller GET mappings which does a database lookup using hibernate. I' have an H2 in memory database created for the junit tests.
I've tried a variety of different setups, and nothing seems to work correctly.
Edited the below, I'm getting a NullPointer,
java.lang.NullPointerException
at com.lmig.informaticaservice.api.DBcontroltest.saveTest(DBcontroltest.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Here is the edited test.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class DBcontroltest {
#Autowired
DbRequest dbRequest;
#Autowired
ConnectionRequestRepository connectionRequestRepository;
private MockMvc mockMvc;
// #Autowired
//private TestEntityManager entityManager;
#Test
public void saveTest() throws Exception {
ConnectionRequest connectionRequest = new ConnectionRequest((long) 1, "test");
connectionRequestRepository.save(connectionRequest);
System.out.println(connectionRequestRepository.findAll().toString());
mockMvc.perform(get("/api/selectDB/{connectionId}" ,1))
.andExpect(status().isOk());
}
}
Typical JPA repository
package com.test.models;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ConnectionRequestRepository extends JpaRepository<ConnectionRequest, Long> {
}
Here is my controller.
package com.test.api;
import com.models.ConnectionRequest;
import com.test.models.ConnectionRequestRepository;
import java.util.List;
import javax.validation.Valid;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Data
#RestController
#RequestMapping("/api/")
public class DbRequest {
#Autowired
private ConnectionRequestRepository connectionRequestRepository;
private ConnectionRequest connectionRequest;
#GetMapping("/selectDB")
public List<ConnectionRequest> getAllRequests() {
return connectionRequestRepository.findAll();
}
#GetMapping("/selectDB/{connectionId}")
public ResponseEntity<ConnectionRequest> getRequestById(#PathVariable("connectionId") Long connectionId) throws Exception {
ConnectionRequest connectionRequest = connectionRequestRepository.findById(connectionId)
.orElseThrow(() -> new Exception("Connection Request " + connectionId + " not found"));
return ResponseEntity.ok().body(connectionRequest);
}
}
Here is the model for the database.
package com.testing.models;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
#Data
#Entity
#Table(name = "connrequest", schema = "testschema")
#AllArgsConstructor
#NoArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class ConnectionRequest {
#Id
#Column(name = "connection_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long connectionId;
#Column(name = "requestor", nullable = false)
private String requestor;
}
It looks like the on of the annotations on the PK of ConnectionRequest is the problem.
The annotation #GeneratedValue tells JPA that it needs to determine the value, so any provided value for the ID will be actively discarded. From the docs
Indicates that the persistence provider must assign primary keys for the entity using a database identity column.
To fix this try either removing that annotation, so then you must always provide an ID, or alternatively, after saving the entity in your test, get the ID that is assigned and call connectionRequestRepository.getOne() with that ID.

Spring Data IGNORE optional parameter in query method [duplicate]

I want to write some query methods in repository layer. This method must ignore null parameters. For example:
List<Foo> findByBarAndGoo(Bar barParam, #optional Goo gooParam);
This method must be return Foo by this condition:
bar == barParam && goo == gooParam;
if gooParam not null. if gooParam was null then condition change to:
bar == barParam;
Is there any solution? Can someone help me?
I don't believe you'll be able to do that with the method name approach to query definition. From the documentation (reference):
Although getting a query derived from the method name is quite
convenient, one might face the situation in which either the method
name parser does not support the keyword one wants to use or the method
name would get unnecessarily ugly. So you can either use JPA named
queries through a naming convention (see Using JPA NamedQueries for
more information) or rather annotate your query method with #Query
I think you have that situation here, so the answer below uses the #Query annotation approach, which is almost as convenient as the method name approach (reference).
#Query("select foo from Foo foo where foo.bar = :bar and "
+ "(:goo is null or foo.goo = :goo)")
public List<Foo> findByBarAndOptionalGoo(
#Param("bar") Bar bar,
#Param("goo") Goo goo);
Too late to answer. Not sure about relationship between Bar and Goo. Check if Example can helps you.
It worked for me. I have a similar situation, entity User have set of attributes and there is findAll method which search user based on attributes(which are optional).
Example,
Class User{
String firstName;
String lastName;
String id;
}
Class UserService{
// All are optional
List<User> findBy(String firstName, String lastName, String id){
User u = new User();
u.setFirstName(firstName);
u.setLastName(lastName);
u.setId(id);
userRepository.findAll(Example.of(user));
// userRepository is a JpaRepository class
}
}
Complementing the answer of #chaserb, I personally would add the parameter as a Java8 Optional type to make it explicit in the signature of the method the semantics that is an optional filter.
#Query("select foo from Foo foo where foo.bar = :bar and "
+ "(:goo is null or foo.goo = :goo)")
public List<Foo> findByBarAndOptionalGoo(
#Param("bar") Bar bar,
#Param("goo") Optional<Goo> goo);
You can 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
So many great answers already, but I specifically implemented this using the answer from #Pankaj Garg (Using the Spring Specification API). There are a few use cases I am adding to my answer
4 parameters that may or may not be null.
Paginated response from the repository.
Filtering by a field in a nested object.
Ordering by a specific field.
First I create a couple of entities, specifically Ticket, Movie and Customer. Nothing fancy here:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
#Entity
#Table(name = "ticket", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Ticket implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#JoinColumn(name = "movie_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER)
private Movie movie;
#JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
#Column(name = "booking_date")
#Temporal(TemporalType.TIMESTAMP)
private Date bookingDate;
}
Movie:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
#Entity
#Table(name = "movie", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Movie implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#Basic(optional = false)
#NotNull
#Size(max = 100)
#Column(name = "movie_name", nullable = false, length = 100)
private String movieName;
}
Customer:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
#Entity
#Table(name = "customer", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Customer implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#Basic(optional = false)
#NotNull
#Size(max = 100)
#Column(name = "full_name", nullable = false, length = 100)
private String fullName;
}
Then I create a class with fields for the parameters I wish to filter by:
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
import java.util.UUID;
#Data
#AllArgsConstructor
public class TicketFilterParam {
private UUID movieId;
private UUID customerId;
private Date start;
private Date end;
}
Next I create a class to generate a Specification based on the filter parameters. Note the way nested objects are accessed, as well as the way ordering is added to the query.
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TicketSpecifications {
public static Specification<Ticket> getFilteredTickets(TicketFilterParam params) {
return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getMovieId() != null) {
predicates.add(criteriaBuilder.equal(root.get("movie").<UUID> get("id"), params.getMarketerId()));
}
if (params.getCustomerId() != null) {
predicates.add(criteriaBuilder.equal(root.get("customer").<UUID> get("id"), params.getDepotId()));
}
if (params.getStart() != null && params.getEnd() != null) {
predicates.add(criteriaBuilder.between(root.get("bookingDate"), params.getStart(), params.getEnd()));
}
criteriaQuery.orderBy(criteriaBuilder.desc(root.get("bookingDate")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}
Next I define the Repository interface. This would have not only JpaRepository, but also JpaSpecificationExecutor:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
#Repository
public interface TicketRepository extends JpaRepository<Ticket, UUID>, JpaSpecificationExecutor<Ticket> {
}
Finally, in some service class, I obtain results like this:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
#Service
public class TicketService {
#Autowired
private TicketRepository ticketRepository;
public Page<Ticket> getTickets(TicketFilterParam params, PageRequest pageRequest) {
Specification<Ticket> specification = TicketSpecifications.getFilteredTickets(params);
return ticketRepository.findAll(specification, pageRequest);
}
}
PageRequest and TicketFilterParam would probably be obtained from some parameters and values on a rest endpoint.
You could code this yourself in just a few lines:
List<Foo> findByBarAndOptionalGoo(Bar bar, Goo goo) {
return (goo == null) ? this.findByBar(bar) : this.findByBarAndGoo(bar, goo);
}
Otherwise, I don't know if Spring-Data supports this out of the box.
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below, I have faced the same issue and finally could find this solution that looks like very simple and efficient than the others to me:
my Controller Class:
#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.
my Repository interface:
#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.

Categories