通過Spring Data Neo4J操作您的圖形資料庫
本文中所使用的所有的程式碼都是基於Spring Data Neo4J 4.1.1的。我已經將這些程式碼放置在https://github.com/loveis715/Spring-Neo4J中。讀者可以自行下載並檢視其所包含的各次更改。該樣例專案內部是按照版本進行組織的。也就是說,一旦我發現其它後續版本再次出現大範圍的API改動,那麼我會建立一個新版本資料夾,並在其中新增相應的程式碼。
新增Spring Data Neo4J的支援
如果想要使用Spring Data Neo4J,我們要做的第一步便是在專案中新增對Spring Data Neo4J的支援。如果您是使用Maven對專案進行管理,那麼您首先要在專案中新增使用Spring Data Neo4J所需要的各個依賴項:
1 <dependency> 2 <groupId>org.neo4j</groupId> 3 <artifactId>neo4j-ogm-embedded-driver</artifactId> 4 <version>2.0.1</version> 5 </dependency> 6 <dependency> 7 <groupId>org.springframework</groupId> 8 <artifactId>spring-test</artifactId> 9 <version>4.2.5.RELEASE</version> 10 </dependency> 11 <dependency> 12 <groupId>junit</groupId> 13 <artifactId>junit</artifactId> 14 <version>4.9</version> 15 </dependency> 16 <dependency> 17 <groupId>org.springframework.data</groupId> 18 <artifactId>spring-data-neo4j</artifactId> 19 <version>4.1.1.RELEASE</version> 20 </dependency>
首先來讓我們來看看依賴項neo4j-ogm-embedded-driver。其主要用來提供對內嵌Driver的支援。Spring Data Neo4J主要支援兩類Driver:內嵌Driver以及Http Driver。內嵌Driver會直接連線本地圖形資料庫,因此較適合開發環境;而Http Driver則會通過Http與圖形資料庫例項溝通,因此更加適合生產環境。
第二個依賴項spring-test以及第三個依賴項JUnit則添加了基於Spring Framework的測試功能的支援。這裡我們所使用的Spring Framework的版本是4.2.5.RELEASE,也是與Spring Data Neo4J所相容的最低版本。
而最後一個依賴項spring-data-neo4j也不必多說。其是Spring Data Neo4J所對應的依賴項。
新增完這些依賴項之後,我們就需要對Spring Data Neo4J進行配置了。在之前的版本中,我們只需要通過Spring的配置檔案標明圖形資料庫的地址以及圖形資料庫資料型別所在的包即可。而Spring Data Neo4J 4.1.1已經不再支援這種配置方式了,甚至用來處理http://www.springframework.org/schema/data/neo4j這個XML名稱空間的Neo4jNamespaceHandler類都已經被移除。作為替代,我們需要從Neo4jConfiguration類派生,以指定Spring Data Neo4J執行時所需要使用的配置:
1 @Configuration 2 @EnableNeo4jRepositories(basePackages = "com.ambergarden.samples.neo4j.repositories") 3 @EnableTransactionManagement 4 public class GraphDBConfiguration extends Neo4jConfiguration { 5 6 @Bean 7 public org.neo4j.ogm.config.Configuration getConfiguration() { 8 org.neo4j.ogm.config.Configuration config = 9 new org.neo4j.ogm.config.Configuration(); 10 // TODO: Temporary uses the embedded driver. We need to switch to http 11 // driver. Then we can horizontally scale neo4j 12 config.driverConfiguration() 13 .setDriverClassName("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver") 14 .setURI("file:/D:/neo4j/graph.db/"); 15 return config; 16 } 17 18 @Override 19 @Bean 20 public SessionFactory getSessionFactory() { 21 // Return the session factory which also includes the persistent entities 22 return new SessionFactory(getConfiguration(), "com.ambergarden.samples.neo4j.entities"); 23 } 24 }
上面的程式碼主要指出了三個配置項:Spring Data Neo4J所需要使用的各個Repository主要存在於com.ambergarden.samples.neo4j.repositories包中,而其所操作的各個資料型別則主要存在於com.ambergarden.samples.neo4j.entities包中。同時我們還標明瞭我們將使用內嵌Driver,並將資料暫時儲存在D:\neo4j\graph.db資料夾下。
而為了能讓Spring能夠探測到該配置類,我們首先需要在該型別之上新增@Configuration標記,然後還需要在Spring的配置檔案中通過component-scan元素來讓Spring Framework搜尋配置檔案所在的包com.ambergarden.samples.neo4j:
1 <context:component-scan base-package="com.ambergarden.samples.neo4j" />
至此為止,我們已經完成了對Spring Data Neo4J的配置。那麼好,讓我們建立一個簡單的測試類來驗證這些配置的正確性:
1 @NodeEntity 2 public class Person { 3 @GraphId 4 private Long id; 5 6 public Long getId() { 7 return id; 8 } 9 10 public void setId(Long id) { 11 this.id = id; 12 } 13 }
接下來,我們就需要新增對該型別進行CRUD的Repository:
1 public interface PersonRepository extends GraphRepository<Person> { 2 }
最後,編寫一小段測試程式碼來看看這些型別是否能夠正常工作:
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration({ "classpath*:/spring/dal-test-context.xml" }) 3 public class PersonRepositoryTest { 4 @Autowired 5 private PersonRepository personRepository; 6 7 @Test 8 public void testCRUDPerson() { 9 Person person = new Person(); 10 person = personRepository.save(person); 11 assertNotNull(person); 12 assertNotNull(person.getId()); 13 14 Long personId = person.getId(); 15 personRepository.delete(person); 16 person = personRepository.findOne(personId); 17 assertNull(person); 18 } 19 }
如果您的Eclipse能正確地執行這些單元測試,那麼恭喜,您已經成功地配置了Spring Data Neo4J。
定義資料實體
在確認了我們為使用Spring Data Neo4J所進行的專案配置不存在問題之後,我們就需要開始嘗試定義操作Neo4J所需的實體了。如果需要將一個型別宣告為Neo4J實體,那麼我們就需要在相應的型別定義上使用@NodeEntity標記:
1 @NodeEntity 2 public class Person { 3 …… 4 }
除此之外,我們還需要通過@GraphId標記標明用來記錄ID的屬性:
1 @NodeEntity 2 public class Person { 3 @GraphId 4 private Long id; 5 6 public Long getId() { 7 return id; 8 } 9 10 public void setId(Long id) { 11 this.id = id; 12 } 13 }
由於該域是如此常用,因此我們常常會將其移到基類實現中,並根據該域的值來重寫hashCode()和equals()方法:
1 public abstract class AbstractEntity { 2 3 @GraphId 4 private Long id; 5 …… 6 7 @Override 8 public boolean equals(Object obj) { 9 …… 10 } 11 12 @Override 13 public int hashCode() { 14 …… 15 } 16 }
此時,所有的Neo4J實體只需要從該類派生就能得到equals()和hashCode()的支援了。至於如何正確地實現equals()以及如何正確地實現hashCode(),網路上的講解很多,這裡就不再贅述。感興趣的讀者可以自行檢視GitHub上的示例程式碼。
好,有了實體,下一步要做的就是定義實體之間的關係了。先讓我們看一個如何建立簡單關係的示例:
1 @NodeEntity 2 public class Person extends NamedEntity { 3 @Relationship(type="READER_OF") 4 private Set<Book> books; 5 …… 6 } 7 8 @NodeEntity 9 public class Book extends DescriptiveEntity { 10 @Relationship(type="READER_OF", direction=Relationship.INCOMING) 11 private Set<Person> readers; 12 …… 13 }
上面的程式碼展示瞭如何在Person類和Book類之間建立簡單的關係。可以看到,我們只需要建立對關係另一端例項的引用,並通過@Relationship標記在這些引用之上標明關係的名稱即可。如果一個關係是有向的,例如READER_OF關係需要被解讀為Person p is READER_OF Book b,那麼我們就需要在關係的兩端標明關係的方向。在上面的示例中,READER_OF關係的方向便是由Person指向Book。當然,我們也可以通過UNDIRECTED來標示一個關係是沒有方向的。
當然,我們是為了方便才在Book中添加了指向Person例項的集合。如果在您的Book實體中新增這麼個域並不會為您帶來方便,那麼您完全可以省略該域。而且該做法還會在一定程度上簡化您編寫程式碼的複雜度。讓我們來看下面的一段用來在Person例項和Book例項之間建立關係的示例程式碼(詳見測試程式碼BookRepositoryTest.testCRUDRelationships()):
1 @Test 2 public void testCRUDRelationships() { 3 …… 4 // Test add readers 5 Book book2 = new Book(); 6 book2.setName(TEST_BOOK_NAME_2); 7 book2 = bookRepository.save(book2); 8 9 readers = new HashSet<Person>(); 10 readers.add(person1); 11 person1.getBooks().add(book2); 12 book2.setReaders(readers); 13 book2 = bookRepository.save(book2); 14 …… 15 }
您可能會問:為什麼向Person例項新增一個對Book類例項的引用需要這麼多行程式碼?答案就是,如果我們只更新了關係的一端卻沒有更新關係的另一端,那麼關係兩端所記錄的關係就會出現不一致,進而導致Neo4J根本無法判斷應該根據哪一端所記錄的資料對資料庫進行操作。您說是不?
往更深一步說,圖形資料庫中一個非常容易犯錯的地方就是對圖的修改會導致圖處於一個非一致狀態。在這種情況下,對這些資料的儲存將可能導致圖本身處於一個非期望的狀態,並逐漸發展為混亂狀態。例如在刪除一個例項時沒有維護圖中其它結點對它的引用,那麼該刪除操作就可能會導致這些結點中所記錄的引用是非法的,甚至使得整個資料庫無法載入。您別不信,我們真遇到過。
所以說,圖形資料庫程式設計中,您一定要在每個對圖的操作中保證圖是處於一個合法的一致狀態。這也便是為何我們常常在關係的兩端都記錄關係的原因:一旦其中一個結點有變,我們可以快速找到對它的引用,並對該引用進行維護。
您可能還有一個問題,那就是,圖形資料庫不是用來記錄富關係的麼?那我們應該如何在關係中記錄額外的資料呢?此時我們就需要通過@RelationshipEntity標記來定義一個富關係,並進而在各個實體定義中使用該關係:
1 @RelationshipEntity(type="WRITER_OF") 2 public class WriterOf extends AbstractEntity { 3 @StartNode 4 private Person writer; 5 6 @EndNode 7 private Book book; 8 9 private Date startDate; 10 private Date endDate; 11 …… 12 } 13 14 @NodeEntity 15 public class Person extends NamedEntity { 16 @Relationship(type="WRITER_OF") 17 private Set<WriterOf> writings; 18 19 @Relationship(type="READER_OF") 20 private Set<Book> books; 21 …… 22 }
此時在我們的實體定義中所記錄的不再是富關係另一端的實體集合,而是富關係本身。
相信經過這麼一篇簡短的介紹,您已經瞭解瞭如何使用Spring Data Neo4J來操作資料庫了。如果您感興趣,可以下載我放置在https://github.com/loveis715/Spring-Neo4J中的示例程式碼。