首页 > Java > java教程 > 正文

Spring Data JPA 中查询嵌入式实体属性的正确姿势

心靈之曲
发布: 2025-09-26 13:40:01
原创
622人浏览过

Spring Data JPA 中查询嵌入式实体属性的正确姿势

本文旨在解决Spring Data JPA在使用existsBy等派生查询方法时,针对嵌入式实体(@Embedded)内部属性进行查询时遇到的PropertyReferenceException问题。核心内容是阐明Spring Data JPA的命名约定,即通过组合嵌入式实体字段名和其内部属性名来构建正确的查询方法,确保框架能够准确解析查询路径,从而实现对嵌入式实体属性的有效过滤。

理解Spring Data JPA与嵌入式实体

在spring data jpa应用中,我们经常使用@embeddable和@embedded注解来构建更清晰、更模块化的实体模型。@embeddable注解用于标记一个类可以被嵌入到其他实体中,而@embedded则用于在实体中引用这个可嵌入类的一个实例。这种设计模式有助于将一组相关属性封装起来,提高代码的内聚性。

例如,在一个牙医管理系统中,Person类可能包含姓名、身份证号(CPF)、生日等个人信息,而Address类则包含地址详情。这些信息可以作为嵌入式对象被Dentist实体引用:

Person.java (嵌入式类)

import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;

@Embeddable
@Data
public class Person {
    @Column(nullable = false, length = 11)
    private String cpf; // 身份证号

    @Column(name = "full_name", nullable = false, length = 60)
    private String fullName; // 姓名

    @Column(nullable = false)
    private String birthdate; // 出生日期

    @Column(name = "email", nullable = true, length = 30)
    private String emailAddress; // 邮箱地址

    @Column(name = "cellphone_number", nullable = true, length = 11)
    private String cellphoneNumber; // 手机号码

    @Embedded
    private Address address; // 嵌入式地址信息 (如果Address也是一个@Embeddable类)
}
登录后复制

Dentist.java (实体类)

import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Entity
@Table(name = "tb_dentists")
public class Dentist implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "dentist_id")
    private UUID id;

    @Column
    private LocalDateTime registrationDate; // 注册日期

    @Column(nullable = false, unique = true, length = 6)
    private String croNumber; // 牙医注册号

    @Embedded // 嵌入Person信息
    private Person person;
}
登录后复制

Spring Data JPA 查询嵌入式实体时遇到的问题

当我们需要基于嵌入式实体Person中的某个属性(例如cpf)来查询Dentist实体时,Spring Data JPA的派生查询方法提供了一种便捷的方式。然而,如果命名不当,可能会遇到org.springframework.data.mapping.PropertyReferenceException错误。

例如,如果尝试在DentistRepository中定义如下方法:

// 错误的示例
@Repository
public interface DentistRepository extends JpaRepository<Dentist, UUID> {
    boolean existsByCroNumber(String croNumber); // 工作正常

    boolean existsByCpf(String cpf); // 导致 PropertyReferenceException
}
登录后复制

并在服务层调用:

// DentistService.java
public boolean existsByPerson_Cpf(String cpf) {
    return dentistRepository.existsByCpf(cpf); // 错误调用
}
登录后复制

Spring会抛出类似Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'cpf' found for type 'Dentist'的异常。这是因为Spring Data JPA在解析existsByCpf时,会直接在Dentist实体中查找名为cpf的属性,而不是person对象下的cpf属性。Dentist实体本身并没有直接的cpf属性,cpf是嵌入在person对象内部的。

解决方案:遵循命名约定查询嵌入式属性

Spring Data JPA为查询嵌入式实体提供了一套明确的命名约定。当需要查询嵌入式实体内部的属性时,查询方法的命名应遵循以下模式:By<EmbeddedEntityFieldName><EmbeddedEntityProperty>。

蓝心千询
蓝心千询

蓝心千询是vivo推出的一个多功能AI智能助手

蓝心千询34
查看详情 蓝心千询

在我们的例子中,Dentist实体中嵌入了Person类型的person字段,而Person实体中包含cpf属性。因此,正确的查询方法名应该是existsByPersonCpf。

DentistRepository.java (正确示例)

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

@Repository
public interface DentistRepository extends JpaRepository<Dentist, UUID> {
    boolean existsByCroNumber(String croNumber);

    // 正确的查询方法:通过嵌入式实体字段名 'person' 和其内部属性名 'cpf' 组合
    boolean existsByPersonCpf(String cpf);
}
登录后复制

更新后的服务层和控制器层将调用这个正确命名的方法:

DentistService.java (更新)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DentistService {

    @Autowired
    DentistRepository dentistRepository;

    public boolean existsByCroNumber(String croNumber) {
        return dentistRepository.existsByCroNumber(croNumber);
    }

    public boolean existsByPersonCpf(String cpf) { // 调用正确命名的方法
        return dentistRepository.existsByPersonCpf(cpf);
    }

    // ... 其他业务方法
}
登录后复制

DentistController.java (更新)

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.ZoneId;

@RestController
@RequestMapping("/dentists")
public class DentistController {

    @Autowired
    DentistService dentistService;

    @PostMapping
    public ResponseEntity<Object> saveDentist(@RequestBody @Valid Dentist dentistDto) { // 接收Dentist对象,Person信息包含在内

        if(dentistService.existsByCroNumber(dentistDto.getCroNumber())) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("CONFLICT: CRO number is already in use!");
        }

        // 从接收的dentistDto中获取嵌入的person对象的cpf
        if(dentistService.existsByPersonCpf(dentistDto.getPerson().getCpf())) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("CONFLICT: CPF number is already in use!");
        }

        var dentistModel = new Dentist();
        BeanUtils.copyProperties(dentistDto, dentistModel);
        dentistModel.setRegistrationDate(LocalDateTime.now(ZoneId.of("UTC")));
        // 保存操作
        return ResponseEntity.status(HttpStatus.CREATED).body(dentistService.save(dentistModel));
    }
}
登录后复制

注意: 在DentistController中,Person对象应该作为Dentist对象的一部分通过@RequestBody接收,而不是单独作为方法参数。dentistDto.getPerson().getCpf()是获取嵌入式Person对象中cpf的正确方式。

注意事项与最佳实践

  1. 严格遵循命名约定: Spring Data JPA的强大之处在于其基于方法名的查询解析能力。对于嵌入式实体,务必遵循By<EmbeddedEntityFieldName><EmbeddedEntityProperty>的命名模式,否则会导致运行时错误。
  2. 多层嵌套嵌入式实体: 如果嵌入式实体也包含其他嵌入式实体,命名约定将进一步扩展。例如,如果Person中嵌入了Address,而Address有street属性,那么查询street的方法可能就是existsByPersonAddressStreet(String street)。
  3. 复杂查询的替代方案: 尽管派生查询方法非常方便,但对于过于复杂或涉及多表关联的查询,考虑使用@Query注解编写JPQL或原生SQL,或者使用Specification API,以提高查询的灵活性和可读性。
  4. 字段类型匹配: 确保查询方法参数的类型与实体中对应属性的类型一致,否则也会导致类型不匹配的错误。

总结

通过本文的讲解,我们深入理解了Spring Data JPA在处理嵌入式实体查询时,派生查询方法命名的重要性。正确地将嵌入式实体字段名与目标属性名组合起来,如existsByPersonCpf,是解决PropertyReferenceException的关键。掌握这一命名约定,能够帮助开发者更高效、更准确地利用Spring Data JPA的强大功能,构建健壮的持久层。

以上就是Spring Data JPA 中查询嵌入式实体属性的正确姿势的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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