0
votes

I'm having some trouble getting unique constraints working with spring-data-neo4j in grails.

I suspect that it's because I've not wired it in properly, but the repository is being scanned and wired, and CRUD is working, so I'm not sure what I've done wrong.

I'm using grails 2.4.3, with these dependencies:

  neo4jVerison="2.1.5"
  dependencies {
    compile("org.neo4j:neo4j-community:$neo4jVerison")
    compile("org.neo4j.app:neo4j-server:$neo4jVerison")
    compile("org.neo4j:neo4j-rest-graphdb:2.0.1")
    compile("org.neo4j:neo4j-spatial:0.13-neo4j-2.1.4")
    compile 'org.springframework.data:spring-data-neo4j:3.2.1.RELEASE'
    test group:"org.neo4j", name:"neo4j-kernel", version: "$neo4jVerison", classifier:"tests"
    test "org.grails:grails-datastore-test-support:1.0-grails-2.4"
    test "xmlunit:xmlunit:1.5"
} 

and these beans for my test environment:

testGraphDatabaseFactory(org.neo4j.test.TestGraphDatabaseFactory)
graphDb(GraphDatabaseService) { bean ->
    bean.factoryBean = "testGraphDatabaseFactory"
    bean.factoryMethod = "newImpermanentDatabase"
    bean.destroyMethod = "shutdown"  }

xmlns neo4j:"http://www.springframework.org/schema/data/neo4j"
neo4j.'repositories'( 'base-package': "repositories" )
neo4jTemplate(Neo4jTemplate, graphDb)
neo4jMappingContext(Neo4jMappingContext)
tx(NeoTransactions, graphDb)

The tx class just exposes a 'tx.tx { closure }' method that starts a graphDb transaction, and closes it even if the closure fails.

My domain class is:

package repositories

import org.springframework.data.neo4j.annotation.GraphId
import org.springframework.data.neo4j.annotation.Indexed
import org.springframework.data.neo4j.annotation.NodeEntity

@NodeEntity
class Junction {
    @GraphId Long id;
    @Indexed(unique = true) Long legacyId
}

and the repository is:

package repositories

import org.springframework.data.neo4j.repository.GraphRepository

interface JunctionRepository extends GraphRepository<Junction> {}

Given all of that, I would expect this to fail:

package repositories

import org.springframework.dao.DataIntegrityViolationException
import org.springframework.data.neo4j.conversion.Result

class JunctionRepositoryIntegrationSpec extends RepositorySpecification {
    JunctionRepository junctionRepository
    def tx

    def "should not be able to create two junctions with the same legacyId"() {
        given: "some junctions with the same id"
        Junction j = new Junction(legacyId: 10)
        Junction j2 = new Junction(legacyId: 10)

        when: "both are saved"
        tx.tx {
            [j, j2].each { junctionRepository.save(it) }
        }

        then:
        1 == junctionRepository.count()

        when: "the second one is changed to have the same legacy id"
        def j3 = new Junction(legacyId: 11)
        tx.tx {
            junctionRepository.save(j3)
            j3.legacyId = 10
            junctionRepository.save(j3)
        }

        then: "an exception should be thrown"
        thrown DataIntegrityViolationException
    }
}

Strangely although only one junction was saved (i.e. 1 == junctionRepository.count()) the exception wasn't thrown when I try and save the modified j3.

Additionally, when I open the database in the Neo4J web console and run :schema it doesn't appear as if any indexes/constraints have been created.

I'm guessing that my wiring is not configured correctly, as I believe that the indexes should be setup when the domain object is parsed at wiring time.

Does anyone know what's missing please?

FOLLOW-UP 1:

I suspect that I should not be creating a template or mapping context by hand. The canonical spring configuration appears to be <neo:config/>.

However, I get a strange error when I try and do that through the spring dsl:

 xmlns neo4j:"http://www.springframework.org/schema/data/neo4j"
 neo4j.'repositories'( 'base-package': "repositories" )
 neo4j.'config'( 'graphDatabaseService': "graphDb" )

The error is:

| Error Fatal error running tests: Error creating bean with name 'junctionRepository':
  Cannot resolve reference to bean 'neo4jTemplate' while setting bean property 'neo4jTemplate';
  nested exception is org.springframework.beans.factory.BeanCreationException: Error creating
   bean with name 'neo4jTemplate' defined in class org.springframework.data.neo4j.config.Neo4jConfiguration:
  Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.data.neo4j.support.Neo4jTemplate org.springframework.data.neo4j.config.Neo4jConfiguration.neo4jTemplate() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'neo4jMappingContext' defined in class org.springframework.data.neo4j.config.Neo4jConfiguration: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.data.neo4j.support.mapping.Neo4jMappingContext org.springframework.data.neo4j.config.Neo4jConfiguration.neo4jMappingContext() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityIndexCreator' defined in class org.springframework.data.neo4j.config.Neo4jConfiguration: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.data.neo4j.support.mapping.EntityIndexCreator org.springframework.data.neo4j.config.Neo4jConfiguration.entityIndexCreator() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'indexProvider' defined in class org.springframework.data.neo4j.config.Neo4jConfiguration: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.data.neo4j.support.index.IndexProvider org.springframework.data.neo4j.config.Neo4jConfiguration.indexProvider() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'graphDatabaseService' is defined (Use --stacktrace to see the full trace)

Hmm: No bean named 'graphDatabaseService' is defined? Is that a bug? neo4j.'config'( 'graphDatabaseService': "graphDb" ) tells it to use the graphDb bean, doesn't it?

FOLLOW-UP 2:

If I change the bean name to graphDatabaseService it works fine. Looks like neo4j.config isn't passing the named graphDb bean to all of those that it constructs. :)

1

1 Answers

1
votes

Yes it does rely on the name graphDatabaseService (unfortunately).

Also make sure that your tx class also calls tx.success() not just tx.close()