DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Exploring Exciting New Features in Java 17 With Examples
  • Providing Enum Consistency Between Application and Data
  • JSON-Based Serialized LOB Pattern
  • Projections/DTOs in Spring Data R2DBC

Trending

  • Understanding Java Signals
  • The Role of Retrieval Augmented Generation (RAG) in Development of AI-Infused Enterprise Applications
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • STRIDE: A Guide to Threat Modeling and Secure Implementation
  1. DZone
  2. Data Engineering
  3. Data
  4. Advanced and Dynamic Searching With Spring Data JPA

Advanced and Dynamic Searching With Spring Data JPA

Empower your search functionality with the latest approach using Spring Data JPA. Discover how this new technique can help you search more effectively and efficiently.

By 
Biagio Tozzi user avatar
Biagio Tozzi
·
Jan. 30, 24 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
4.3K Views

Join the DZone community and get the full member experience.

Join For Free

I have often been asked to develop advanced search services. By advanced search, I mean searches in which it’s possible to apply multiple filters on all (or almost all) fields such as like, between, in, greater than, etc.

So imagine having to build a service based on one or more entities capable of offering an endpoint that can be called like this (start keeping an eye out for special suffixes <propertyName><_suffix>):

 
curl - request GET \
 - url 'http://www.myexampledomain.com/persons?
firstName=Biagio
&lastName_startsWith=Toz
&birthDate_gte=19910101
&country_in=IT,FR,DE
&company.name_in=Microsoft,Apple
&company.employees_between=500,5000'


Or

 
curl --request GET \
--url 'http://www.myexampledomain.com/persons?
firstName_endsWith=gio
&lastName_in=Tozzi,Totti
&birthDate_lt=19980101
&_offset=0
&_limit=100
&birthDate_sort=ASC'


If you are using JPA in a Spring Boot project, you can now develop this search service with just a few lines of code, thanks to JPA Search Helper! Let me explain what it is.

JPA Search Helper

First Step: @Searchable Annotation

Start by applying the @Searchable annotation to the fields in your DTO, or alternatively, your JPA entity, that you want to make available for search.

@Data
public class Person {

    @Searchable
    private String firstName;

    @Searchable
    private String lastName;

    @Searchable(entityFieldKey = "dateOfBirth")
    private Date birthDate;

    @Searchable
    private String country;
    
    private Company company;

    @Data
    public static class Company {
        
        @Searchable(entityFieldKey=companyEntity.name)
        private String name;
              
        @Searchable(entityFieldKey=companyEntity.employeesCount)
        private int employees;

    }

}

The annotation allows you to specify:

  • Core Properties
    • entityFieldKey: The name of the field defined on the entity bean (not to be specified if using the annotation on the entity bean). If not specified, the key will be the field name.
    • targetType: The managed object type by entity. If not specified the librariy tries to obtain it based on field type (es. Integer field without target type definition will be INTEGER). If there is no type compatible with those managed, it will be managed as a string. Managed types: STRING, INTEGER, DOUBLE, FLOAT, LONG, BIGDECIMAL, BOOLEAN, DATE, LOCALDATE, LOCALDATETIME, LOCALTIME, OFFSETDATETIME, OFFSETTIME.
  • Validation Properties
    • datePattern: Only for DATE target type. Defines the date pattern to use.
    • maxSize, minSize: Maximum/minimum length of the value.
    • maxDigits, minDigits: Only for numeric types. Maximum/minimum number of digits.
    • regexPattern: Regex pattern.
    • decimalFormat: Only for decimal numeric types. Default #.##

Continuing the example, our entity classes:

 
@Entity
@Data
public class PersonEntity {

    @Id
    private Long id;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    @Column(name = "BIRTH_DATE")
    private Date dateOfBirth;

    @Column(name = "COUNTRY")
    private String country;
        
    @OneToOne
    private CompanyEntity companyEntity;

}

@Entity
@Data
public class CompanyEntity {

    @Id
    private Long id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "COUNT")
    private Integer employeesCount;

}


Second and last step: JPASearchRepository<?>

Your Spring JPA repository must extend JPASearchRepository<?>:

 
@Repository
public interface PersonRepository extends JpaRepository<PersonEntity, Long>, JPASearchRepository<PersonEntity> {

}


Well, let’s build the filters and feed them to the repository:

 
// ...

Map<String, String> filters = new HashMap<>();
filters.put("firstName_eq", "Biagio");
filters.put("lastName_startsWith", "Toz");
filters.put("birthDate_gte", "19910101"); 
filters.put("country_in", "IT,FR,DE");
filters.put("company.name_in", "Microsoft,Apple");
filters.put("company.employees_between", "500,5000");

// Without pagination
List<PersonEntity> fullSearch = personRepository.findAll(filters, Person.class);

filters.put("birthDate_sort" : "ASC");
filters.put("_limit", "10");
filters.put("_offset", "0");

// With pagination
Page<PersonEntity> sortedAndPaginatedSearch = personRepository.findAllWithPaginationAndSorting(filters, Person.class);

// ...


Basically, you just need to define a map whose key is made up of <fieldName><_suffix> and search value. The complete list of suffixes, i.e., available filters, is here.

Note 1: If no suffix is specified, the search is done in equal (_eq)

Note 2: In the example, I applied the @Searchable annotation on the DTO fields. Alternatively, it’s possible to apply them directly on the entity.


A Pseudo-Real Implementation in a Spring Boot Project

Service/Manager bean:

 
@Service 
public class PersonManager {     
        
    @Autowired         
    private PersonRepository personRepository;
                     
    public List<Person> find(Map<String, String> filters) {
      return personRepository.findAllWithPaginationAndSorting(filters, Person.class).stream().map(this::toDTO).toList(); 
    } 

    private static Person toDTO(PersonEntity personEntity) {
        // ...
    }

}


Controller:

 
@RestController
public class MyController {
    
    @Autowired         
    private PersonManager personManager;
         
    @GetMapping(path="/persons", produces = MediaType.APPLICATION_JSON_VALUE)  
    public List<Person> findPersons(@RequestParam Map<String, String> requestParams) {  
        return personManager.find(requestParams);  
    }
}


..et voilà les jeux sont faits

Extra

The library allows you to force join fetch.

A “fetch” join allows associations or collections of values to be initialized along with their parent objects using a single select.

That’s how:

 
// ...

Map<String, JoinFetch> fetches = Map.of("companyEntity", JoinFetch.LEFT);
personRepository.findAll(filters, Person.class, fetches);

// ...


That’s all.. for now!

Data transfer object Data (computing) entity Spring Boot Strings Data Types

Published at DZone with permission of Biagio Tozzi. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Exploring Exciting New Features in Java 17 With Examples
  • Providing Enum Consistency Between Application and Data
  • JSON-Based Serialized LOB Pattern
  • Projections/DTOs in Spring Data R2DBC

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!