Spring Data Neo4j Projections
如上所述,投影有两种类型:界面和基于 DTO 的投影。在 Spring Data Neo4j 中,这两种类型的投影对通过线路传输的属性和关系有直接影响。因此,如果你处理的节点和实体包含许多在应用程序中的所有使用场景中可能不需要的属性,那么这两种方法都可以减少数据库上的负载。
As stated above, projections come in two flavors: Interface and DTO based projections. In Spring Data Neo4j both types of projections have a direct influence which properties and relationships are transferred over the wire. Therefore, both approaches can reduce the load on your database in case you are dealing with nodes and entities containing lots of properties which might not be needed in all usage scenarios in your application.
对于基于界面和基于 DTO 的投影,Spring Data Neo4j 将使用库的域类型来构建查询。将考虑所有可能更改查询的属性上的所有注释。域类型是已通过库声明定义的类型(对于类似于 interface TestRepository extends CrudRepository<TestEntity, Long>
的声明,域类型将是 TestEntity
)。
For both interface and DTO based projections, Spring Data Neo4j will use the repository’s domain type for building the
query. All annotations on all attributes that might change the query will be taken in consideration.
The domain type is the type that has been defined through the repository declaration
(Given a declaration like interface TestRepository extends CrudRepository<TestEntity, Long>
the domain type would be
TestEntity
).
基于界面的投影将始终是对底层域类型的动态代理。在此类界面上定义的访问器的名称(例如 getName
)必须解析为投影实体上存在的属性(此处:name
)。这些属性在域类型上是否有访问器并不重要,只要可以通过公共 Spring Data 基础结构访问它们。后者已经得到保证,因为域类型一开始就不是一个持久实体。
Interface based projections will always be dynamic proxies to the underlying domain type. The names of the accessors defined
on such interfaces (like getName
) must resolve to properties (here: name
) that are present on the projected entity.
Whether those properties have accessors or not on the domain type is not relevant, as long as they can be accessed through
the common Spring Data infrastructure. The latter is already ensured, as the domain type wouldn’t be a persistent entity in
the first place.
基于 DTO 的投影在与自定义查询一起使用时更加灵活。虽然标准查询是从原始域类型导出的,因此只能使用在此处定义的属性和关系,但自定义查询可以添加其他属性。
DTO based projections are somewhat more flexible when used with custom queries. While the standard query is derived from the original domain type and therefore only the properties and relationship being defined there can be used, custom queries can add additional properties.
规则如下:首先,域类型的属性用于填充 DTO。如果 DTO 声明了其他属性(通过访问器或字段),则 Spring Data Neo4j 将在结果记录中查找匹配的属性。属性必须通过名称完全匹配,并且可以是简单类型(如 org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes
中定义的)或已知的持久实体。支持它们的集合,但不支持映射。
The rules are as follows: first, the properties of the domain type are used to populate the DTO. In case the DTO declares
additional properties - via accessors or fields - Spring Data Neo4j looks in the resulting record for matching properties.
Properties must match exactly by name and can be of simple types (as defined in org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes
)
or of known persistent entities. Collections of those are supported, but maps are not.
Multi-level projections
Spring Data Neo4j 还支持多级投影。
Spring Data Neo4j also supports multi-level projections.
interface ProjectionWithNestedProjection {
String getName();
List<Subprojection1> getLevel1();
interface Subprojection1 {
String getName();
List<Subprojection2> getLevel2();
}
interface Subprojection2 {
String getName();
}
}
即使可以建模循环投影或指向将创建循环的实体,投影逻辑也不会遵循这些循环,而只会创建无循环查询。
Even though it is possible to model cyclic projections or point towards entities that will create a cycle, the projection logic will not follow those cycles but only create cycle-free queries.
多级投影绑定到它们应该投射到的实体。在这种情况下,RelationshipProperties
属于实体类别,并且在应用投影时需要予以尊重。
Multi-level projections are bounded to the entities they should project.
RelationshipProperties
fall into the category of entities in this case and needs to get respected if projections get applied.
Data manipulation of projections
如果你已将投影获取为 DTO,则可以修改其值。但是,如果你使用基于界面的投影,则不能只更新该界面。一个可以使用的典型模式是在你的域实体类中提供一个方法,该方法使用该界面并用从该界面复制的值创建一个域实体。通过这种方式,你随后可以更新该实体并使用下一节中描述的投影蓝图/掩码再次持久化该实体。
If you have fetched the projection as a DTO, you can modify its values. But in case you are using the interface-based projection, you cannot just update the interface. A typical pattern that can be used is to provide a method in your domain entity class that consumes the interface and creates a domain entity with the copied values from the interface. This way, you can then update the entity and persist it again with the projection blueprint/mask as described in the next section.
Persistence of projections
类似于通过投影检索数据,它们也可以用作持久性的蓝图。Neo4jTemplate
提供了一个流畅的 API 来对保存操作应用这些投影。
Analogue to the retrieval of data via projections, they can also be used as a blueprint for persistence.
The Neo4jTemplate
offers a fluent API to apply those projections to a save operation.
你可以为给定的域类保存投影
You could either save a projection for a given domain class
Projection projection = neo4jTemplate.save(DomainClass.class).one(projectionValue);
或者你可以保存一个域对象,但只保留在投影中定义的字段。
or you could save a domain object but only respect the fields defined in the projection.
Projection projection = neo4jTemplate.saveAs(domainObject, Projection.class);
在这两种情况下,也可以用于基于集合的操作,只有投影中定义的字段和关系才会得到更新。
In both cases, that also are available for collection based operations, only the fields and relationships defined in the projection will get updated.
为防止数据删除(例如,删除关系),您应始终至少加载所有随后应获取持久性的数据。 |
To prevent deletion of data (e.g. removal of relationships), you should always load at least all the data that should get persisted later. |
A full example
给定以下实体、投影和相应的库:
Given the following entities, projections and the corresponding repository:
@Node
class TestEntity {
@Id @GeneratedValue private Long id;
private String name;
@Property("a_property") (1)
private String aProperty;
}
1 | This property has a different name in the Graph |
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 | This attribute doesn’t exist on the projected entity |
下面显示了 TestEntity
的库,它将按照清单中的解释进行处理。
A repository for TestEntity
is shown below and it will behave as explained with the listing.
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 | The domain type of the repository is TestEntity |
2 | Methods returning one or more TestEntity will just return instances of it, as it matches the domain type |
3 | Methods returning one or more instances of classes that extend the domain type will just return instances of the extending class. The domain type of the method in question will be the extended class, which still satisfies the domain type of the repository itself |
4 | This method returns an interface projection, the return type of the method is therefore different
from the repository’s domain type. The interface can only access properties defined in the domain type.
The suffix By is needed to make SDN not look for a property called InterfaceProjections in the TestEntity |
5 | This method returns a DTO projection. Executing it will cause SDN to issue a warning, as the DTO defines
numberOfRelations as additional attribute, which is not in the contract of the domain type.
The annotated attribute aProperty in TestEntity will be correctly translated to a_property in the query.
As above, the return type is different from the repositories' domain type.
The suffix By is needed to make SDN not look for a property called DTOProjections in the TestEntity |
6 | This method also returns a DTO projection. However, no warning will be issued, as the query contains a fitting value for the additional attributes defined in the projection |
虽然 the listing above 中的存储库使用一个具体的返回类型来定义投影,但另一个变体是使用 dynamic projections,如 Spring Data Neo4j 与其他 Spring Data 项目共享的文档部分中所解释的那样。动态投影可以应用于封闭和开放接口投影以及基于类的 DTO 投影:动态投影的关键在于将所需的投影类型指定为存储库中查询方法的最后一个参数,如下所示: |
While the repository in projections.simple-entity-repository uses a concrete return type to define the projection, another variant is the use of dynamic projections as explained in the parts of the documentation Spring Data Neo4j shares with other Spring Data Projects. A dynamic projection can be applied to both closed and open interface projections as well as to class based DTO projections: The key to a dynamic projection is to specify the desired projection type as the last parameter to a query method
in a repository like this: |