如何通过Spring JPQL基于非空DTO字段实现图书查询过滤?
基于DTO非空字段动态过滤图书的JPQL实现方案
1. 前置说明
假设你的Book实体类与提供的BookDTO字段一一对应(数据库表books映射到Book实体),先确保实体类定义如下:
@Entity @Table(name = "books") public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String title; private String isbn; private String author; private int pages; private String size; // 省略getter、setter及构造方法 }
2. 动态构建JPQL查询(推荐方案)
由于DTO的非空字段是动态变化的,需要手动拼接JPQL的WHERE条件,仅将非空字段作为过滤条件。可以在业务层或自定义Repository中实现:
实现代码(使用EntityManager)
@Service public class BookService { @PersistenceContext private EntityManager entityManager; public List<Book> filterBooks(BookDTO dto) { // 初始WHERE条件设为1=1,方便后续拼接AND条件 StringBuilder jpql = new StringBuilder("SELECT b FROM Book b WHERE 1=1"); List<Object> params = new ArrayList<>(); // 处理字符串类型字段:非空且非空白字符串才加入过滤 if (dto.getName() != null && !dto.getName().trim().isEmpty()) { jpql.append(" AND b.name = ?"); params.add(dto.getName()); } if (dto.getTitle() != null && !dto.getTitle().trim().isEmpty()) { jpql.append(" AND b.title = ?"); params.add(dto.getTitle()); } if (dto.getIsbn() != null && !dto.getIsbn().trim().isEmpty()) { jpql.append(" AND b.isbn = ?"); params.add(dto.getIsbn()); } if (dto.getAuthor() != null && !dto.getAuthor().trim().isEmpty()) { jpql.append(" AND b.author = ?"); params.add(dto.getAuthor()); } if (dto.getSize() != null && !dto.getSize().trim().isEmpty()) { jpql.append(" AND b.size = ?"); params.add(dto.getSize()); } // 处理int类型pages:注意int默认值为0,若需区分"未传入"和"传入0",建议将DTO的pages改为Integer类型 // 此处假设pages>0时才作为过滤条件 if (dto.getPages() > 0) { jpql.append(" AND b.pages = ?"); params.add(dto.getPages()); } // 绑定参数并执行查询 TypedQuery<Book> query = entityManager.createQuery(jpql.toString(), Book.class); for (int i = 0; i < params.size(); i++) { query.setParameter(i + 1, params.get(i)); } return query.getResultList(); } }
3. 简化方案:结合Spring Data JPA的@Query与SpEL
如果仅需等值匹配,可通过SpEL表达式在@Query中动态判断字段是否有效,无需手动拼接JPQL:
public interface BookRepository extends JpaRepository<Book, Long> { @Query("SELECT b FROM Book b WHERE " + "(:#{#dto.name == null or #dto.name.trim().isEmpty()} OR b.name = :#{#dto.name}) " + "AND (:#{#dto.title == null or #dto.title.trim().isEmpty()} OR b.title = :#{#dto.title}) " + "AND (:#{#dto.isbn == null or #dto.isbn.trim().isEmpty()} OR b.isbn = :#{#dto.isbn}) " + "AND (:#{#dto.author == null or #dto.author.trim().isEmpty()} OR b.author = :#{#dto.author}) " + "AND (:#{#dto.pages <= 0} OR b.pages = :#{#dto.pages}) " + "AND (:#{#dto.size == null or #dto.size.trim().isEmpty()} OR b.size = :#{#dto.size})") List<Book> findByFilter(@Param("dto") BookDTO dto); }
这种写法更简洁,但灵活性不足,若需模糊查询(如LIKE)或复杂条件,还是手动拼接JPQL更合适。
关键注意点
- 基本类型字段问题:DTO中的
pages是int类型,默认值为0,无法区分“未传入”和“传入0”,建议将其改为Integer包装类,通过dto.getPages() != null判断是否过滤。 - 防SQL注入:必须使用参数绑定(
?或命名参数),禁止直接拼接字段值到JPQL中。 - 模糊查询扩展:若需要模糊匹配,只需将JPQL中的
=改为LIKE,并给参数添加通配符,例如params.add("%" + dto.getName() + "%")。
内容的提问来源于stack exchange,提问作者Ahmed M. Elzubair
相关产品推荐
相关产品推荐

