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 中還內建了一種額外的機制,允許在實體定義級別定義載入和持久化邊界。請參閱聚合邊界部分了解更多資訊。
多級投影
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 投影。但是,不會發出警告,因為查詢包含與投影中定義的附加屬性匹配的值 |