Search code examples
springtransactionsneo4jspring-data-graphspring-data-neo4j

Correct way to get transactions using Spring Data Neo4j's simple object/graph mapping?


I'm using the simple object/graph mapping in Spring Data Neo4j 2.0, where I perform persistence operations using the Spring Data repository framework. I'm working with the repositories rather than working with the Neo4jTemplate. I inject the repositories into my Spring Web MVC controllers, and the controllers call the repos directly. (No intermediate service layer--my operations are generally CRUDs and finder queries.)

When I do read operations, there are no issues. But when I do write operations, I get "NotInTransactionException". My understanding is that read ops in Neo4j don't require transactions, but write ops do.

What's the best way to get transactions into the picture here, assuming I want to stick with the simple OGM? I'm wanting to use @Transactional, but putting that on the various repository interfaces doesn't work. If I introduce an intermediate service tier in between the controllers and the repositories and then annotate the service beans with @Transactional, then it works, but I'm wondering whether there's a simpler way to do it. Without Spring Data, I'd typically have access to the DAO (repository) implementations, so I'd be able to annotate the concrete DAOs with @Transactional if I wanted to avoid a pass-through service tier. With Spring Data the repos are dynamically generated so that doesn't appear to be an option.


Solution

  • First, note that having transactional DAOs is not generally a good practice. But if you don't have a service layer, then let it be on the DAOs.

    Then, you can enable declarative transactions. Here's how I did it:

    First, define an annotation called @GraphTransactional:

    @Retention(RetentionPolicy.RUNTIME)
    @Transactional("neo4jTransactionManager")
    public @interface GraphTransactional {
    
    }
    

    Update: spring-data-neo4j have added such an annotation, so you can reuse it instead of creating a new one: @Neo4jTransactional

    Then, in applicationContext.xml, have the following (where neo4jdb is your EmbeddedGraphDatabase):

    <bean id="neo4jTransactionManagerService"
        class="org.neo4j.kernel.impl.transaction.SpringTransactionManager">
        <constructor-arg ref="neo4jdb" />
    </bean>
    <bean id="neo4jUserTransactionService" class="org.neo4j.kernel.impl.transaction.UserTransactionImpl">
        <constructor-arg ref="neo4jdb" />
    </bean>
    
    <bean id="neo4jTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="neo4jTransactionManagerService" />
        <property name="userTransaction" ref="neo4jUserTransactionService" />
    </bean>
    
    <tx:annotation-driven transaction-manager="neo4jTransactionManager" />
    

    Have in mind that if you use another transaction manager as well, you'd have to specify order="2" for this annotation-driven definition, and also have in mind that you won't have two-phase commit if you have one method that is declared to be both sql and neo4j transactional.