|
对于最新稳定版本,请使用Spring Data Neo4j 8.0.0! |
Spring Data Neo4j 预测
如上所述,投影有两种类型:接口投影和基于DTO的投影。在Spring Data Neo4j中,这两种投影都直接影响哪些属性和关系被传输通过线路传输。因此,如果你处理的节点和实体包含许多属性,而这些属性可能并非所有应用场景都需要。
无论是基于接口还是基于DTO的投影,Spring Data Neo4j都会使用仓库的域类型来构建 查询。 所有可能改变查询的属性的注释都会被考虑。域类型是通过仓库声明定义的类型(给定一个声明,如interface TestRepository 扩展了 CrudRepository<TestEntity, Long>域名类型为测试实体).
基于接口的投影始终是底层域类型的动态代理。访问器名称在此类接口上定义(如getName)必须解析为性质(此处:名称)这些元素存在于投影实体上。这些属性是否在域类型上有访问者并不重要,只要它们可以通过公共的 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();
}
}
尽管可以建模循环投影或指向会创建循环的实体,投影逻辑不会遵循这些循环,只会创建无循环的查询。
多层投影受限于它们应投影的实体。关系属性在这种情况下,属于实体类别,如果应用投影,就需要得到尊重。
投影的数据作
如果你已经将投影作为DTO获取,可以修改其值。 但如果你使用基于接口的投影,你不能仅仅更新界面。 一个典型的模式是在你的域实体类中提供一个方法,消耗接口并创建一个包含从接口复制的值的域实体。 这样,你可以更新实体,并像下一节描述的投影蓝图/遮罩一样再次持久化。
投影的持久性
类似于通过投影检索数据,它们也可以作为持久化的蓝图。
这Neo4j模板提供流畅的 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 | 该性质在图中有不同的名称 |
测试实体@Node
class ExtendedTestEntity extends TestEntity {
private String otherAttribute;
}
测试实体interface TestEntityInterfaceProjection {
String getName();
}
测试实体,包括一个额外的属性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 | 该属性不存在于投影实体中 |
一个测试实体如下所示,且其行为将如同商品列表所述。
测试实体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 | 仓库的域类型为测试实体 |
| 2 | 返回一个或多个的方法测试实体只返回实例,因为它与域类型相符 |
| 3 | 返回一个或多个扩展域类型的类实例的方法只返回实例 扩展阶级的成员。该方法的域类型将是扩展类,其中 仍然满足仓库本身的域类型 |
| 4 | 该方法返回接口投影,因此返回类型不同
来自仓库的域名类型。该接口只能访问域类型中定义的属性。
后缀由是让 SDN 不去寻找接口投影在测试实体 |
| 5 | 该方法返回 DTO 投影。执行该警告会使SDN发出警告,正如DTO所定义的关系数量作为附加属性,但该属性不在域类型的契约中。
注释属性aProperty在测试实体将正确翻译为a_property在查询中。
如上所述,返回类型与仓库的域类型不同。
后缀由是让 SDN 不去寻找DTOProjections在测试实体 |
| 6 | 该方法还返回 DTO 投影。但不会发出警告,因为查询包含配对 投影中定义的额外属性值 |