共207行
2026-07-20 11:36:31
在 Spring Data JPA 中使用原生 SQL 进行多表关联查询并分页,主要有两种方式:
@Query 注解(推荐)public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
@Query(value = """
SELECT u.id, u.name, o.order_no, o.amount, p.product_name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_products op ON o.id = op.order_id
LEFT JOIN products p ON op.product_id = p.id
WHERE u.status = :status
AND (:name IS NULL OR u.name LIKE CONCAT('%', :name, '%'))
ORDER BY u.create_time DESC
""",
countQuery = """
SELECT COUNT(*)
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_products op ON o.id = op.order_id
LEFT JOIN products p ON op.product_id = p.id
WHERE u.status = :status
AND (:name IS NULL OR u.name LIKE CONCAT('%', :name, '%'))
""",
nativeQuery = true)
Page<Object[]> findUserOrderInfo(@Param("status") Integer status,
@Param("name") String name,
Pageable pageable);
}@Service
public class UserService {
public Page<UserOrderDTO> getUsersWithOrders(Integer status, String name, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "u.createTime"));
Page<Object[]> result = userRepository.findUserOrderInfo(status, name, pageable);
return result.map(row -> {
UserOrderDTO dto = new UserOrderDTO();
dto.setUserId(((Number) row[0]).longValue());
dto.setUserName((String) row[1]);
dto.setOrderNo((String) row[2]);
dto.setAmount((BigDecimal) row[3]);
dto.setProductName((String) row[4]);
return dto;
});
}
}EntityManager + Query(灵活可控)@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Page<UserOrderDTO> searchUserOrders(UserSearchCriteria criteria, Pageable pageable) {
// 构建基础 SQL
StringBuilder selectSql = new StringBuilder("""
SELECT u.id, u.name, o.order_no, o.amount, p.product_name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_products op ON o.id = op.order_id
LEFT JOIN products p ON op.product_id = p.id
WHERE 1=1
""");
StringBuilder countSql = new StringBuilder("""
SELECT COUNT(*)
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_products op ON o.id = op.order_id
LEFT JOIN products p ON op.product_id = p.id
WHERE 1=1
""");
// 动态条件
List<String> conditions = new ArrayList<>();
Map<String, Object> params = new HashMap<>();
if (criteria.getStatus() != null) {
conditions.add("AND u.status = :status");
params.put("status", criteria.getStatus());
}
if (StringUtils.hasText(criteria.getName())) {
conditions.add("AND u.name LIKE CONCAT('%', :name, '%')");
params.put("name", criteria.getName());
}
String conditionStr = String.join(" ", conditions);
selectSql.append(conditionStr).append(" ORDER BY u.create_time DESC");
countSql.append(conditionStr);
// 查询总数
Query countQuery = entityManager.createNativeQuery(countSql.toString());
params.forEach(countQuery::setParameter);
long total = ((Number) countQuery.getSingleResult()).longValue();
// 查询分页数据
Query selectQuery = entityManager.createNativeQuery(selectSql.toString());
params.forEach(selectQuery::setParameter);
selectQuery.setFirstResult((int) pageable.getOffset());
selectQuery.setMaxResults(pageable.getPageSize());
List<Object[]> rows = selectQuery.getResultList();
List<UserOrderDTO> dtos = rows.stream().map(this::convertToDTO).collect(Collectors.toList());
return new PageImpl<>(dtos, pageable, total);
}
private UserOrderDTO convertToDTO(Object[] row) {
UserOrderDTO dto = new UserOrderDTO();
dto.setUserId(((Number) row[0]).longValue());
dto.setUserName((String) row[1]);
dto.setOrderNo((String) row[2]);
dto.setAmount((BigDecimal) row[3]);
dto.setProductName((String) row[4]);
return dto;
}
}
// 自定义 Repository 接口
public interface CustomUserRepository {
Page<UserOrderDTO> searchUserOrders(UserSearchCriteria criteria, Pageable pageable);
}
// 主 Repository 继承自定义接口
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
}countQuery 必须提供原生 SQL 分页时,JPA 无法自动推断 COUNT 语句,必须手动提供 countQuery,否则会报错。
Object[]:最灵活,但需要手动转换@SqlResultSetMapping + @ConstructorResult@SqlResultSetMapping(
name = "UserOrderMapping",
classes = @ConstructorResult(
targetClass = UserOrderDTO.class,
columns = {
@ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "name", type = String.class),
@ColumnResult(name = "order_no", type = String.class),
@ColumnResult(name = "amount", type = BigDecimal.class),
@ColumnResult(name = "product_name", type = String.class)
}
)
)然后在 @Query 中使用:
@Query(nativeQuery = true, resultSetMapping = "UserOrderMapping")
Page<UserOrderDTO> findUserOrderInfo(...);确保 application.yml 配置了正确的数据库方言:
spring:
jpa:
database-platform: org.hibernate.dialect.MySQL8DialectWHERE 条件和 ORDER BY 字段上加联合索引SELECT *,只查需要的字段src/main/java/com/example/
├── entity/
│ ├── User.java
│ ├── Order.java
│ └── Product.java
├── repository/
│ ├── UserRepository.java
│ ├── CustomUserRepository.java
│ └── CustomUserRepositoryImpl.java
├── dto/
│ └── UserOrderDTO.java
└── service/
└── UserService.java