跳转到内容

Spring数据查询

来自代码酷

Spring数据查询[编辑 | 编辑源代码]

概述[编辑 | 编辑源代码]

Spring数据查询是Spring Data模块的核心功能之一,它允许开发者通过简单的方法命名约定、注解或自定义查询语言(如JPQL、Native SQL)来访问和操作数据库。Spring Data提供了统一的抽象层,支持多种数据存储技术(如JPA、MongoDB、Redis等),显著减少了样板代码的编写。

Spring数据查询的主要特点包括:

  • 方法名派生查询:通过解析方法名自动生成查询
  • @Query注解:支持JPQL或原生SQL
  • 动态查询:通过`Specification`或`QueryDSL`实现复杂条件组合
  • 分页与排序:内置支持

查询方法类型[编辑 | 编辑源代码]

方法名派生查询[编辑 | 编辑源代码]

Spring Data可以通过解析Repository接口中的方法名自动生成查询。规则如下:

public interface UserRepository extends JpaRepository<User, Long> {
    // 根据属性名查询
    List<User> findByLastName(String lastName);
    
    // 多条件查询
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    
    // 排序
    List<User> findByAgeGreaterThan(int age, Sort sort);
    
    // 分页
    Page<User> findByActiveTrue(Pageable pageable);
}

命名约定:

  • 关键字:`findBy`、`readBy`、`queryBy`、`countBy`、`deleteBy`
  • 条件:`And`、`Or`、`Between`、`LessThan`、`Like`、`IgnoreCase`
  • 排序:`OrderBy[属性]Asc/Desc`

@Query注解[编辑 | 编辑源代码]

对于复杂查询,可以使用`@Query`注解定义JPQL或原生SQL:

@Query("SELECT u FROM User u WHERE u.email LIKE %?1%")
List<User> findByEmailContaining(String emailPart);

@Query(value = "SELECT * FROM users WHERE registration_date > ?1", nativeQuery = true)
List<User> findRegisteredAfter(Date date);

动态查询[编辑 | 编辑源代码]

Specification接口[编辑 | 编辑源代码]

实现`JpaSpecificationExecutor`接口后,可以使用`Specification`构建动态查询:

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}

// 使用示例
Specification<User> spec = (root, query, cb) -> {
    List<Predicate> predicates = new ArrayList<>();
    if (name != null) {
        predicates.add(cb.like(root.get("name"), "%" + name + "%"));
    }
    if (minAge != null) {
        predicates.add(cb.ge(root.get("age"), minAge));
    }
    return cb.and(predicates.toArray(new Predicate[0]));
};
List<User> users = userRepository.findAll(spec);

QueryDSL集成[编辑 | 编辑源代码]

更类型安全的动态查询方案:

QUser user = QUser.user;
BooleanExpression predicate = user.firstName.contains("John")
                            .and(user.age.between(20, 30));
Iterable<User> users = userRepository.findAll(predicate);

分页与排序[编辑 | 编辑源代码]

Spring Data提供开箱即用的分页支持:

Pageable pageable = PageRequest.of(0, 10, Sort.by("lastName").ascending());
Page<User> page = userRepository.findByActiveTrue(pageable);

// 获取结果
List<User> content = page.getContent();
int totalPages = page.getTotalPages();

投影与DTO[编辑 | 编辑源代码]

可以定义接口投影或类投影来返回部分字段:

// 接口投影
public interface NameOnly {
    String getFirstName();
    String getLastName();
}

List<NameOnly> users = userRepository.findByDepartment("IT");

// 类投影
@Query("SELECT new com.example.UserDto(u.firstName, u.lastName) FROM User u")
List<UserDto> findUserDtos();

性能优化[编辑 | 编辑源代码]

实体图[编辑 | 编辑源代码]

通过`@EntityGraph`定义关联加载策略:

@EntityGraph(attributePaths = {"addresses", "roles"})
List<User> findAllWithAddressesAndRoles();

批量处理[编辑 | 编辑源代码]

使用`@Modifying`进行批量更新:

@Modifying
@Query("UPDATE User u SET u.active = ?2 WHERE u.id IN ?1")
int updateActiveStatus(List<Long> ids, boolean active);

实际案例[编辑 | 编辑源代码]

电商平台商品搜索[编辑 | 编辑源代码]

graph TD A[前端请求] --> B(Controller) B --> C{参数解析} C -->|简单条件| D[方法名派生查询] C -->|复杂条件| E[Specification构建] D & E --> F[Repository执行] F --> G[返回分页结果]

实现代码:

public Page<Product> searchProducts(String name, 
                                   BigDecimal minPrice, 
                                   BigDecimal maxPrice, 
                                   String category,
                                   Pageable pageable) {
    Specification<Product> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (StringUtils.hasText(name)) {
            predicates.add(cb.like(root.get("name"), "%" + name + "%"));
        }
        if (minPrice != null) {
            predicates.add(cb.ge(root.get("price"), minPrice));
        }
        if (maxPrice != null) {
            predicates.add(cb.le(root.get("price"), maxPrice));
        }
        if (StringUtils.hasText(category)) {
            predicates.add(cb.equal(root.get("category").get("name"), category));
        }
        return cb.and(predicates.toArray(new Predicate[0]));
    };
    return productRepository.findAll(spec, pageable);
}

数学表达式示例[编辑 | 编辑源代码]

Spring Data JPA中的分页总页数计算:

totalPages=totalElementspageSize

其中:

  • totalElements = 总记录数
  • pageSize = 每页大小

总结[编辑 | 编辑源代码]

Spring数据查询提供了多种灵活的方式来访问数据:

  • 简单查询优先使用方法名派生
  • 复杂查询使用`@Query`或`Specification`
  • 动态查询考虑`QueryDSL`
  • 性能敏感场景使用投影和实体图
  • 大数据量操作使用批量处理

通过合理组合这些技术,可以在保持代码简洁的同时满足各种业务查询需求。