diff --git a/README.textile b/README.textile
index 4d5e5f678..d07bcd239 100644
--- a/README.textile
+++ b/README.textile
@@ -79,7 +79,7 @@ h2. Maven configuration
org.codehaus.mojo
aspectj-maven-plugin
- 1.0
+ 1.4
true
@@ -107,12 +107,12 @@ h2. Maven configuration
org.aspectj
aspectjrt
- 1.6.11.RELEASE
+ 1.7.4
org.aspectj
aspectjtools
- 1.6.11.RELEASE
+ 1.7.4
diff --git a/pom.xml b/pom.xml
index 902d10da7..6d618db3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,10 +53,17 @@
true
+
+ distribute
+
+ true
+
+
examples
spring-data-neo4j-examples/hello-worlds
+ spring-data-neo4j-examples/hello-worlds-aspects
spring-data-neo4j-examples/imdb
spring-data-neo4j-examples/cineasts
spring-data-neo4j-examples/cineasts-aspects
diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java
index f13815a8c..942d259e7 100644
--- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java
+++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java
@@ -164,7 +164,7 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware {
* @return the unique entity of type entityClass (if it exists) otherwise returns null.
*
*/
- public T findUniqueEntity(final Class entityClass,String propertyName, Object value) {
+ /*public T findUniqueEntity(final Class entityClass,String propertyName, Object value) {
final Neo4jPersistentEntityImpl> persistentEntity = getPersistentEntity(entityClass);
Neo4jPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(propertyName);
@@ -175,6 +175,7 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware {
}
return (T)getSchemaIndexProvider().findAll(persistentProperty,value).singleOrNull();
}
+ */
/**
* @return true if a transaction manager is available and a transaction is currently running
diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java
index 1c8462fa6..1e76b6227 100644
--- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java
+++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java
@@ -20,9 +20,12 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Node;
+import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.neo4j.repository.GraphRepository;
+import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.unique.common.CommonClub;
import org.springframework.data.neo4j.unique.common.CommonUniqueClub;
import org.springframework.data.neo4j.unique.common.CommonUniqueEntityTestBase;
@@ -37,13 +40,20 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:unique-legacy-test-context.xml"})
@Transactional
public class UniqueLegacyIndexBasedEntityTests extends CommonUniqueEntityTestBase {
+ @Autowired
+ private Neo4jTemplate neo4jTemplate;
+
@Autowired
private ClubRepository clubRepository;
@@ -77,6 +87,45 @@ public class UniqueLegacyIndexBasedEntityTests extends CommonUniqueEntityTestBas
assertEquals("Expected same node Ids", club1.getId(),club2.getId());
}
+ @Test
+ public void creatingDistinctUniqueEntitiesViaNeo4jTemplateShouldResolveToDifferentEntities() {
+ Collection labels = Arrays.asList( UniqueClub.class.getSimpleName(),"_"+ UniqueClub.class.getSimpleName() );
+
+ Map fooParams = new HashMap();
+ fooParams.put("name","foo");
+ fooParams.put("description","foo description");
+ Map barParams = new HashMap();
+ barParams.put("name","bar");
+ barParams.put("description","foo description");
+
+ Node club1 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(), "name", "foo", fooParams, labels);
+ Node club2 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(),"name","bar", barParams, labels);
+
+ assertNotEquals("Expected different node Ids", club1.getId(), club2.getId());
+ assertEquals(2, getUniqueClubRepository().count());
+ }
+
+ @Test
+ public void creatingTheSameUniqueEntitiesViaNeo4jTemplateShouldResolveToOriginalEntity() {
+ Collection labels = Arrays.asList( UniqueClub.class.getSimpleName(),"_"+ UniqueClub.class.getSimpleName() );
+
+ Map fooParams = new HashMap();
+ fooParams.put("name","foo");
+ fooParams.put("description","foo description");
+ Map foo2Params = new HashMap();
+ foo2Params.put("name","foo");
+ foo2Params.put("description","bar description"); // Note: description differs but will be discarded
+
+ Node club1 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(), "name", "foo", fooParams, labels);
+ Node club2 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(),"name","foo", foo2Params, labels);
+
+ assertEquals("Expected the same node Ids", club1.getId(), club2.getId());
+ assertEquals(1, getUniqueClubRepository().count());
+ assertEquals("foo description", club2.getProperty("description"));
+ }
+
+
+
@Override
protected CommonClub createNonUniqueClub(String name) {
Club club = new Club();
diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java
index 2913264ce..2a51f3a6a 100644
--- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java
+++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java
@@ -20,8 +20,10 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Node;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.repository.GraphRepository;
+import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.unique.common.CommonClub;
import org.springframework.data.neo4j.unique.common.CommonUniqueClub;
import org.springframework.data.neo4j.unique.common.CommonUniqueEntityTestBase;
@@ -36,15 +38,19 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
+import java.util.*;
+
+import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:unique-schema-test-context.xml"})
@Transactional
public class UniqueSchemaBasedEntityTests extends CommonUniqueEntityTestBase {
+ @Autowired
+ private Neo4jTemplate neo4jTemplate;
+
@Autowired
private ClubRepository clubRepository;
@@ -81,6 +87,44 @@ public class UniqueSchemaBasedEntityTests extends CommonUniqueEntityTestBase {
return (CommonUniqueClub)getUniqueClubRepository().findBySchemaPropertyValue(propertyName, value);
}
+ @Test
+ public void creatingDistinctUniqueEntitiesViaNeo4jTemplateShouldResolveToDifferentEntities() {
+ Collection labels = Arrays.asList( UniqueClub.class.getSimpleName(),"_"+ UniqueClub.class.getSimpleName() );
+
+ Map fooParams = new HashMap();
+ fooParams.put("name","foo");
+ fooParams.put("description","foo description");
+ Map barParams = new HashMap();
+ barParams.put("name","bar");
+ barParams.put("description","foo description");
+
+ Node club1 = neo4jTemplate.merge(UniqueClub.class.getSimpleName(), "name", "foo", fooParams, labels);
+ Node club2 = neo4jTemplate.merge(UniqueClub.class.getSimpleName(),"name","bar", barParams, labels);
+
+ assertNotEquals("Expected different node Ids", club1.getId(), club2.getId());
+ assertEquals(2, getUniqueClubRepository().count());
+ }
+
+ @Test
+ public void creatingTheSameUniqueEntitiesViaNeo4jTemplateShouldResolveToOriginalEntity() {
+ Collection labels = Arrays.asList( UniqueClub.class.getSimpleName(),"_"+ UniqueClub.class.getSimpleName() );
+
+ Map fooParams = new HashMap();
+ fooParams.put("name","foo");
+ fooParams.put("description","foo description");
+ Map foo2Params = new HashMap();
+ foo2Params.put("name","foo");
+ foo2Params.put("description","bar description"); // Note: description differs but will be discarded
+
+ Node club1 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(), "name", "foo", fooParams, labels);
+ Node club2 = neo4jTemplate.getOrCreateNode(UniqueClub.class.getSimpleName(),"name","foo", foo2Params, labels);
+
+ assertEquals("Expected the same node Ids", club1.getId(), club2.getId());
+ assertEquals(1, getUniqueClubRepository().count());
+ assertEquals("foo description", club2.getProperty("description"));
+ }
+
+
@Override
protected CommonClub createNonUniqueClub(String name) {
Club club = new Club();
diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml
index c0ab698ab..b17933acb 100644
--- a/src/docbkx/index.xml
+++ b/src/docbkx/index.xml
@@ -36,7 +36,7 @@
distributed in print or electronically.
- Copyright 2010-2011 Neo Technology
+ Copyright 2010-2014 Neo Technology
diff --git a/src/docbkx/reference/neo4j.xml b/src/docbkx/reference/neo4j.xml
index a0a87fc40..0dc595e3e 100644
--- a/src/docbkx/reference/neo4j.xml
+++ b/src/docbkx/reference/neo4j.xml
@@ -74,16 +74,15 @@
Creating nodes and relationships
Using the API of GraphDatabaseService, it is easy to create nodes and relate them to each other.
- Relationships are typed. Both nodes and relationships can have properties. Property values can be
- primitive Java types and Strings, or arrays of both. Node creation and
- modification has to happen within a transaction, while reading from the graph store can be
- done with or without a transaction.
+ Relationships are typed and both nodes and relationships can have properties. Property values can be
+ primitive Java types and Strings, or arrays of both. As of Neo4j 2.0, any operation on a
+ node or relationship (creation, modification or simply reading) must happen within a transaction.
Neo4j usage
-
@@ -124,31 +121,40 @@ for (Path position : traversalDescription.traverse(myStartNode)) {
Indexing
The best way for retrieving start nodes for traversals and queries is by using Neo4j's integrated index
- facilities. The GraphDatabaseService provides access to the IndexManager which in turn provides
+ facilities.
+
+
+ As of SDN 3.0 , schema based indexes (i.e. indexes based on labels) are the default, however
+ the legacy indexing functionality still remains, as there is some functionality (for example full text
+ searches, range searches) which is not possible/ available yet. It should be noted
+ that legacy based indexes are deprecated in 3.0 and the intention is to eventually
+ remove it completely as and when schema based indexes/functionality is fully able to support
+ existing functionality.
+
+
+
+ The GraphDatabaseService still provides access to the legacy IndexManager which in turn provides
named indexes for nodes and relationships. Both can be indexed with property names and values.
Retrieval is done with query methods on indexes, returning an IndexHits iterator.
- Spring Data Neo4j provides automatic indexing via the @Indexed annotation, eliminating the need
- for manual index management.
+ Spring Data Neo4j provides automatic indexing via the @Indexed annotation, defaulting to make use
+ of schema based indexes (aka labels), eliminating the need for manual index management.
-
- Modifying Neo4j indexes also requires transactions.
-
- Index usage
+ Legacy Index usage
nodeIndex = indexManager.forNodes("a-node-index");
Node node = ...;
-Transaction tx = graphDb.beginTx();
-try {
+try (Transaction tx = graphDb.beginTx()) {
nodeIndex.add(node, "property","value");
tx.success();
-} finally {
- tx.close();
}
-for (Node foundNode : nodeIndex.get("property","value")) {
- // found node
+try (Transaction tx = graphDb.beginTx()) {
+ for (Node foundNode : nodeIndex.get("property","value")) {
+ // found node
+ }
+ tx.success();
}
]]>
@@ -164,9 +170,12 @@ for (Node foundNode : nodeIndex.get("property","value")) {
Neo4j video site.
- Cypher queries always begin with a start set of nodes. Those can be either expressed by their
- IDs or by an index lookup expression. Those start-nodes are then related to other nodes in the
- match clause. Start and match clauses can introduce new identifiers for nodes and
+ As of Neo4 2.0, Cypher queries typically begin with a match clause, although the optional
+ start clause (only really needed when using legacy indexes) is also still supported.
+ The match clause can be used to provide a way to pattern match against a starting set of nodes, via their
+ IDs or label based index lookup, with the legacy start clause providing similar functionality.
+ These starting patterns or start nodes, are then related to other nodes via additional
+ match clauses. Start and/or match clauses can introduce new identifiers for nodes and
relationships. In the where clause additional filtering of the result set is applied by evaluating
expressions. The return clause defines which part of the query result will be available.
Aggregation also happens in the return clause by using aggregation functions on some of the values.
@@ -182,7 +191,17 @@ for (Node foundNode : nodeIndex.get("property","value")) {
Cypher Examples on the Cineasts.net Dataset
- Please not that the lucene based manual indexes are deprecated with Neo4j 2.0 and Spring Data Neo4j 3.0.
- The default index is now based on labels and schema indexes. Only for fulltext and spatial indexes the
- "legacy" index framework should be used. The related APIs have been deprecated as well.
+ Please note that the lucene based manual indexes are deprecated with Neo4j 2.0 and Spring Data Neo4j 3.0.
+ The default index is now based on labels and schema indexes and the related APIs have been deprecated as well.
+ The "legacy" index framework should only be used for fulltext and spatial indexes which are not currently
+ supported via schema based indexes.
- Label based schema indexes
+ Schema (Label based) indexes
Since Neo4j version 2.0 indexes and unique constraints based on labels and properties are supported throughout the
- API including cypher. For properties of entities annotated with @Indexed an appropriate schema index
- and for @Indexed(unique=true) a constraint is created.
+ API including cypher. For properties of entities annotated with @Indexed, this defaults to using the schema
+ based strategy, and an appropriate schema index is created. For @Indexed(unique=true) a constraint is created.
Those indexes will be automatically used by cypher queries that are generated for the derived finders and are
@@ -30,28 +31,43 @@
-
- The Neo4j graph database employs different index providers for exact lookups and fulltext
- searches. Lucene is the default index provider implementation. Each named index is configured to be
- fulltext or exact. There is also a spatial index provider for geo-searches.
-
+
+ Legacy indexes
+
+ If you would like to force a property on an entity to rather use the legacy index (instead of the
+ default schema based index), then you will need to explicitly specify the type as either
+ @Indexed(indexType = IndexType.SIMPLE) or @Indexed(indexType = IndexType.FULLTEXT)
+
+
+ The Neo4j graph database employs different index providers for legacy exact (SIMPLE) lookups and fulltext
+ searches. Lucene is the default index provider implementation. Each named index is configured to be
+ fulltext or exact. There is also a spatial index provider for geo-searches.
+
+
Exact and numeric index
- When using the standard Neo4j API, nodes and relationships have to be manually indexed with
- key-value pairs, typically being the property name and value. When using Spring Data Neo4j,
+ Prior to Neo4j 2.0, when using the standard Neo4j API, nodes and relationships had to be manually
+ indexed with key-value pairs, typically being the property name and value. With the introduction
+ of schemas and labels, indexing now happens automatically for you under the covers.
+ When using Spring Data Neo4j,
+ irrespective of whether you are using the newer schema based indexes or legacy indexes,
this task is simplified to just adding an @Indexed annotation on entity fields
- by which the entity should be searchable. This will result in automatic updates of the index
+ by which the entity should be searchable. This will result in automatic updates of the appropriate index
every time an indexed field changes.
- Numerical fields are indexed numerically so that they are available for range queries. All
+ Numerical fields are indexed numerically so that they are available for range queries.
+ Automatic numerical range queries are not currently supported for
+ schema based numeric indexes.
+ All
other fields are indexed with their string representation. If a numeric field should not be
indexed numerically, it is possible to switch it off with @Indexed(numeric=false).
- The @Indexed annotation also provides the option of using a custom index name. The default index
+ The @Indexed annotation also provides the option of using a custom index name (for legacy
+ indexes). The default index
name is the simple class name of the entity, so that each class typically gets its own index.
It is recommended to not have two entity classes with the same class name, regardless of
package.
@@ -63,28 +79,51 @@
that is provided or of the actual entity instance.
- The indexes can be queried by using a repository (see
+ The schema based indexes can be queried by using a repository (see
).
The repository is an instance of
+ org.springframework.data.neo4j.repository.SchemaIndexRepository.
+ The methods findBySchemaPropertyValue() and findAllBySchemaPropertyValue() work on
+ the exact indexes and return the first or all matches. Range queries are not supported yet.
+
+
+ The legacy indexes can also be queried by using a repository (see
+ ).
+ The repository is still an instance of the deprecated
org.springframework.data.neo4j.repository.IndexRepository.
The methods findByPropertyValue() and findAllByPropertyValue() work on
the exact indexes and return the first or all matches. To do range queries, use
findAllByRange() (please note that currently both values are inclusive).
- For providing explicit index names the repository has to extend NamedIndexRepository.
+ When providing explicit index names (for legacy indexes) the repository has to extend NamedIndexRepository.
This adds the shown methods with another signature that take the index name as first parameter.
- Indexing entities
+ Exact (schema based) indexes
graphRepository = template.repositoryFor(Person.class);
+// Exact match, in named index
+Person mark = graphRepository.findBySchemaPropertyValue("name", "mark");
+]]>
+
+
+
+ Exact (legacy) indexes
+ graphRepository = template.repositoryFor(Person.class);
+
// Exact match, in named index
Person mark = graphRepository.findByPropertyValue("people", "name", "mark");
@@ -97,9 +136,10 @@ for (Person middleAgedDeveloper : graphRepository.findAllByRange("age", 20, 40))
- Fulltext indexes
+ Fulltext (legacy) indexes
- Spring Data Neo4j also supports fulltext indexes. By default, indexed fields are stored in
+ Spring Data Neo4j also supports fulltext indexes - currently still only via the legacy
+ indexes. By default, legacy indexed fields are stored in
an exact lookup index. To have them analyzed and prepared for fulltext search, the
@Indexed annotation has the type attribute which can be set to IndexType.FULLTEXT.
@@ -118,7 +158,7 @@ for (Person middleAgedDeveloper : graphRepository.findAllByRange("age", 20, 40))
Fulltext indexing
graphRepository =
@@ -130,7 +170,7 @@ Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
- Please note that indexes are currently created on demand, so whenever an index that doesn't exist
+ Please note that the legacy indexes are currently created on demand, so whenever an index that doesn't exist
is requested from a query or get operation it is created. This is subject to change but has
currently the implication that those indexes won't be configured as fulltext which causes
subsequent fulltext updates to those indexes to fail.
@@ -139,30 +179,34 @@ Person mark = graphRepository.findAllByQuery("people-search", "name", "ma*");
Unique indexes
- Unique indexing with index.putIfAbsent and UniqueFactory was introduced in Neo4j 1.6.
- It is also available via the REST API.
- In Spring Data Neo4j this is made available via Neo4jTemplate.getOrCreateNode and
- Neo4jTemplate.getOrCreateRelationship.
+ Unique indexing can be applied either via the inbuilt schema (label based) unique constraint for nodes, or,
+ via the legacy index.putIfAbsent and UniqueFactory code for both nodes and relationships.
+ In Spring Data Neo4j this is done by setting the unique=true property on the @Indexed annotation.
+ Methods for programmatically getting and/or creating unique entities is available on the Neo4jTemplate class, namely
+ getOrCreateNode and getOrCreateRelationship for legacy indexes, and merge for schema based
+ unique entities.
In an entity at most one field can be annotated with @Indexed(unique=true) regardless of the index-type used.
The uniqueness will be taken into account when creating the entity by reusing an existing entity if that unique key-combination
- already exists. On saving of the field it will be cross-checked against the index and fail with a DataIntegrityViolationException
- if the field was changed to an already existing unique value. Null values are no longer allowed for these properties.
+ already exists. On saving of the field it will be cross-checked against the schema or legacy index and fail with a
+ DataIntegrityViolationException if the field was changed to an already existing unique value.
+ Null values are no longer allowed for these properties.
- This works for both Node-Entities as well as Relationship-Entities. Relationship-Uniqueness in Neo4j is global so that
+ This works for both Node-Entities as well as Relationship-Entities (legacy indexes only). Relationship-Uniqueness in Neo4j is global so that
an existing unique instance of this relationship may connect two completely different nodes and might also have a
different type.
- Unique indexing
+ Unique indexing (Schema Based)
+
+
+ Unique indexing (Legacy Based)
+
@@ -186,12 +255,12 @@ repository.save(thomas); // fails with a DataIntegrityViolationException
- Manual index access
+ Manual (Legacy) index access
- The index for a domain class is also available from Neo4jTemplate via
+ The legacy index for a domain class is also available from Neo4jTemplate via
the getIndex() method. The second parameter is optional and takes the index name
if it should not be inferred from the class name. It returns the index implementation that is
- provided by Neo4j.
+ provided by Neo4j. Note: Manual Legacy index access is deprecated in SDN 3.0
Manual index retrieval by type and name
diff --git a/src/docbkx/reference/programming-model/typerepresentationstrategy.xml b/src/docbkx/reference/programming-model/typerepresentationstrategy.xml
index fe8e68ca0..bfcfb7600 100644
--- a/src/docbkx/reference/programming-model/typerepresentationstrategy.xml
+++ b/src/docbkx/reference/programming-model/typerepresentationstrategy.xml
@@ -98,13 +98,13 @@
Java-based configuration
// (This will probably move into an/the @EnableNeo4jRepositories in the future)
- setBasePackage("org.springframework.data.neo4j.model,org.springframework.data.neo4j.repository.query");
+ setBasePackage("org.example.domain");
}
@Override