We want to expose an api for getting customers base on fullName, email, address and phone columns and those input values for filtering customers are optional. It means, the query input can contain only fullName and others can be null etc...
If we use the query like we usually use as below with the input is only full_name.
Then we will receive the result with the data like this.
Name
Value
id
2b7b468d-4d15-4a62-bbd2-0f7712488471
address
dob
1995-10-10 07:00:00.000
email
full_name
Katrina Kerluke
gender
M
phone
It will try to get the record with fullName is as the input and other fields have to contain null values.
Of course, this is not what we are expecting, we are expecting that the query will execute base on input params that contain values, for null values we will skip them. In this post we will handle this case by using native query in JPA.
Jpa supports us to use the native query through using @Query annotation with param nativeQuery=true.
For example:
123
@Query(value="select * from customers c where c.email = :email",nativeQuery=true)Optional<CustomerEntity>getCustomerByEmail(@Param("email")StringfullName);
Then in SQL we have a way to avoid null input value in query as below.
So before the or we have :full_name is null, it means if we don't input any value for :full_name then we have null is null and this statement is true . So the query will become.
12
SELECT*FROMcustomerscWHEREtrue;
The Sql statement above is correct and it is not broken with the WHERE clause.
Go back to the example that we discussed before, so to to expose an api for getting customers base on fullName, email, address and phone columns. We will have the native query statement below.
<plugins><plugin><groupId>org.openapitools</groupId><artifactId>openapi-generator-maven-plugin</artifactId><version>7.0.1</version><executions><execution><goals><goal>generate</goal></goals><configuration><!-- path to the openapi file spec `.yml` --><inputSpec>${project.basedir}/src/main/resources/openapi/openapi-server.yml
</inputSpec><generatorName>spring</generatorName><!-- generated package for api interface --><apiPackage>com.springboot.project.generated.api</apiPackage><!-- generated package for models --><modelPackage>com.springboot.project.generated.model</modelPackage><!-- using supportingFilesToGenerate --><supportingFilesToGenerate>ApiUtil.java
</supportingFilesToGenerate><configOptions><useTags>true</useTags><delegatePattern>true</delegatePattern><dateLibrary>java8</dateLibrary><java8>false</java8><interfaceOnly>true</interfaceOnly><useBeanValidation>true</useBeanValidation><performBeanValidation>true</performBeanValidation><useOptional>false</useOptional><useSpringBoot3>true</useSpringBoot3></configOptions></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><source>17</source><target>17</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.5.5.Final</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins>
Now, in the folder resource, we will create a folder openapi and a openapi-server.yaml inside it. The content of this file will look like below with some apis.
openapi:3.0.3info:title:Swagger Customer - OpenAPI 3.0description:Everything about sample hibernate second level cachetermsOfService:http://swagger.io/terms/contact:email:apiteam@swagger.iolicense:name:Apache 2.0url:http://www.apache.org/licenses/LICENSE-2.0.htmlversion:1.0.11externalDocs:description:Find out more about Swaggerurl:http://swagger.ioservers:-url:https://petstore3.swagger.io/api/v1tags:-name:customerdescription:Everything about your CustomerexternalDocs:description:Find out moreurl:http://swagger.io-name:orderdescription:Everything about your OrderexternalDocs:description:Find out moreurl:http://swagger.iopaths:/v1/customers:get:tags:-customersummary:get an existing customer infodescription:get an existing customer info by EmailoperationId:getCustomerInfoByEmailparameters:-name:emailin:queryrequired:trueschema:type:stringresponses:'200':description:Successful operationcontent:application/json:schema:$ref:'#/components/schemas/CustomerResponse''400':description:Invalid ID supplied'404':description:Pet not found'405':description:Validation exceptionpost:tags:-customersummary:Add a Customer to databasedescription:Add a Customer to databaseoperationId:addCustomerrequestBody:description:Create a Customer to databasecontent:application/json:schema:$ref:'#/components/schemas/CustomerRequest'required:trueresponses:'200':description:Successful operationcontent:application/json:schema:$ref:'#/components/schemas/CustomerResponse''405':description:Invalid input/v1/customers/{customerId}/info:get:tags:-customersummary:get an existing customer infodescription:get an existing customer info by IdoperationId:getCustomerInfoparameters:-name:customerIdin:pathrequired:trueschema:type:stringformat:uuidresponses:'200':description:Successful operationcontent:application/json:schema:$ref:'#/components/schemas/CustomerResponse''400':description:Invalid ID supplied'404':description:Pet not found'405':description:Validation exceptionput:tags:-customersummary:Update an existing customerdescription:Update an existing customer by IdoperationId:updateCustomersparameters:-name:customerIdin:pathrequired:trueschema:type:stringformat:uuidrequestBody:description:Update an existent customer in the databasecontent:application/json:schema:$ref:'#/components/schemas/CustomerResponse'required:trueresponses:'200':description:Successful operationcontent:application/json:schema:$ref:'#/components/schemas/CustomerResponse''400':description:Invalid ID supplied'404':description:Pet not found'405':description:Validation exception/v1/customers/action-filter:post:tags:-customersummary:filter customersdescription:filter customersoperationId:filterCustomersrequestBody:description:Update an existent customer in the databasecontent:application/json:schema:$ref:'#/components/schemas/CustomerFilterRequest'required:trueresponses:'200':description:Successful operationcontent:application/json:schema:type:arrayitems:$ref:'#/components/schemas/CustomerResponse''400':description:Invalid ID supplied'404':description:Pet not found'405':description:Validation exceptioncomponents:schemas:CustomerResponse:allOf:-$ref:'#/components/schemas/CustomerRequest'-type:objectproperties:id:type:stringformat:uuidCustomerRequest:type:objectrequired:-email-phoneproperties:fullName:type:stringemail:type:stringaddress:type:stringphone:type:stringgender:type:stringenum:-M-Fdob:type:stringformat:dateCustomerFilterRequest:type:objectproperties:fullName:type:stringemail:type:stringaddress:type:stringphone:type:string
Now, we can use command mvn clean install to build and generate apis and model from openapi-server.yaml.
packagecom.springboot.project.repository;importcom.springboot.project.entity.CustomerEntity;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.data.jpa.repository.Query;importorg.springframework.data.repository.query.Param;importorg.springframework.stereotype.Repository;importjava.util.List;importjava.util.Optional;importjava.util.UUID;@RepositorypublicinterfaceCustomerRepositoryextendsJpaRepository<CustomerEntity,UUID>{Optional<CustomerEntity>findCustomerByEmail(Stringemail);@Query(value=" SELECT * FROM customers c "+/*1*/" WHERE (:full_name is null or c.full_name = :full_name) "+/*2*/" AND (:email is null or c.email = :email) "+/*3*/" AND (:address is null or c.address = :address) "+/*4*/" AND (:phone is null or c.phone = :phone); ",/*5*/nativeQuery=true)List<CustomerEntity>filterCustomers(@Param("full_name")StringfullName,@Param("email")Stringemail,@Param("address")Stringaddress,@Param("phone")Stringphone);}
As you can see in this Repository we will define a method to filter customers using native query with optional params.
packagecom.springboot.project.service;importcom.springboot.project.entity.CustomerEntity;importcom.springboot.project.mapper.AutoCustomerMapper;importcom.springboot.project.model.Customer;importcom.springboot.project.model.CustomerFilter;importcom.springboot.project.repository.CustomerRepository;importlombok.AllArgsConstructor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.List;importjava.util.Optional;importjava.util.UUID;@Service@AllArgsConstructor(onConstructor=@__(@Autowired))publicclassCustomerService{privatefinalCustomerRepositorycustomerRepository;publicCustomercreateCustomer(Customercustomer){CustomerEntitycustomerEntity=AutoCustomerMapper.MAPPER.mapToCustomerEntity(customer);customerEntity=this.customerRepository.save(customerEntity);returnAutoCustomerMapper.MAPPER.mapToCustomer(customerEntity);}publicCustomergetCustomer(UUIDcustomerId){Optional<CustomerEntity>customerEntity=this.customerRepository.findById(customerId);if(customerEntity.isPresent()){returnAutoCustomerMapper.MAPPER.mapToCustomer(customerEntity.get());}thrownewRuntimeException("Customer Not Found!");}publicList<Customer>filterCustomers(CustomerFiltercustomerFilter){List<CustomerEntity>foundCustomers=this.customerRepository.filterCustomers(customerFilter.getFullName(),customerFilter.getEmail(),customerFilter.getAddress(),customerFilter.getPhone());returnAutoCustomerMapper.MAPPER.mapToCustomers(foundCustomers);}publicvoidupdateCustomer(UUIDcustomerId,Customercustomer){Optional<CustomerEntity>customerEntity=this.customerRepository.findById(customerId);if(customerEntity.isPresent()){CustomerEntityexistedCustomerEntity=customerEntity.get();CustomerEntityupdateCustomerEntity=AutoCustomerMapper.MAPPER.mapToCustomerEntity(customer);AutoCustomerMapper.MAPPER.updateCustomerEntity(existedCustomerEntity,updateCustomerEntity);this.customerRepository.save(existedCustomerEntity);return;}thrownewRuntimeException("Customer Not Found!");}publicvoiddeleteCustomer(UUIDcustomerId){this.customerRepository.deleteById(customerId);}publicCustomerfindCustomerByEmail(Stringemail){Optional<CustomerEntity>customerEntity=this.customerRepository.findCustomerByEmail(email);if(customerEntity.isPresent()){returnAutoCustomerMapper.MAPPER.mapToCustomer(customerEntity.get());}thrownewRuntimeException("Customer Not Found! with email: "+email);}}