Spring Data Neo4j 投影

如上所述,投影有两种形式:基于接口和基于 DTO 的投影。在 Spring Data Neo4j 中,这两种类型的投影都会直接影响哪些属性和关系通过网络传输。因此,如果您正在处理包含许多属性的节点和实体,而这些属性可能在应用程序的所有使用场景中都不需要,那么这两种方法都可以减少数据库的负载。

对于基于接口和基于 DTO 的投影,Spring Data Neo4j 将使用存储库的域类型来构建查询。所有可能更改查询的所有属性上的注释都将被考虑在内。域类型是通过存储库声明定义的类型(如果声明类似于 interface TestRepository extends CrudRepository<TestEntity, Long>,则域类型将为 TestEntity)。

基于接口的投影将始终是底层域类型的动态代理。在此类接口上定义的访问器名称(如 getName)必须解析为投影实体上存在的属性(此处为:name)。这些属性在域类型上是否具有访问器与否无关紧要,只要可以通过通用的 Spring Data 基础设施访问即可。后者已得到保证,因为域类型本身首先并不是一个持久实体。

基于 DTO 的投影在与自定义查询一起使用时更加灵活。虽然标准查询是从原始域类型派生的,因此只能使用在那里定义的属性和关系,但自定义查询可以添加其他属性。

规则如下:首先,使用域类型的属性填充 DTO。如果 DTO 通过访问器或字段声明了其他属性,则 Spring Data Neo4j 会在结果记录中查找匹配的属性。属性必须完全按名称匹配,并且可以是简单类型(如 org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes 中定义的)或已知的持久实体。支持这些类型的集合,但不支持映射。

多级投影

Spring Data Neo4j 也支持多级投影。

多级投影示例
interface ProjectionWithNestedProjection {

    String getName();

    List<Subprojection1> getLevel1();

    interface Subprojection1 {
        String getName();
        List<Subprojection2> getLevel2();
    }

    interface Subprojection2 {
        String getName();
    }
}

即使可以建模循环投影或指向将创建循环的实体,投影逻辑也不会遵循这些循环,而只会创建无循环查询。

多级投影受限于它们应该投影的实体。在这种情况下,RelationshipProperties 属于实体类别,如果应用投影,则需要对其进行尊重。

投影的数据操作

如果您已将投影作为 DTO 获取,则可以修改其值。但是,如果您使用的是基于接口的投影,则不能仅仅更新接口。可以使用的一种典型模式是在域实体类中提供一个方法,该方法使用接口并使用从接口复制的值创建一个域实体。这样,您就可以更新实体并使用下一节中描述的投影蓝图/掩码再次将其持久化。

投影的持久化

类似于通过投影检索数据,它们也可以用作持久化的蓝图。Neo4jTemplate 提供了一个流畅的 API 将这些投影应用于保存操作。

您可以为给定的域类保存投影

为给定的域类保存投影
Projection projection = neo4jTemplate.save(DomainClass.class).one(projectionValue);

或者您可以保存域对象,但仅尊重投影中定义的字段。

使用给定的投影蓝图保存域对象
Projection projection = neo4jTemplate.saveAs(domainObject, Projection.class);

在这两种情况下,也适用于基于集合的操作,只有投影中定义的字段和关系才会被更新。

为了防止数据删除(例如关系的移除),您应该始终至少加载以后应该持久化的所有数据。

完整示例

给定以下实体、投影和相应的存储库

一个简单的实体
@Node
class TestEntity {
    @Id @GeneratedValue private Long id;

    private String name;

    @Property("a_property") (1)
    private String aProperty;
}
1 此属性在图中具有不同的名称
一个派生实体,继承自 TestEntity
@Node
class ExtendedTestEntity extends TestEntity {

    private String otherAttribute;
}
TestEntity 的接口投影
interface TestEntityInterfaceProjection {

    String getName();
}
TestEntity 的 DTO 投影,包括一个额外的属性
class TestEntityDTOProjection {

    private String name;

    private Long numberOfRelations; (1)

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getNumberOfRelations() {
        return numberOfRelations;
    }

    public void setNumberOfRelations(Long numberOfRelations) {
        this.numberOfRelations = numberOfRelations;
    }
}
1 此属性在投影实体上不存在

下面显示了一个 TestEntity 的存储库,它将按照列表中的说明进行操作。

TestEntity 的存储库
interface TestRepository extends CrudRepository<TestEntity, Long> { (1)

    List<TestEntity> findAll(); (2)

    List<ExtendedTestEntity> findAllExtendedEntities(); (3)

    List<TestEntityInterfaceProjection> findAllInterfaceProjectionsBy(); (4)

    List<TestEntityDTOProjection> findAllDTOProjectionsBy(); (5)

    @Query("MATCH (t:TestEntity) - [r:RELATED_TO] -> () RETURN t, COUNT(r) AS numberOfRelations") (6)
    List<TestEntityDTOProjection> findAllDTOProjectionsWithCustomQuery();
}
1 存储库的域类型是 TestEntity
2 返回一个或多个 TestEntity 的方法将只返回其实例,因为它与域类型匹配。
3 返回一个或多个扩展域类型类的实例的方法将只返回扩展类的实例。相关方法的域类型将是扩展类,这仍然满足存储库本身的域类型。
4 此方法返回一个接口投影,因此方法的返回类型与存储库的域类型不同。该接口只能访问域类型中定义的属性。需要后缀 By 以使 SDN 不在 TestEntity 中查找名为 InterfaceProjections 的属性。
5 此方法返回一个 DTO 投影。执行它将导致 SDN 发出警告,因为 DTO 将 numberOfRelations 定义为附加属性,该属性不在域类型的契约中。TestEntity 中带注释的属性 aProperty 将在查询中正确转换为 a_property。与上面一样,返回类型与存储库的域类型不同。需要后缀 By 以使 SDN 不在 TestEntity 中查找名为 DTOProjections 的属性。
6 此方法也返回一个 DTO 投影。但是,不会发出警告,因为查询包含投影中定义的附加属性的合适值。
虽然 上面列表中 的存储库使用具体返回类型来定义投影,但另一种变体是使用 动态投影,如 Spring Data Neo4j 与其他 Spring Data 项目共享的文档部分中所述。动态投影可以应用于封闭和开放接口投影以及基于类的 DTO 投影。

动态投影的关键是将所需的投影类型指定为存储库中查询方法的最后一个参数,例如:<T> Collection<T> findByName(String name, Class<T> type)。这是一种可以添加到上面 TestRepository 中的声明,允许通过相同的方法检索不同的投影,而无需在多个方法上重复可能的 @Query 注解。