You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Data JPA中使用Java Record作为返回类型时findAll方法冲突及映射问题咨询

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

火山引擎 最新活动