Spring JPA DTO 投影和处理具有空值的嵌套投影

Spring JPA DTO projection and handling the nested projection with null values

我正在使用基于 class 的投影和构造函数表达式。这是我工作中的示例代码

@Query("select new com.core.data.category.CategoryDto(c.id,c.code,c.externalCode,c.seoMeta, c.createdAt, c.updatedAt,c.parent.code) FROM Category c where c.code = :code")
CategoryDto findCategoryByCode(@Param("code") String code); 

这是我的 CategoryDto 的样子:

public class CategoryDto implements Serializable {
 
private Long id;
private String code;
private String externalCode;
private SEOMeta seoMeta;
private CategoryDto parent;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
Map<String, LocCategoryDto> translation;

//constructor based on the requirement
}

除了嵌套对象的 属性 为 null 的一种情况外,这似乎工作正常。在我的例子中,父 属性 可以是 null,以防这是一个根类别,但它似乎使用 c.parent.code 导致了问题并且整个对象即将出现 null。有人可以帮我解决以下问题

  1. 有没有办法使用相同的构造函数表达式来处理这种情况?我试图查看文档,但没有找到详细信息。
  2. 我认为其他选项可能正在使用 ResultTransformer(它将我的代码绑定到特定的 JPA)但是我没有找到任何关于如何将它与 Spring JPA 一起使用的信息。

更新 我什至尝试了使用 CASE 选项的选项,但似乎这对我也不起作用,因为我仍然得到空实体(虽然数据在数据库中可用)。这是我尝试过的更新代码

@Query(value = "select new com.core.data.category.CategoryDto(c.id,c.code,c.externalCode,c.seoMeta, c.createdAt, c.updatedAt, " +
            "CASE " +
            "WHEN  c.parent is NULL " +
            "THEN NULL " +
            "ELSE c.parent.code " +
            "END ) " +
            "FROM Category c where c.code = :code")
    CategoryDto findCategoryByCode(@Param("code") String code);

编辑 2 我也尝试过加入,但这似乎也不起作用。

更新:我做了一个愚蠢的 mistake.Was 使用简单连接而不是左连接导致了这个问题。

尝试使用左连接

@Query("select new com.core.data.category.CategoryDto(c.id,c.code,c.externalCode,c.seoMeta, c.createdAt, c.updatedAt,parent.code) FROM Category c left join c.parent as parent where c.code = :code")
CategoryDto findCategoryByCode(@Param("code") String code); 

我怀疑您的问题完全不同,因为使用左外连接可以解决您描述的问题。

将您的查询更改为:

select new com.core.data.category.CategoryDto(c.id,c.code,c.externalCode,c.seoMeta, c.createdAt, c.updatedAt, p.code) 
FROM Category c
LEFT JOIN c.parent p
WHERE c.code = :code

我创建了一个reproducer demonstrating that an outer join fixes the problem

相关代码:

@Entity
class SomeEntity {
    @Id
    @GeneratedValue
    Long id;

    String name;

    @ManyToOne
    SomeEntity parent;
}
public class Dto {

    final String name;
    final String parentName;

    public Dto(String name, String parentName) {
        this.name = name;
        this.parentName = parentName;
    }

    @Override
    public String toString() {
        return name + " - " + parentName;
    }
}
public interface SomeEntityRepository extends JpaRepository<SomeEntity, Long> {

    @Query("select new de.schauderhaft.de.constructorexpressionwithnestedreference.Dto(e.name, p.name) " +
            "from SomeEntity e " +
            "left join e.parent p")
    List<Dto> findDto();

    @Query("select new de.schauderhaft.de.constructorexpressionwithnestedreference.Dto(e.name, e.parent.name) " +
            "from SomeEntity e")
    List<Dto> findDtoInnerJoin();

    @Query("select e from SomeEntity e")
    List<SomeEntity> findEntities();
}
@SpringBootTest
class ConstructorExpressionWithNestedReferenceApplicationTests {

    @Autowired
    SomeEntityRepository ents;

    @Test
    @Transactional
    void testDtos() {

        createEnts();

        assertThat(ents.findDto()).extracting(Dto::toString).containsExactlyInAnyOrder("ents name - parents name", "parents name - null");

    }

    @Test
    @Transactional
    void testDtosInnerJoin() {

        createEnts();

        assertThat(ents.findDtoInnerJoin()).extracting(Dto::toString).containsExactly("ents name - parents name");

    }

    @Test
    @Transactional
    void testEntities() {

        createEnts();

        assertThat(ents.findEntities()).extracting(e -> e.name).containsExactlyInAnyOrder("ents name", "parents name");

    }

    private void createEnts() {

        SomeEntity ent = new SomeEntity();
        ent.name = "ents name";
        ent.parent = new SomeEntity();
        ent.parent.name = "parents name";


        ents.saveAll(asList(ent, ent.parent));
    }

}