Spring Data Neo4j 投影
如上所述,投影有兩種型別:基於介面的投影和基於 DTO 的投影。在 Spring Data Neo4j 中,這兩種型別的投影都直接影響哪些屬性和關係透過網路傳輸。因此,如果你處理的節點和實體包含大量屬性,而在你的應用程式中並非所有使用場景都需要這些屬性時,這兩種方法都可以減少資料庫的負載。
對於基於介面和基於 DTO 的投影,Spring Data Neo4j 將使用 repository 的領域型別來構建查詢。所有可能改變查詢的屬性上的註解都將被考慮在內。領域型別是透過 repository 宣告定義的型別(例如,給定 interface TestRepository extends CrudRepository<TestEntity, Long>
這樣的宣告,領域型別將是 TestEntity
)。
基於介面的投影將始終是底層領域型別的動態代理。在此類介面上定義的訪問器(例如 getName
)的名稱必須解析為投影實體上存在的屬性(此處為 name
)。這些屬性在領域型別上是否有訪問器並不重要,只要它們可以透過通用的 Spring Data 基礎設施訪問即可。後者已經得到保證,因為如果領域型別不是持久化實體,它就不會首先被用作領域型別。
基於 DTO 的投影在使用自定義查詢時更加靈活。雖然標準查詢是派生自原始領域型別,因此只能使用其中定義的屬性和關係,但自定義查詢可以新增額外的屬性。
規則如下:首先,使用領域型別的屬性來填充 DTO。如果 DTO 聲明瞭額外的屬性(透過訪問器或欄位),Spring Data Neo4j 會在結果記錄中查詢匹配的屬性。屬性必須名稱完全匹配,並且可以是簡單型別(如 org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes
中定義的)或已知的持久化實體。支援這些型別的集合,但不支援 Map。
多層投影
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);
在這兩種情況下(對於基於集合的操作也同樣適用),只有投影中定義的欄位和關係會被更新。
為了防止資料被刪除(例如關係的移除),你應該始終至少載入所有稍後需要持久化的資料。 |
完整示例
給定以下實體、投影和相應的 repository
@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
的 repository,其行為如列表所示。
TestEntity
的 repositoryinterface 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 | 這個 repository 的領域型別是 TestEntity |
2 | 返回一個或多個 TestEntity 的方法將只返回其例項,因為這與領域型別匹配 |
3 | 返回一個或多個擴充套件領域型別的類例項的方法將只返回擴充套件類的例項。相關方法的領域型別將是擴充套件類,這仍然滿足 repository 本身的領域型別 |
4 | 這個方法返回一個介面投影,因此該方法的返回型別與 repository 的領域型別不同。該介面只能訪問領域型別中定義的屬性。需要字尾 By 以便 SDN 不在 TestEntity 中查詢名為 InterfaceProjections 的屬性 |
5 | 這個方法返回一個 DTO 投影。執行它會使 SDN 發出警告,因為該 DTO 將 numberOfRelations 定義為額外屬性,這不在領域型別的契約中。TestEntity 中帶有註解的屬性 aProperty 將被正確地轉換為查詢中的 a_property 。如上所述,返回型別與 repositories 的領域型別不同。需要字尾 By 以便 SDN 不在 TestEntity 中查詢名為 DTOProjections 的屬性 |
6 | 這個方法也返回一個 DTO 投影。然而,不會發出警告,因為查詢包含了投影中定義的額外屬性的合適值 |
雖然 上面的列表 中的 repository 使用具體的返回型別來定義投影,但另一種變體是使用 動態投影,這在 Spring Data Neo4j 與其他 Spring Data 專案共享的文件部分中有解釋。動態投影可以應用於封閉和開放介面投影以及基於類的 DTO 投影 動態投影的關鍵是在 repository 的查詢方法中將所需的投影型別指定為最後一個引數,例如: <T> Collection<T> findByName(String name, Class<T> type) 。這是一個可以新增到上面 TestRepository 的宣告,允許透過同一個方法檢索不同的投影,而無需在多個方法上重複可能的 @Query 註解。 |