简体   繁体   中英

Spring Data JPA fails to return correct result with complex expression

I try to implement filters for a some entity. As far as I know, Spring doesn't support optional parameters so when no filter is set I check for empty string and when the parameter is set I want to match contains and case insensitive .

Currently, no Product is linked to a Role in the Database, so p.role is always null. That means the second part of the expression ( lower(p.role.name) like concat('%', lower(:role), '%') ) is never true, resulting in an empty set. But the first part ( :role = '' ) should be true so I would expect to get ALL data. But for some reason it doesn't work in this particular case.

ProductRepository.java :

@RepositoryRestResource(collectionResourceRel = "product", path = "/product")
public interface ProductRepository extends JpaRepository<Product, Long> {
    @RestResource(path="filter", rel="filter")
    @Query("select p from Product p where"
        + "(:vendor = '' or lower(p.vendor.name) like concat('%', lower(:vendor), '%'))"
        + "and"
        + "(:role = '' or lower(p.role.name) like concat('%', lower(:role), '%'))"
    Page<Product> filterProduct(@Param("role") String role, Pageable pageable);
}

Product.java :

@Entity
public class Product {
    @ManyToOne
    @JoinColumn(name = "role_id", referencedColumnName = "id")
    private Role role;

    @ManyToOne
    @JoinColumn(name = "vendor_id", referencedColumnName = "id")
    private Vendor vendor;
}

Role.java :

@Entity
public class Role {
    private Long id;
    private String name;
}

Vendor.java :

@Entity
public class Vendor {
    private Long id;
    private String name;

My Tests

For testing I replaced the second part of the expression with a statement that is always true or false ( 1=1 or 1=2 ).

ProductRepository.java :

@RepositoryRestResource(collectionResourceRel = "product", path = "/product")
public interface ProductRepository extends JpaRepository<Product, Long> {
    @RestResource(path="filter", rel="filter")
    @Query("select p from Product p where"
        + "(:role = '' or 1=2)"
    Page<Product> filterProduct(@Param("role") String role, Pageable pageable);
}

Case 1=1 :

It doesn't matter if I provide the role parameter or not, I always get ALL products.

Case 1=2 :

If I do not provide the role parameter (eg http://localhost/product/search/filter?role= ) I get ALL products because :role = '' evaluates to true .

If I do provide the role parameter (eg http://localhost/product/search/filter?role=foo ) I get an empty set because no role with the name 'foo' exists.

I thought that maybe because p.role is null trying to match p.role.name results in an error, but there is no error message and I also tried to include p.role is not null and ... in the expression but with no change.

Summary :

  • :role = '' alone works as expected.
  • :role = '' combined with a simple expression works.
  • :role = '' combined with the expression I want does not work.

You can't directly access associated entity columns using dot notation. Declare the join with alias to access:

@RestResource(path="filter", rel="filter")
@Query("select p from Product p LEFT JOIN p.role r LEFT JOIN p.vendor v where"
    + "(:vendor = '' or lower(v.name) like concat('%', lower(:vendor), '%'))"
    + "and"
    + "(:role = '' or lower(r.name) like concat('%', lower(:role), '%'))")
Page<Product> filterProduct(@Param("vendor") String vendor, @Param("role") String role);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM