0

0

Spring Data JPA 复合主键查询策略与最佳实践

聖光之護

聖光之護

发布时间:2025-12-08 08:49:42

|

834人浏览过

|

来源于php中文网

原创

Spring Data JPA 复合主键查询策略与最佳实践

本文深入探讨了spring data jpa在处理复合主键时findbyid()方法的正确使用方式。针对jparepository仅支持单一id类型的限制,教程详细介绍了如何将embeddedid类型作为仓库的id类型,并通过实例展示了使用findbyid()、自定义方法名查询以及jpql @query进行数据检索。同时,强调了处理optional返回值的最佳实践,包括引入优雅的异常处理机制和推荐使用现代日期时间api,以构建健壮且可维护的spring boot应用。

理解 Spring Data JPA 与复合主键

在使用 Spring Data JPA 进行数据操作时,我们经常会遇到需要通过主键查询实体的情况。对于简单的单字段主键,JpaRepository 提供的 findById() 方法能够直接接收主键值并返回对应的 Optional 实体。然而,当实体采用复合主键(由多个字段组成的主键)时,直接尝试将多个字段作为参数传递给 findById() 是行不通的。

这是因为 JpaRepository 的定义 JpaRepository 明确规定了它只能接受一个 ID 类型作为主键类型。因此,findById 方法的设计也只接受一个参数,即该 ID 类型的实例。对于复合主键,我们需要将其封装成一个独立的类,并将其指定为 JpaRepository 的 ID 类型。

正确配置复合主键实体与仓库

首先,确保你的复合主键类被正确地定义为 @Embeddable,并且你的实体类使用 @EmbeddedId 注解来引用这个复合主键。

// 复合主键类
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Embeddable
public class PlansPKId implements Serializable {

    private long planId;

    private Date planDate; // 注意:推荐使用 java.time.* 替代 java.util.Date
}

// 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "plans")
public class Plans {
    @EmbeddedId
    private PlansPKId plansPKId;

    @Column
    private String planName;

    @Column
    private String weekday;

    // ... 其他字段和关联关系
}

接下来,关键在于正确定义你的 JpaRepository。你需要将复合主键类 (PlansPKId) 指定为仓库的 ID 类型:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PlansRepository extends JpaRepository {
    // 仓库现在以 PlansPKId 作为其 ID 类型
}

使用 findById() 查询复合主键实体

一旦 PlansRepository 被正确定义,你就可以使用 findById() 方法了。此时,findById() 需要一个 PlansPKId 实例作为参数,而不是多个单独的字段。

import java.util.Date;
import java.util.Optional;

// ...

public Plans assignPlansToMeds(Long id, Long planId, Date planDate) {
    // ... 获取 Meds 实体,这里也应遵循 Optional 的安全处理
    Meds meds = medsRepo.findById(id)
                        .orElseThrow(() -> MedsNotFoundException.id(id).get()); // 示例:安全处理 Optional

    // 创建复合主键实例
    PlansPKId compositeId = new PlansPKId(planId, planDate);

    // 使用 findById 查询
    Plans plans = plansRepo.findById(compositeId)
                           .orElseThrow(() -> PlansNotFoundException.idAndDate(planId, planDate).get()); // 示例:安全处理 Optional

    // ... 后续业务逻辑
    // medsSet = plans.getAssignedMeds();
    // medsSet.add(meds);
    // plans.setAssignedMeds(medsSet);
    // return plansRepo.save(plans);
    return plans; // 示例返回
}

自定义查询方法

除了使用 findById() 传递 PlansPKId 实例外,你还可以通过自定义仓库方法来实现更灵活的查询。

1. 基于方法名派生查询

Spring Data JPA 能够根据方法名自动生成查询。对于复合主键,你可以通过在方法名中引用复合主键类的字段来构建查询。

import java.util.Date;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PlansRepository extends JpaRepository {
    // 通过方法名派生查询,Spring 会根据 PlansPKId 的 planId 和 planDate 字段生成查询
    Optional findByPlansPKIdPlanIdAndPlansPKIdPlanDate(long planId, Date planDate);
}

调用示例:

// ... 在服务层
Optional optionalPlans = plansRepo.findByPlansPKIdPlanIdAndPlansPKIdPlanDate(planId, planDate);
Plans plans = optionalPlans.orElseThrow(() -> PlansNotFoundException.idAndDate(planId, planDate).get());

2. 使用 @Query 注解定义 JPQL 查询

如果你不喜欢冗长的方法名,或者需要更复杂的查询逻辑,可以使用 @Query 注解编写 JPQL (Java Persistence Query Language) 查询。

import java.util.Date;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface PlansRepository extends JpaRepository {
    @Query("select p from Plans p where p.plansPKId.planId = :planId and p.plansPKId.planDate = :planDate")
    Optional findByCompositeId(@Param("planId") long planId, @Param("planDate") Date planDate);
}

调用示例:

// ... 在服务层
Optional optionalPlans = plansRepo.findByCompositeId(planId, planDate);
Plans plans = optionalPlans.orElseThrow(() -> PlansNotFoundException.idAndDate(planId, planDate).get());

最佳实践与注意事项

1. 安全处理 Optional 返回值

Spring Data JPA 的 findById() 和自定义查询方法通常返回 Optional。直接调用 get() 而不检查 Optional 是否包含值是一个常见的错误,这可能导致 NoSuchElementException。始终使用 orElseThrow()、orElse()、ifPresent() 等方法安全地处理 Optional。

Facet
Facet

Facet.ai是一款AI图像生成和编辑工具,具备实时图像生成和编辑功能

下载

推荐使用 orElseThrow() 结合自定义异常,提供清晰的错误信息。

2. 优雅的错误处理机制

为了提供更好的用户体验和更清晰的错误日志,建议实现一套统一的异常处理机制。以下是一个基于 NotFoundException 抽象类的示例:

import java.util.Map;
import java.util.stream.Collectors;

// 抽象的 NotFoundException 基类
public abstract class NotFoundException extends RuntimeException {

    protected NotFoundException(final String object, final String identifierName, final Object identifier) {
        super(String.format("No %s found with %s %s", object, identifierName, identifier));
    }

    protected NotFoundException(final String object, final Map identifiers) {
        super(String.format("No %s found with %s", object,
                identifiers.entrySet().stream()
                        .map(entry -> String.format("%s %s", entry.getKey(), entry.getValue()))
                        .collect(Collectors.joining(" and "))));
    }
}

针对特定实体创建具体的异常类,并提供静态工厂方法来方便地创建异常实例和 Supplier:

import java.util.Map;
import java.util.function.Supplier;

// PlansNotFoundException
public class PlansNotFoundException extends NotFoundException {

    private PlansNotFoundException(final Map identifiers) {
        super("plans", identifiers);
    }

    public static Supplier idAndDate(final long planId, final Date planDate) {
        return () -> new PlansNotFoundException(Map.of("planId", planId, "planDate", planDate));
    }
}

// MedsNotFoundException
public class MedsNotFoundException extends NotFoundException {

    private MedsNotFoundException(final String identifierName, final Object identifier) {
        super("meds", identifierName, identifier);
    }

    public static Supplier id(final long id) {
        return () -> new MedsNotFoundException("id", id);
    }
}

在服务层使用时,代码将更加简洁和健壮:

// ...
Meds meds = medsRepo.findById(id).orElseThrow(MedsNotFoundException.id(id));
Plans plans = plansRepo.findById(new PlansPKId(planId, planDate))
                       .orElseThrow(PlansNotFoundException.idAndDate(planId, planDate));
// ...

结合 Spring 的 @ControllerAdvice 和 @ExceptionHandler,你可以将这些 NotFoundException 映射到 HTTP 404 状态码,并返回友好的错误消息。

3. 使用现代日期时间 API

在你的复合主键定义中,使用了 java.util.Date。强烈建议改用 java.time 包下的现代日期时间 API,如 LocalDate、LocalDateTime 或 ZonedDateTime。这些类提供了更好的线程安全性、不变性以及更丰富的日期时间操作功能。

例如,将 PlansPKId 中的 Date planDate 替换为 LocalDate planDate:

import java.time.LocalDate; // 导入 LocalDate

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Embeddable
public class PlansPKId implements Serializable {

    private long planId;

    private LocalDate planDate; // 使用 LocalDate
}

这将使你的代码更健壮,并避免 java.util.Date 带来的一些常见问题

总结

处理 Spring Data JPA 中的复合主键需要理解 JpaRepository 的设计哲学。通过将复合主键封装为 @Embeddable 类型并正确配置仓库,你可以有效地使用 findById() 进行查询。此外,Spring Data JPA 还提供了基于方法名派生查询和 @Query 注解的强大功能,以满足更复杂的查询需求。在实际开发中,结合安全处理 Optional 返回值、实现优雅的异常处理机制以及采用现代日期时间 API,能够显著提升应用程序的健壮性、可维护性和用户体验。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

844

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

742

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

740

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

400

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

431

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

0

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.8万人学习

C# 教程
C# 教程

共94课时 | 7.3万人学习

Java 教程
Java 教程

共578课时 | 49.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号