Spring Data Neo4j Projections
如上所述,投影有两种类型:界面和基于 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
中定义的)或已知的持久实体。支持它们的集合,但不支持映射。
Multi-level projections
Spring Data Neo4j 还支持多级投影。
interface ProjectionWithNestedProjection {
String getName();
List<Subprojection1> getLevel1();
interface Subprojection1 {
String getName();
List<Subprojection2> getLevel2();
}
interface Subprojection2 {
String getName();
}
}
即使可以建模循环投影或指向将创建循环的实体,投影逻辑也不会遵循这些循环,而只会创建无循环查询。
多级投影绑定到它们应该投射到的实体。在这种情况下,RelationshipProperties
属于实体类别,并且在应用投影时需要予以尊重。
Data manipulation of projections
如果你已将投影获取为 DTO,则可以修改其值。但是,如果你使用基于界面的投影,则不能只更新该界面。一个可以使用的典型模式是在你的域实体类中提供一个方法,该方法使用该界面并用从该界面复制的值创建一个域实体。通过这种方式,你随后可以更新该实体并使用下一节中描述的投影蓝图/掩码再次持久化该实体。
Persistence of projections
类似于通过投影检索数据,它们也可以用作持久性的蓝图。Neo4jTemplate
提供了一个流畅的 API 来对保存操作应用这些投影。
你可以为给定的域类保存投影
Projection projection = neo4jTemplate.save(DomainClass.class).one(projectionValue);
或者你可以保存一个域对象,但只保留在投影中定义的字段。
Projection projection = neo4jTemplate.saveAs(domainObject, Projection.class);
在这两种情况下,也可以用于基于集合的操作,只有投影中定义的字段和关系才会得到更新。
为防止数据删除(例如,删除关系),您应始终至少加载所有随后应获取持久性的数据。 |
A full example
给定以下实体、投影和相应的库:
@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
, including one additional attributeclass 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 | 存储库的 domain 类型是 TestEntity |
下面显示了 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 的方法只会返回其实例,因为它与 domain 类型匹配 |
2 | 返回扩展 domain 类型的一个或多个类的实例的方法只会返回扩展类的实例。该方法的 domain 类型将是扩展的类,它仍然满足存储库本身的 domain 类型 |
3 | 此方法返回一个界面投影,因此该方法的返回类型不同于存储库的 domain 类型。该界面只能访问在 domain 类型中定义的属性。后缀 By 是必需的,以便使 SDN 不在 TestEntity 中查找名为 InterfaceProjections 的属性 |
4 | 此方法返回一个 DTO 投影。执行它将导致 SDN 发出警告,因为该 DTO 将 numberOfRelations 定义为附加属性,它不在 domain 类型的约定中。 |
5 | TestEntity 中带注解的属性 aProperty 将在查询中正确地转换为 a_property 。如上所述,返回类型不同于存储库的 domain 类型。后缀 By 是必需的,以便使 SDN 不在 TestEntity 中查找名为 DTOProjections 的属性 |
6 | 此方法还返回一个 DTO 投影。但是,由于该查询包含一个适合该投影中定义的附加属性的值,因此不会发出警告 |
虽然 the listing above 中的存储库使用一个具体的返回类型来定义投影,但另一个变体是使用 dynamic projections,如 Spring Data Neo4j 与其他 Spring Data 项目共享的文档部分中所解释的那样。动态投影可以应用于封闭和开放接口投影以及基于类的 DTO 投影:动态投影的关键在于将所需的投影类型指定为存储库中查询方法的最后一个参数,如下所示: |