Spring Data Neo4j
Spring Data Neo4j — это проект в рамках проекта
Spring Data, который предоставляет расширение
модели программирования Spring для написания приложений, использующих
Neo4j в качестве графической базы данных.
Spring Data, который предоставляет расширение
модели программирования Spring для написания приложений, использующих
Neo4j в качестве графической базы данных.
Чтобы написать тесты с использованием
NoSQLUnit для приложений
Spring Data Neo4j , вам не нужно ничего особенного, кроме того, что
Spring Data Neo4j использует специальное свойство, называемое
type
узлами и связями графа, в котором хранится полное имя класса этой сущности.Помимо
type
свойства на уровне узла / отношения, нам также необходимо создать один индекс для узлов и один индекс для отношений. В случае узловtypes
требуется индексное имя, аrel_types
для отношений требуется. В обоих случаях мы должны установить значение ключа равнымclassName
значению полного имени класса.Сопоставление типовIndexingNodeTypeRepresentationStrategy и IndexingRelationshipTypeRepresentationStrategy используется в качестве реализации сопоставления типов по умолчанию, но вы также можете указать, SubReferenceNodeTypeRepresentationStrategy какие типы сущностей хранятся в дереве на графике, представляющем иерархию типов и интерфейсов, или вы можете настроить еще больше, реализовав NodeTypeRepresentationStrategy интерфейс. |
Руки на работе
заявка
Звездный флот попросил нас разработать приложение для хранения всех членов звёздного флота , их отношений с другими членами звёздного флота и корабля, на котором они служат.
Лучший способ реализовать это требование — использовать
базу данных Neo4j в качестве бэкэнд-системы. Кроме того,
Spring Data Neo4j используется на постоянном уровне.
Это приложение смоделировано на два
класса Java , один для членов и другой для звездолетов. Обратите внимание, что для этого примера в отношениях нет свойств, поэтому моделируются только узлы.
Член класса
@NodeEntity public class Member { private static final String COMMANDS = "COMMANDS"; @GraphId Long nodeId; private String name; private Starship assignedStarship; public Member() { super(); } public Member(String name) { this.name = name; } @Fetch @RelatedTo(type=COMMANDS, direction=Direction.OUTGOING) private Set<Member> commands; public void command(Member member) { this.commands.add(member); } public Set<Member> commands() { return this.commands; } public Starship getAssignedStarship() { return assignedStarship; } public String getName() { return name; } public void assignedIn(Starship starship) { this.assignedStarship = starship; } //Equals and Hash methods }
@NodeEntity public class Member { private static final String COMMANDS = "COMMANDS"; @GraphId Long nodeId; private String name; private Starship assignedStarship; public Member() { super(); } public Member(String name) { this.name = name; } @Fetch @RelatedTo(type=COMMANDS, direction=Direction.OUTGOING) private Set<Member> commands; public void command(Member member) { this.commands.add(member); } public Set<Member> commands() { return this.commands; } public Starship getAssignedStarship() { return assignedStarship; } public String getName() { return name; } public void assignedIn(Starship starship) { this.assignedStarship = starship; } //Equals and Hash methods } |
Звездный класс
@NodeEntity public class Starship { private static final String ASSIGNED = "assignedStarship"; @GraphId Long nodeId; private String starship; public Starship() { super(); } public Starship(String starship) { this.starship = starship; } @RelatedTo(type = ASSIGNED, direction=Direction.INCOMING) private Set<Member> crew; public String getStarship() { return starship; } public void setStarship(String starship) { this.starship = starship; } //Equals and Hash methods }
Помимо классов моделей нам также нужны два репозитория для реализации операций CRUD и
файл контекста
xml
Spring.Spring Data Neo4j использует
инфраструктуру Spring Data Commons, позволяющую нам создавать основанные на интерфейсе композиции репозиториев, обеспечивая реализации по умолчанию для определенных операций.
Класс MemberRepository
public interface MemberRepository extends GraphRepository<Member>, RelationshipOperationsRepository<Member> { Member findByName(String name); }
Смотрите, кроме операций , предусмотренных
GrapRepository
интерфейсом , какsave
,findAll
,findById
, … мы определяем один метод запроса тоже называетсяfindByName
.Репозитории Spring Data Neo4j (и большинство проектов
Spring Data ) предоставляют механизм для определения запросов с использованием известного
подхода Ruby on Rails для определения запросов поиска.
StarshipRepository class
public interface StarshipRepository extends GraphRepository<Starship>, RelationshipOperationsRepository<Starship> { }
файл контекста приложения
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd"> <context:component-scan base-package="com.lordofthejars.nosqlunit.springdata.neo4j"/> <context:annotation-config/> <neo4j:repositories base-package="com.lordofthejars.nosqlunit.springdata.repository"/> </beans>
тестирование
Модульное тестирование
Как было сказано ранее, для написания наборов данных для
Spring Data Neo4j нам не нужно делать ничего особенного, кроме правильного создания свойств узла и отношений и определения необходимых индексов. Давайте посмотрим на набор данных, используемый для тестирования
findByName
метода,заполнив базу данных Neo4j .
файл star-trek-TNG-dataset.xml
<?xml version="1.0" ?> <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> <key id="name" for="node" attr.name="name" attr.type="string"></key> <key id="__type__" for="node" attr.name="__type__" attr.type="string"></key> <key id="starship" for="node" attr.name="starship" attr.type="string"></key> <graph id="G" edgedefault="directed"> <node id="3"> <data key="__type__">com.lordofthejars.nosqlunit.springdata.neo4j.Member</data> <data key="name">Jean-Luc Picard</data> <index name="__types__" key="className">com.lordofthejars.nosqlunit.springdata.neo4j.Member</index> </node> <node id="1"> <data key="__type__">com.lordofthejars.nosqlunit.springdata.neo4j.Member</data> <data key="name">William Riker</data> <index name="__types__" key="className">com.lordofthejars.nosqlunit.springdata.neo4j.Member</index> </node> <node id="4"> <data key="__type__">com.lordofthejars.nosqlunit.springdata.neo4j.Starship</data> <data key="starship">NCC-1701-E</data> <index name="__types__" key="className">com.lordofthejars.nosqlunit.springdata.neo4j.Starship</index> </node> <edge id="11" source="3" target="4" label="assignedStarship"></edge> <edge id="12" source="1" target="4" label="assignedStarship"></edge> <edge id="13" source="3" target="1" label="COMMANDS"></edge> </graph> </graphml>
Обратите внимание, что у каждого узла есть хотя бы одно
type
свойство с полным именем класса и индексом с именемtypes
, ключомclassName
и полным именем класса в качестве значения.Следующий шаг — настройка контекста приложения для модульных тестов.
приложения контекстно-погруженный neo4j.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd"> <import resource="classpath:com/lordofthejars/nosqlunit/springdata/neo4j/application-context.xml"/> <neo4j:config storeDirectory="target/config-test"/> </beans>
Notice that we are using
Neo4j namespace for instantiating an embedded
Neo4j database.
Neo4j namespace for instantiating an embedded
Neo4j database.
And now we can write the
JUnit test case:
JUnit test case:
WhenInformationAboutAMemberIsRequired
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("application-context-embedded-neo4j.xml") public class WhenInformationAboutAMemberIsRequired { @Autowired private MemberRepository memberRepository; @Autowired private StarshipRepository starshipRepository; @Autowired private ApplicationContext applicationContext; @Rule public Neo4jRule neo4jRule = newNeo4jRule() .defaultSpringGraphDatabaseServiceNeo4j(); @Test @UsingDataSet(locations = "star-trek-TNG-dataset.xml", loadStrategy = LoadStrategyEnum.CLEAN_INSERT) public void information_about_starship_where_serves_and_members_under_his_service_should_be_retrieved() { Member jeanLuc = memberRepository.findByName("Jean-Luc Picard"); assertThat(jeanLuc, is(createMember("Jean-Luc Picard"))); assertThat(jeanLuc.commands(), containsInAnyOrder(createMember("William Riker"))); Starship starship = starshipRepository.findOne(jeanLuc.getAssignedStarship().nodeId); assertThat(starship, is(createStarship("NCC-1701-E"))); } private Object createStarship(String starship) { return new Starship(starship); } private static Member createMember(String memberName) { return new Member(memberName); } }
There are some important points in the previous class to take under consideration:
-
Recall that we need to use
Spring
ApplicationContext
object to retrieve embedded
Neo4j instance defined into
Spring application context. -
Since lifecycle of database is managed by
Spring Data container, there is no need to define any
NoSQLUnit lifecycle manager.
Integration Test
Unit tests are usually run against embedded in-memory instances, but in production environment you may require access to external
Neo4j servers by using
Rest connection, or in case of
Spring Data Neo4j instantiating
Neo4j servers by using
Rest connection, or in case of
Spring Data Neo4j instantiating
SpringRestGraphDatabase
class. You need to write tests to validate that your application still works when you integrate your code with a remote server, and this tests are typically known as integration tests.
To write integration tests for our application is as easy as defining an application context with
NoSQLUnit to control the lifecycle of
Neo4j database.
SpringRestGraphDatabase
and allowNoSQLUnit to control the lifecycle of
Neo4j database.
.application-context-managed-neo4j.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd"> <import resource="classpath:com/lordofthejars/nosqlunit/springdata/neo4j/application-context.xml"/> <bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase"> <constructor-arg index="0" value="http://localhost:7474/db/data"></constructor-arg> </bean> <neo4j:config graphDatabaseService="graphDatabaseService"/> </beans>
Note how instead of registering an embedded instance, we are configuring
Neo4j server.
SpringRestGraphDatabase
class to connect to localhost server. And let’s implement an integration test which verifies that all starships can be retrieved fromNeo4j server.
WhenInformationAboutAMemberIsRequired
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("application-context-managed-neo4j.xml") public class WhenInformationAboutStarshipsAreRequired { @ClassRule public static ManagedNeoServer managedNeoServer = newManagedNeo4jServerRule() .neo4jPath( "/Users/alexsotobueno/Applications/neo4j-community-1.7.2") .build(); @Autowired private StarshipRepository starshipRepository; @Autowired private ApplicationContext applicationContext; @Rule public Neo4jRule neo4jRule = newNeo4jRule() .defaultSpringGraphDatabaseServiceNeo4j(); @Test @UsingDataSet(locations = "star-trek-TNG-dataset.xml", loadStrategy = LoadStrategyEnum.CLEAN_INSERT) public void information_about_starship_where_serves_and_members_under_his_service_should_be_retrieved() { EndResult<Starship> allStarship = starshipRepository.findAll(); assertThat(allStarship, containsInAnyOrder(createStarship("NCC-1701-E"))); } private Object createStarship(String starship) { return new Starship(starship); } }
Because
application context, in our case it will return the defined
defaultSpringGraphDatabaseServiceNeo4j
method returns aGraphDatabaseService
instance defined intoapplication context, in our case it will return the defined
SpringRestGraphDatabase
instance.
Conclusions
There is not much difference between writing tests for none
Spring Data Neo4j applications than the ones they use it. Only keep in mind to define correctly the
Spring Data Neo4j applications than the ones they use it. Only keep in mind to define correctly the
type
property and create required indexes.
Also see that from the point of view of
NoSQLUnit there is no difference between writing unit or integration tests, apart of lifecycle management of the database engine.
NoSQLUnit there is no difference between writing unit or integration tests, apart of lifecycle management of the database engine.
We keep learning,
Alex.