Announcement Announcement Module
Collapse
No announcement yet.
Applying transaction boundaries Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Applying transaction boundaries

    This kind of follows on from this this post here (http://forum.springsource.org/showth...ith-properties)

    What I am trying to do is have the @Service as my transaction boundary, but from what I can see the transaction boundary is actually being applied only at the repository layer.

    Given this service method:

    @Transactional
    public void addFriend(Person firstPerson, Person secondPerson) {
    firstPerson.makeFriendsWith(secondPerson);
    repository.save(firstPerson);
    }

    I end up getting a NotInTransactionException. I thought that my transaction manager wasn't being loaded at all but then I found a curious thing - when I commented out the makeFriendsWith method, I could run the the code without exception. Does this mean that the transaction boundary is not being properly applied to the service?

    FYI - inside the makeFriendsWith method, a new Friendship method is being created and added to the original Person's list of friends. Presumably this is kicking off a transaction which for some reason doesn't have an open transaction around it.

    Bug, or just coded wrong on my behalf?

    Thanks

  • #2
    But you have tx management setup correctly ? And your @Service is injected by Spring?

    Can you perhaps share your project for us to check?

    Thanks Michael

    Comment


    • #3
      Thanks Michael,

      Just putting a tets up into GitHub now. The dependencies are almost the same as the hello world example, and my app context is as follows:

      <?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schem...-beans-3.0.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schem...ontext-3.0.xsd
      http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schem...-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">


      <contextroperty-placeholder location="classpath:META-INF/socialsystem.properties"
      system-properties-mode="OVERRIDE"/>

      <context:annotation-config/>

      <context:spring-configured/>
      <context:component-scan base-package="gamesys.social"/>

      <neo4j:config graphDatabaseService="neo4jGraphDatabase"/>
      <neo4j:repositories base-package="gamesys.social.infrastructure.persistence .graph"/>

      <!--<bean id="graph" class="com.tinkerpop.blueprints.pgm.impls.neo4j.Ne o4jGraph">-->
      <!--<constructor-arg value="/tmp/graph.db"/>-->
      <!--</bean>-->

      <bean id="wrappingNeoServerBootstrapper" class="org.neo4j.server.WrappingNeoServerBootstrap per" init-method="start"
      destroy-method="stop">
      <constructor-arg ref="neo4jGraphDatabase"/>
      </bean>

      <bean id="neo4jGraphDatabase" class="${graph.graphClassName}"
      destroy-method="shutdown">
      <constructor-arg value="${graph.path}"/>
      <constructor-arg ref="neo4j-properties"/>
      </bean>

      <bean name="neo4j-properties" class="java.util.HashMap">
      <constructor-arg index="0">
      <map>
      <entry key="allow_store_upgrade">
      <value>true</value>
      </entry>
      <entry key="ha.machine_id" value="${server.id}"/>
      <entry key="ha.server" value="zoo1:${ha.server.port}"/>
      <entry key="ha.zoo_keeper_servers" value="${zookeeper.servers}"/>
      <entry key="enable_remote_shell" value="port=1331"/>
      <entry key="ha.pull_interval" value="1"/>
      </map>
      </constructor-arg>
      </bean>

      <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

      </beans>

      Not sure if perhaps my tx declaration is wrong but I'll upload the test now.

      Thanks

      Comment


      • #4
        Annoyingly I've written a bunch of unit tests which all work perfectly with my sample application on github, which makes what I am running into even weirder. I've put the github stuff in as I think it illustrates it.

        My service is at https://github.com/mikeycmccarthy/Sp...rviceImpl.java - We simply get or create two nodes, then relate them.

        The test for this is at https://github.com/mikeycmccarthy/Sp...rviceTest.java. It is super simple, and just tries to show I don't get a not in transaction exception.

        The domain model is at https://github.com/mikeycmccarthy/Sp...el/Member.java. Referring a member simply adds them to that list of referees.

        As I said, all this code passes, but in my actual work project, the process of performing the add inside the domain class (it's definitely this - I comment it out and all works) is what causes the exception. Is there any way that at this point we have somehow become detached from the wrapping transaction? And if so, what would cause this? My production code is almost identical to the gh stuff.

        My workaround for now I guess could be to use the template to save the relationship directly, but I'm still very confused as to how i have ended up outside of a transaction.

        Thanks

        Comment


        • #5
          Coded wrong. The 2 objects are retrieved outside of the transaction (in a test method in general there is a transaction around the test method making the 2 object retrieved inside the same transaction). The whole unit of work (getting the persons, adding friends) should be a single transaction. I suspect the calling code is first retrieving the persons then call this code.

          Also make sure that transactions are actually applied, in your stacktrace there should be a TransactionInterceptor somewhere.
          Last edited by Marten Deinum; Jan 26th, 2012, 05:42 AM.

          Comment


          • #6
            I've tried to reproduce this in a project on GitHub and annoyingly it always seems to work there.

            I am confident everything is fine with my Spring setup (I've got tests that prove that a simple save works and a simple retrieve from the repository works).

            I am injecting the service into my test, much like this test here - https://github.com/mikeycmccarthy/Sp...rviceTest.java and I know for sure the transactionality being applied is from my service as my test class has no transactional annotations.

            I think the problem is that somehow my transaction seems to be missing when I do anything to the relationship. Consider my service code:

            @Override
            @Transactional
            public void refer(Long referer, Long referee) {
            Member refererMember = getOrCreateMember(referer);
            Member refereeMember = getOrCreateMember(referee);
            refererMember.refer(refereeMember); // Add the relationship
            memberRepository.save(refererMember);
            }

            public Member getOrCreateMember(long memberId) {

            Member member = memberRepository.findByPropertyValue("memberId", memberId);

            if (member == null) {
            member = memberRepository.save(new Member(memberId));
            }

            return member;
            }

            That commented line is the one that throws the NotInTransactionException (if it gets commented out the entities are persisted, just not the relationship). Interestingly, the exact same thing happens when I use the template directly to save the relationship (again, doesn't fail in my Github code, only on the production code).

            Are there any scenarios you can think of at all where the transaction is not available for persisting the relationship?

            Comment


            • #7
              If it helps, this is the particular stack trace exception at this point:

              org.neo4j.graphdb.NotInTransactionException
              at org.neo4j.kernel.impl.persistence.PersistenceManag er.getResource(PersistenceManager.java:253)
              at org.neo4j.kernel.impl.persistence.PersistenceManag er.relationshipCreate(PersistenceManager.java:162)
              at org.neo4j.kernel.impl.core.NodeManager.createRelat ionship(NodeManager.java:321)
              at org.neo4j.kernel.impl.core.NodeImpl.createRelation shipTo(NodeImpl.java:517)
              at org.neo4j.kernel.impl.core.NodeProxy.createRelatio nshipTo(NodeProxy.java:197)
              at org.springframework.data.neo4j.support.mapping.Ent ityStateHandler.createRelationship(EntityStateHand ler.java:131)
              at org.springframework.data.neo4j.support.mapping.Ent ityStateHandler.useOrCreateState(EntityStateHandle r.java:118)
              at org.springframework.data.neo4j.support.mapping.Neo 4jEntityConverterImpl.write(Neo4jEntityConverterIm pl.java:145)
              at org.springframework.data.neo4j.support.mapping.Neo 4jEntityPersister$CachedConverter.write(Neo4jEntit yPersister.java:176)
              at org.springframework.data.neo4j.support.mapping.Neo 4jEntityPersister.persist(Neo4jEntityPersister.jav a:244)
              at org.springframework.data.neo4j.support.mapping.Neo 4jEntityPersister.persist(Neo4jEntityPersister.jav a:227)
              at org.springframework.data.neo4j.support.Neo4jTempla te.save(Neo4jTemplate.java:287)
              at org.springframework.data.neo4j.fieldaccess.OneToNR elationshipEntityFieldAccessorFactory$OneToNRelati onshipEntityFieldAccessor.persistEntities(OneToNRe lationshipEntityFieldAccessorFactory.java:80)
              at org.springframework.data.neo4j.fieldaccess.OneToNR elationshipEntityFieldAccessorFactory$OneToNRelati onshipEntityFieldAccessor.setValue(OneToNRelations hipEntityFieldAccessorFactory.java:74)
              at org.springframework.data.neo4j.fieldaccess.Managed FieldAccessorSet.updateValue(ManagedFieldAccessorS et.java:90)
              at org.springframework.data.neo4j.fieldaccess.Managed FieldAccessorSet.update(ManagedFieldAccessorSet.ja va:78)
              at org.springframework.data.neo4j.fieldaccess.Managed FieldAccessorSet.add(ManagedFieldAccessorSet.java: 104)
              at xxx.social.model.xxx.makeBuddiesWith(xxx.java:58)

              Comment


              • #8
                FYI, I've actually found a slight difference - reading through the docs:

                It must not be a primitive type because then the "non-attached" case can not be represented as the default value 0 would point to the reference node. Please make also sure that an equals() and hashCode() method have to be provided which take the id field into account (and also handle the "non-attached", null case).

                I'm not factoring the graphId into hashCode and equals of my objects. I had purposely left this out but I'll add it in now with the recommendations. Can't imagine this is the fix, but it's a worthwhile thing to do anyways.

                Comment


                • #9
                  Fixed - the test wasn't working properly because in the test context I was missing:

                  <tx:annotation-driven mode="proxy"/>

                  and the non test wasn't working because I defined the annotation mode in the graph bean as such:

                  <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

                  Comment


                  • #10
                    Hello Michael,

                    I have the same issue... Using template.save() is working in a @Transactional @Test method, but not in a (non test) @Transactional @Service method. I had the <tx:annotation-driven mode="proxy"/> set up in all cases, as well as the annotation-config, spring-configured, component-scan, and neo4j config things.

                    In the code below, as soon as the template.save() call is made, that NotInTransactionException is thrown.


                    @Service
                    public class SimpleDatabasePopulator {

                    @Autowired
                    Neo4jOperations template;

                    @Transactional
                    public void populateDatabase() {
                    SimpleCustomer customer = template.save(new SimpleCustomer("A customer"));
                    SimpleContact contact = template.save(new SimpleContact("A contact"));
                    template.createRelationshipBetween(contact, customer, SimpleRelation.class, "CONTACT_OF_CUSTOMER", false);
                    ...



                    Anything else is needed to configure transactions? Did you have to do anything else that could fix this, like, do we really have to override equals and hashCode?

                    Thank you,
                    Daniel

                    Comment


                    • #11
                      Nothing else really needed. Could the both of you do me a favor and configure
                      <tx:annotation-driven mode="aspectj"/> ?

                      and for good measure
                      <tx:annotation-driven mode="aspectj" transaction-manager="neo4jTransactionManager"/> ?

                      You know that the proxy tx-mode creates a subclass that is overriding the methods and calls super or a delegate instance with tx-boundaries around that call (at least for classes, uses dynamic proxy for interfaces imho). Perhaps that's somehow messing up the tx-propagation?


                      MichaelMC could you please summarize your current state, I'm a bit confused by the many dimensions, 2 projects and test + service, too much for my little brain.

                      Thanks

                      Michael

                      Comment


                      • #12
                        Hi Michael H.,

                        Now I have NotInTransactionException in both cases. I'll continue the investigation tomorrow.

                        Regards,
                        Daniel

                        Comment


                        • #13
                          By the way, I tried running your tests here, the MemberServiceTest passes, and the MemberServiceImplTest fails with:


                          org.springframework.beans.factory.BeanCreationExce ption: Error creating bean with name 'com.michael.service.MemberServiceImplTest': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefini tionException: No matching bean of type [com.michael.service.MemberServiceImpl] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@javax.annotation.Resource(shareable=true, mappedName=, description=, name=, type=class java.lang.Object, authenticationType=CONTAINER)}
                          at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.postProcessPropertyValues(C ommonAnnotationBeanPostProcessor.java:306)
                          at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory.populateBean(AbstractAu towireCapableBeanFactory.java:1106)
                          at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory.autowireBeanProperties( AbstractAutowireCapableBeanFactory.java:374)
                          at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.injectDependencies (DependencyInjectionTestExecutionListener.java:110 )
                          at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.prepareTestInstanc e(DependencyInjectionTestExecutionListener.java:75 )
                          at org.springframework.test.context.TestContextManage r.prepareTestInstance(TestContextManager.java:321)
                          at org.springframework.test.context.junit4.SpringJUni t4ClassRunner.createTest(SpringJUnit4ClassRunner.j ava:211)
                          at org.springframework.test.context.junit4.SpringJUni t4ClassRunner$1.runReflectiveCall(SpringJUnit4Clas sRunner.java:288)
                          at org.junit.internal.runners.model.ReflectiveCallabl e.run(ReflectiveCallable.java:15)
                          at org.springframework.test.context.junit4.SpringJUni t4ClassRunner.methodBlock(SpringJUnit4ClassRunner. java:290)
                          at org.springframework.test.context.junit4.SpringJUni t4ClassRunner.runChild(SpringJUnit4ClassRunner.jav a:231)
                          at org.junit.runners.BlockJUnit4ClassRunner.runChild( BlockJUnit4ClassRunner.java:47)
                          at org.junit.runners.ParentRunner$3.run(ParentRunner. java:231)
                          at org.junit.runners.ParentRunner$1.schedule(ParentRu nner.java:60)
                          at org.junit.runners.ParentRunner.runChildren(ParentR unner.java:229)
                          at org.junit.runners.ParentRunner.access$000(ParentRu nner.java:50)
                          at org.junit.runners.ParentRunner$2.evaluate(ParentRu nner.java:222)
                          at org.springframework.test.context.junit4.statements .RunBeforeTestClassCallbacks.evaluate(RunBeforeTes tClassCallbacks.java:61)
                          at org.springframework.test.context.junit4.statements .RunAfterTestClassCallbacks.evaluate(RunAfterTestC lassCallbacks.java:71)
                          at org.junit.runners.ParentRunner.run(ParentRunner.ja va:300)
                          at org.springframework.test.context.junit4.SpringJUni t4ClassRunner.run(SpringJUnit4ClassRunner.java:174 )
                          at org.eclipse.jdt.internal.junit4.runner.JUnit4TestR eference.run(JUnit4TestReference.java:50)
                          at org.eclipse.jdt.internal.junit.runner.TestExecutio n.run(TestExecution.java:38)
                          at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.runTests(RemoteTestRunner.java:467)
                          at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.runTests(RemoteTestRunner.java:683)
                          at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.run(RemoteTestRunner.java:390)
                          at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.main(RemoteTestRunner.java:197)
                          Caused by: org.springframework.beans.factory.NoSuchBeanDefini tionException: No matching bean of type [com.michael.service.MemberServiceImpl] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@javax.annotation.Resource(shareable=true, mappedName=, description=, name=, type=class java.lang.Object, authenticationType=CONTAINER)}
                          at org.springframework.beans.factory.support.DefaultL istableBeanFactory.raiseNoSuchBeanDefinitionExcept ion(DefaultListableBeanFactory.java:924)
                          at org.springframework.beans.factory.support.DefaultL istableBeanFactory.doResolveDependency(DefaultList ableBeanFactory.java:793)
                          at org.springframework.beans.factory.support.DefaultL istableBeanFactory.resolveDependency(DefaultListab leBeanFactory.java:707)
                          at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.autowireResource(CommonAnno tationBeanPostProcessor.java:438)
                          at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.getResource(CommonAnnotatio nBeanPostProcessor.java:416)
                          at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor$ResourceElement.getResource ToInject(CommonAnnotationBeanPostProcessor.java:54 9)
                          at org.springframework.beans.factory.annotation.Injec tionMetadata$InjectedElement.inject(InjectionMetad ata.java:150)
                          at org.springframework.beans.factory.annotation.Injec tionMetadata.inject(InjectionMetadata.java:87)
                          at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.postProcessPropertyValues(C ommonAnnotationBeanPostProcessor.java:303)
                          ... 26 more

                          Comment


                          • #14
                            I'm a little busy today but I'll see if I can make the project on GitHub fail with the same errors I was seeing, presumably if you've all got a project you can run and see fail it might make it easier to pinpoint exactly what we've done wrong!

                            In summary for me, and the case in point here is a single method in a service injected with a repository.:

                            - Within the method, all calls to my injected repository.save WERE being executed in a transaction
                            - Within the method, the call to add a relationship to a domain model was NOT being executed in a transaction and threw the relevant exception.

                            When I browse into the CRUDRepository source code, I notice that the relevant save methods are marked as transactional. I don't know if that means that transactionality was always applying at that level, just not at my service level.

                            Comment


                            • #15
                              Hi All,

                              Regarding the issue I got related to NotInTransactionException, this was due to component scanning, for those of you using Spring MVC.

                              The root-context must have the base package defined, excluding controllers:

                              Code:
                              <context:component-scan base-package="com.xyz">
                                  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
                              </context:component-scan>
                              and the servlet-context must have the appropriate controller subpackage in it, not the root base package.

                              Code:
                              <context:component-scan base-package="com.xyz.controller"/>
                              After changing that, it went through @Transactional code. Hope this will help someone some day. And good job on Spring Data Neo4j project.

                              Thanks all,
                              Daniel

                              Comment

                              Working...
                              X