Spring Data JPA中使用Java Record作为返回类型时findAll方法冲突及映射问题咨询
我目前使用的是SpringBoot 3.4.2(当前最新版本),可以创建访问Service的RESTful控制器,再通过Repository调用获取数据库数据,这些流程都完全正常。我完全理解大多数情况下不应该直接把Entity返回给RESTful API调用方的原因:有些场景下我们不想返回某些数据,还有些场景需要聚合数据或做计算,这时候就会用到DTO,我完全认可Entity与DTO之间的映射思路。
现在我了解到Java Record可以作为小型数据的DTO使用,据我所知Record是**不可变(immutable)**的。我也读了很多关于Lombok实现的DTO和Record差异的资料。如果想保留现有Repository的结构,我可以自己编写代码实现Entity到Record的映射,但不确定是否应该这么做。从网上的例子来看,Spring Data JPA可以直接把数据加载到Record中,如果字段完全匹配的话映射会很简单;如果数据结构不同也有解决办法,但我还没到那一步。现在我遇到了一个小问题,可能我忽略了某些点,先把相关代码贴出来:
实体类代码
@Entity @Table(name = "company") public class CompanyEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "company_id") private long companyId; @Column(name = "active") private boolean active; @Column(name = "code") private String companyCode; @Column(name = "name") private String companyName; @Column(name = "description") private String description; @Column(name = "address1") private String address1; @Column(name = "address2") private String address2; @Column(name = "city") private String city; @Column(name = "state") private String state; @Column(name = "zip") private String zip; }
Record类代码(字段与实体类完全一致)
public record CompanyRecord(long companyId, boolean active, String companyCode, String companyName, String description, String address1, String address2, String city, String state, String zip) implements Serializable {}
Repository代码
@Repository("CompanyRepository") public interface CompanyRepository extends JpaRepository<CompanyEntity, Long> { // 完全正常,没有问题! public CompanyRecord findByCompanyId(long companyId); // 完全正常,没有问题! public List<CompanyEntity> findAll(); // 这个方法无法正常工作! // IDE报错: // The return type is incompatible with ListCrudRepository<CompanyEntity,Long>.findAll() public List<CompanyRecord> findAll(); // 完全正常,没有问题! List<CompanyRecord> findByCompanyCode(String companyCode); }
这让我困惑,不确定这是JPARepository的问题还是有其他解决办法。更让我疑惑的是JUnit测试的结果:
JUnit测试代码
@Test public void testFindById_Entity() { // 符合预期,没有问题 // 奇怪的是Repository里定义了返回CompanyRecord的findByCompanyId,但这里用findById却能返回Entity long companyId = 1; CompanyEntity companyEntity = companyRepository.findById(companyId).orElse(null); assertNotNull(companyEntity); } @Disabled @Test public void testFindById_Record() { long companyId = 1; // 无法工作,IDE报错: // Type mismatch: cannot convert from CompanyEntity to CompanyRecord // 我原以为这个方法应该能正常工作,因为接口明确声明返回Record CompanyRecord companyRecord = companyRepository.findById(companyId).orElse(null); assertNotNull(companyRecord); } @Test public void testFindByCode_Entity() { String companyCode = "IBM"; // 符合预期无法工作,因为接口定义的findByCompanyCode返回Record List<CompanyEntity> companyEntityList = companyRepository.findByCompanyCode(companyCode); assertNotNull(companyEntityList); } @Test public void testFindByCode() { String companyCode = "IBM"; // 符合预期正常工作,接口声明返回Record List<CompanyRecord> companyRecordList = companyRepository.findByCompanyCode(companyCode); assertNotNull(companyRecordList); } @Test public void testFindAll_Entity() { // 符合预期正常工作 List<CompanyEntity> companyEntityList = companyRepository.findAll(); assertNotNull(companyEntityList); }
我发现这种用法存在很多令人困惑的点:有时候结果符合预期,有时候却不符合,这让我感觉Record似乎还没完全成熟。或者我应该回到传统方式:Repository只处理Entity,在Service层手动创建Record,但我也能看到Spring Data JPA直接使用Record可以减少样板代码。
有没有人有类似的经验,或者能帮我理清Java Record在Spring Data JPA中的使用逻辑?我看过相关资料,但他们的例子大多用了CriteriaQuery,我不想走那条路,平时我通常用HQL处理特定查询,可能我最终也得这么做。
更新 #1
我参考了一些建议,完全理解了Hibernate Entity和Record的使用要求。我也尝试了用JPQL,但虽然编译通过了,JUnit测试却完全失败,因为找不到转换器,报了转换错误:
// 报转换错误 @Query(value = "SELECT c from CompanyEntity c") List<CompanyRecord> findAllCustomerRecords(); // 我觉得这个可能可行,但还没测试 @Query(value = "SELECT new CompanyRecord( c.companyId, c.companyName, ...) from CompanyEntity c") List<CompanyRecord> findAllCustomerRecords();
我知道Record从Java14就出现了,Java16开始正式成为标准,但我工作过的所有团队都从未使用过Record。我开始关注Record是因为在做个人GraphQL项目时,发现它被广泛使用,我原本想按照行业标准方式使用Record,但似乎这种标准还没完全建立起来。
现在我理解了Record存在的意义,但我觉得暂时不需要用Record替换DTO。我们有各种工具可以实现Entity和DTO之间的相互转换,但这些工具似乎还没完全适配Record。我原本希望借助Spring Data JPA自动完成映射,但看起来需要额外的工作量,而且我个人认为这不应该在Repository层做。
感谢大家的解答,我期待更多相关的想法,但目前我打算让Repository只处理Entity,回到使用Lombok实现的DTO,并借助MapStruct或其他工具完成映射。这似乎是更清晰的解决方案,也能减少样板代码。
备注:内容来源于stack exchange,提问作者tjholmes66




