You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于派生属性在Spring Data REST中实现筛选与排序

How to Filter and Sort by Derived Properties in Spring Data REST

Great question! I’ve run into this exact scenario countless times—Spring Data REST handles persistent entity fields seamlessly, but derived properties (like embedded object fields or computed values) need a bit of extra work. Let’s break down the most practical approaches based on your setup (using JpaRepository, QueryDslPredicateExecutor, and QuerydslBinderCustomizer):

1. Customize Querydsl Bindings for Derived Properties

Since you’re already implementing QuerydslBinderCustomizer, this is the most straightforward way to map request parameters to derived properties, whether they’re embedded fields or computed values.

Example 1: Handling Embedded Properties

Say your Person entity has an embedded Address with a city field:

@Getter @Entity
public class Person extends AbstractEntity<Long> {
    @Embedded
    private Address address;
    // other persistent fields...
}

@Embeddable
public class Address {
    private String city;
    // other address fields...
}

You can bind the city request parameter directly to the embedded path in your repository:

public interface PersonRepository extends JpaRepository<Person, Long>,
        QueryDslPredicateExecutor<Person>,
        QuerydslBinderCustomizer<QPerson> {

    @Override
    default void customize(QuerydslBindings bindings, QPerson root) {
        // Map ?city=Paris to p.address.city = Paris
        bindings.bind(root.address.city).first((path, value) -> path.eq(value));
        
        // Optional: Ignore default bindings for unlisted fields to keep endpoints clean
        // bindings.excludeUnlistedProperties(true);
        // bindings.including(root.address.city, root.firstName);
    }
}

Example 2: Handling Computed Derived Properties

For computed properties like fullName (a combination of firstName and lastName), you can manually define the query logic when the request parameter matches:

@Override
default void customize(QuerydslBindings bindings, QPerson root) {
    // Handle ?fullName=John by checking firstName OR lastName for a match
    bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) (path, value) -> {
        if ("fullName".equals(path.getMetadata().getName())) {
            return root.firstName.containsIgnoreCase(value)
                    .or(root.lastName.containsIgnoreCase(value));
        }
        // Fallback to default behavior for other string fields
        return path.containsIgnoreCase(value);
    });
}

2. Define Custom Query Methods with @Query

If you have fixed filtering logic for a derived property, creating a custom query method lets Spring Data REST expose it as a dedicated search endpoint.

Example: Filter by Embedded City or Computed FullName

public interface PersonRepository extends JpaRepository<Person, Long>,
        QueryDslPredicateExecutor<Person>,
        QuerydslBinderCustomizer<QPerson> {

    // Exposes /people/search/findByAddressCity?city=London
    @Query("SELECT p FROM Person p WHERE p.address.city = :city")
    Page<Person> findByAddressCity(@Param("city") String city, Pageable pageable);

    // Exposes /people/search/findByFullNameContaining?fullName=Doe
    @Query("SELECT p FROM Person p WHERE CONCAT(p.firstName, ' ', p.lastName) LIKE %:fullName%")
    Page<Person> findByFullNameContaining(@Param("fullName") String fullName, Pageable pageable);
}

This approach is perfect for complex logic that’s hard to express with Querydsl bindings, and it automatically supports pagination/sorting via the Pageable parameter.

3. Sorting by Derived Properties

Sorting is trickier because Spring Data REST defaults to only sorting on persistent database fields. Here’s how to handle it:

Option 1: Querydsl-Based Sorting

Extend your QuerydslBinderCustomizer configuration to allow sorting on derived paths:

@Override
default void customize(QuerydslBindings bindings, QPerson root) {
    // Allow sorting by address.city via ?sort=address.city,asc
    bindings.including(root.address.city);
    
    // For computed fullName, map ?sort=fullName,asc to sorting by firstName + lastName
    bindings.bind(String.class).all((path, values) -> {
        if ("fullName".equals(path.getMetadata().getName())) {
            SortDirection direction = values.size() == 2 && "desc".equalsIgnoreCase(values.get(1)) 
                    ? SortDirection.DESC 
                    : SortDirection.ASC;
            return Optional.of(direction.isAscending() 
                    ? root.firstName.asc().and(root.lastName.asc()) 
                    : root.firstName.desc().and(root.lastName.desc()));
        }
        return Optional.empty();
    });
}

Option 2: JPQL Query with Explicit Sorting

For computed properties that don’t map directly to a database path, define a query that includes the sort expression:

@Query("SELECT p FROM Person p ORDER BY CONCAT(p.firstName, ' ', p.lastName) ASC")
Page<Person> findAllSortedByFullName(Pageable pageable);

Or, if you want dynamic sorting, use a custom Sort parameter and resolve it in the query:

@Query("SELECT p FROM Person p ORDER BY " +
        "CASE WHEN :sortBy = 'fullName' THEN CONCAT(p.firstName, ' ', p.lastName) END ASC")
Page<Person> findAllWithDynamicSort(@Param("sortBy") String sortBy, Pageable pageable);

Key Notes

  • For embedded properties, always prefer Querydsl bindings—they keep your code clean and align with Spring Data REST’s conventions.
  • For computed properties, test performance: using CONCAT or similar functions in JPQL/Querydsl can impact query speed on large datasets (consider adding a generated database column if needed).
  • Always test your endpoints with tools like Postman to ensure parameters map correctly to your derived properties.

内容的提问来源于stack exchange,提问作者Patrik Mihalčin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:26:00