Master Dynamic SQL in Spring Boot 3 with jpa-search-helper: Real‑World Examples

This article introduces dynamic SQL techniques for Spring Boot 3, compares JPA and MyBatis implementations, and demonstrates how the powerful jpa-search-helper library simplifies complex queries with both map‑based and JSON‑based modes, including pagination and sorting examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Dynamic SQL in Spring Boot 3 with jpa-search-helper: Real‑World Examples

Introduction

Dynamic SQL allows applications to build and execute SQL statements at runtime based on user input, business rules, or data state, improving flexibility and reducing redundant code for complex query scenarios.

JPA Implementation

Using JpaSpecificationExecutor together with Specification and Java Predicate logic, you can construct dynamic queries as shown below:

public Page<Person> dynamicQuery(PersonDTO dto) {
    Specification<Person> spec = (root, query, builder) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (StringUtils.isNotEmpty(dto.getPhone())) {
            predicates.add(builder.equal(root.get("phone"), dto.getPhone()));
        }
        if (StringUtils.isNotEmpty(dto.getName())) {
            predicates.add(builder.like(root.get("name"), "%" + dto.getName() + "%"));
        }
        return predicates.isEmpty() ? null :
               predicates.size() == 1 ? predicates.get(0) :
               builder.and(predicates.toArray(new Predicate[0]));
    };
    return personRepository.findAll(spec, dto.getPageable());
}

MyBatis Implementation

With MyBatis you manually compose XML fragments. A simple example:

<select id="queryPerson" resultType="Person">
  SELECT * FROM x_person
  <where>
    <if test="name != null">AND name = #{name}</if>
    <if test="phone != null">AND email = #{phone}</if>
    <!-- more conditions -->
  </where>
</select>

MyBatis offers flexibility but can lead to bulky XML and maintenance challenges for complex queries.

Introducing jpa-search-helper

The jpa-search-helper library provides a concise way to compose advanced dynamic queries.

Query Modes :

Mode 1 – Map<String,String> for GET‑style query parameters.

Mode 2 – JSON JPASearchInput for POST‑style bodies, supporting nested logic and a full range of operators.

Projection : Select specific fields from results (available since 3.2.0).

Dependency

<dependency>
  <groupId>app.tozzi</groupId>
  <artifactId>jpa-search-helper</artifactId>
  <version>3.5.3</version>
</dependency>
Repository: https://repo.maven.apache.org/maven2

Entity Example

@Entity
public class Person {
    @Id
    private Long id;
    @Searchable
    private String name;
    @Searchable
    private Integer age;
    @Searchable
    private String phone;
    @Searchable
    private String email;
    @Searchable
    private Date birth;
    @NestedSearchable
    @OneToOne
    @JoinColumn(name = "company_id")
    private Company company;
}

@Entity
public class Company {
    @Id
    private Long id;
    @Searchable(entityFieldKey = "company.name")
    private String name;
    @Searchable(entityFieldKey = "company.address")
    private String address;
}

Annotate searchable fields with @Searchable and nested fields with @NestedSearchable.

Repository Interface

public interface PersonRepository extends JpaRepository<Person, Long>, JPASearchRepository<Person> {}

Mode 1 Query Examples

Simple Query

public List<Person> simpleQuery() {
    Map<String, String> filters = new HashMap<>();
    filters.put("name_contains", "十");
    filters.put("age_gte", String.valueOf(10));
    return personRepository.findAll(filters, Person.class);
}

Related Entity Query

public List<Person> simpleRelatedQuery() {
    Map<String, String> filters = new HashMap<>();
    filters.put("name_contains", "十");
    filters.put("age_gte", String.valueOf(10));
    filters.put("company.name_eq", "网易公司");
    return personRepository.findAll(filters, Person.class);
}

Paginated & Sorted Query

public Page<Person> simpleQuerySortAndPage() {
    Map<String, String> filters = new HashMap<>();
    filters.put("name_contains", "十");
    filters.put("age_gte", String.valueOf(10));
    filters.put("age_sort", "ASC");
    filters.put("_limit", "2");
    filters.put("_offset", "0");
    return personRepository.findAllWithPaginationAndSorting(filters, Person.class);
}

Mode 2 Query Examples

Using JSON JPASearchInput you can express complex logical trees. Example of a simple query:

public List<Person> complexQuery(JPASearchInput input) {
    return personRepository.findAll(input, Person.class);
}

Example of a combined query with nested or and and operators (excerpt):

{
  "filter": {
    "operator": "and",
    "filters": [
      {"operator": "gte", "key": "age", "value": "10"},
      {"operator": "and", "filters": [
        {"operator": "or", "filters": [
          {"operator": "contains", "key": "company.name", "value": "网易公司"},
          {"operator": "eq", "key": "company.address", "value": "广州市天河区珠江新城东塔"}
        ]}
      ]}
    ]
  },
  "options": {"pageSize": 1, "pageOffset": 0, "sortOptions": [{"key": "age", "desc": false}]}
}

Pagination and sorting are handled via the options section.

Result Visualization

Generated SQL statements and query results are shown in the original article as screenshots; representative images are retained below.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend DevelopmentSpring BootMyBatisDynamic SQLjpajpa-search-helper
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.