
在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;
}当我们需要基于嵌入式实体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>。
在我们的例子中,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的正确方式。
通过本文的讲解,我们深入理解了Spring Data JPA在处理嵌入式实体查询时,派生查询方法命名的重要性。正确地将嵌入式实体字段名与目标属性名组合起来,如existsByPersonCpf,是解决PropertyReferenceException的关键。掌握这一命名约定,能够帮助开发者更高效、更准确地利用Spring Data JPA的强大功能,构建健壮的持久层。
以上就是Spring Data JPA 中查询嵌入式实体属性的正确姿势的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号