NotInTransactionException при добавлении в сопоставленный набор

У меня есть два @NodeEntities, сопоставленных через SDN с помощью простого сопоставления, PersonNode и FamilyNode. FamilyNode имеет коллекцию @RelatedTo, дети. У меня также есть FamilyService (с использованием аннотации Spring @Service) с аннотацией @Transactional для метода updateFamily. Этот метод загружает FamilyNode с заданным идентификатором и использует интерфейс обратного вызова для изменения узла. В одной реализации обратного вызова я добавляю PersonNode в дочернюю коллекцию, и это генерирует NotInTransactionException, особенно в тот момент, когда Neo4J пытается создать связь между FamilyNode и PersonNode.

Исходный код можно найти на github и, в частности, на провал теста. Вот соответствующие фрагменты кода:

FamilyNode.java:

 @NodeEntity
 public class FamilyNode implements Family {

    @Indexed(indexName = "families", unique = true)
    private String id;
    @GraphId
    private Long identifier;
    @RelatedTo(elementClass = PersonNode.class, type = "CHILD")
    private Set<Person> children;

    void addChild(Person child) {
        if (this.children == null) {
        this.children = new HashSet<>();
        }
        this.children.add(child);
    }
}

PersonNode.java:

@NodeEntity
public class PersonNode implements Person {

    @RelatedTo(elementClass = FamilyNode.class, type = "CHILD", direction = INCOMING)
    private Family childOf;
    @Indexed(indexName = "people", unique = true)
    private String id;
    @GraphId
    private Long identifier;
}

Семейный репозиторий.java:

public interface FamilyRepository extends GraphRepository<Family> {
    public FamilyNode findById(String id);
}

FamilyServiceImpl.java:

@Service
public class FamilyServiceImpl implements FamilyService {

    @Autowired
    private FamilyRepository families;
    @Autowired
    private Neo4jTemplate template;

    @Override
    public List<Family> getFamilies(String[] ids) {
        List<Family> families = new ArrayList<>();
        for (String id : ids) {
            families.add(getFamily(id));
        }
        return families;
    }

    @Override
    public Family getFamily(String id) {
        return familyNode(id);
    }

    @Override
    @Transactional
    public Family createFamily(Family family) {
        return lazyLoadRelationships((FamilyNode) this.families.save(family));
    }

    @Override
    @Transactional
    public Family updateFamily(String id, FamilyNodeUpdater updater) {
        return this.families.save(updater.update(familyNode(id)));
    }

    private FamilyNode familyNode(String id) {
        return lazyLoadRelationships(this.families.findById(id));
    }

    private FamilyNode lazyLoadRelationships(FamilyNode family) {
        this.template.fetch(family.getFather());
        this.template.fetch(family.getMother());
        this.template.fetch(family.getChildren());
        return family;
    }

}

и неудачный тест FamilyServiceTest.java:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { TestConfig.class })
public class FamilyServiceTest {

    @Configuration
    @ComponentScan(basePackageClasses = { com.bonevich.ancestral.family.FamilyServiceImpl.class }, resourcePattern = "FamilyServiceImpl.class")
    @EnableNeo4jRepositories(basePackageClasses = { com.bonevich.ancestral.family.FamilyNode.class })
    static class TestConfig extends Neo4jConfiguration {
        @Bean
        public GraphDatabaseService graphDatabaseService() {
            return new GraphDatabaseFactory().newEmbeddedDatabaseBuilder("/data/neo4j/ancestral-familyservicetest/")
                    .newGraphDatabase();
        }
    }

    @Autowired
    private FamilyService families;

    @Autowired
    private GraphDatabaseService graphDatabaseService;

    @Autowired
    private Neo4jTemplate neo4jTemplate;

    @Test
    public void testUpdateFamily() {
        this.families.createFamily(FamilyNode.instance("testFamily"));

        Transaction tx = this.graphDatabaseService.beginTx();
        PersonNode person = PersonNode.instance("John", "Johanson", "M", "a_person");
        PersonNode expectedChild = this.neo4jTemplate.save(person);
        final long childId = expectedChild.getIdentifier();
        tx.success();
        tx.finish();

        Family actualFamily = this.families.updateFamily("testFamily", new FamilyNodeUpdater() {
            @Override
            public FamilyNode update(FamilyNode family) {
                family.addChild(FamilyServiceTest.this.neo4jTemplate.findOne(childId, PersonNode.class));
                return family;
            }
        });

        assertThat(actualFamily.getId(), is("testFamily"));
        assertThat(actualFamily.getChildren().get(0), is((Person) expectedChild));
    }

}

Запуск этого теста дает следующий стек исключений:

org.neo4j.graphdb.NotInTransactionException
    at org.neo4j.kernel.impl.core.NoTransactionState.acquireWriteLock(NoTransactionState.java:43)
    at org.neo4j.kernel.impl.transaction.LockType$2.acquire(LockType.java:51)
    at org.neo4j.kernel.impl.core.NodeManager.getNodeForProxy(NodeManager.java:473)
    at org.neo4j.kernel.InternalAbstractGraphDatabase$6.lookup(InternalAbstractGraphDatabase.java:733)
    at org.neo4j.kernel.impl.core.NodeProxy.createRelationshipTo(NodeProxy.java:207)
    at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.obtainSingleRelationship(RelationshipHelper.java:62)
    at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createSingleRelationship(RelationshipHelper.java:142)
    at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createAddedRelationships(RelationshipHelper.java:96)
    at org.springframework.data.neo4j.fieldaccess.RelatedToFieldAccessor.createAddedRelationships(RelatedToFieldAccessor.java:78)
    at org.springframework.data.neo4j.fieldaccess.RelatedToCollectionFieldAccessorFactory$RelatedToCollectionFieldAccessor.setValue(RelatedToCollectionFieldAccessorFactory.java:68)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.updateValue(ManagedFieldAccessorSet.java:112)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.update(ManagedFieldAccessorSet.java:100)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.add(ManagedFieldAccessorSet.java:126)
    at com.bonevich.ancestral.family.FamilyNode.addChild(FamilyNode.java:124)
    at com.bonevich.ancestral.family.FamilyServiceTest$1.update(FamilyServiceTest.java:64)
    at com.bonevich.ancestral.family.FamilyServiceImpl.updateFamily(FamilyServiceImpl.java:42)
    at com.bonevich.ancestral.family.FamilyServiceTest.testUpdateFamily(FamilyServiceTest.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Я должен поверить, что что-то упускаю при настройке SDN или транзакций, но не смог это отследить.


person bonez    schedule 24.09.2013    source источник
comment
Будучи новичком в StackOverflow, может ли кто-нибудь объяснить голосование против?   -  person bonez    schedule 24.09.2013
comment
В вашей сущности я бы выбрал @RelatedTo(elementClass = PersonNode.class, type = "CHILD") private Set<Person> children = new Hashset<>(); и удалил нулевую проверку из addChild()   -  person PhilBa    schedule 25.09.2013
comment
Это тоже не решило проблему.   -  person bonez    schedule 25.09.2013
comment
Это было просто предложение и не предназначалось для исправления.   -  person PhilBa    schedule 25.09.2013


Ответы (1)


Недавно я ответил на этот вопрос здесь: Spring Data Neo4J - NotInTransactionException при изменении набора nodeEntity

Короче говоря, это связано с тем, что эти списки являются специальными списками, которые поддерживаются SDN, и любые изменения немедленно сохраняются в БД. Если вы хотите предотвратить такое поведение, вы должны использовать Iterable в своих классах моделей. Если после прочтения ссылки у вас возникнут дополнительные вопросы, просто напишите их в комментариях.

person PhilBa    schedule 25.09.2013
comment
Да, я видел этот вопрос. Я хочу такого поведения - отношения между семьей и ребенком должны быть созданы. Итак, требуется Сет. На другой вопрос, на который вы ответили, я не считаю, что на него ответили. @AndyB (первоначальный вопросник) только что отметил, что он решил свою проблему, но не дал конкретных подробностей о том, в чем заключалось решение (только что-то об исправлении автопроводки). - person bonez; 25.09.2013
comment
О, и я слишком новичок, чтобы комментировать этот вопрос. Я попытался разместить вопрос как ответ, но модератор удалил его (без сомнения, правильно; но все же у меня не было возможности, поэтому я разместил этот вопрос). - person bonez; 25.09.2013
comment
Что ж, в этом случае вам просто нужно внести модификацию Set в транзакцию. Либо вручную, расширяя транзакцию (обратите внимание, что `family.addChild(FamilyServiceTest.this.neo4jTemplate.findOne(childId, PersonNode.class));` выполняется вне транзакции), либо выполняя модификацию внутри метода, аннотированного @ Транзакционный. - person PhilBa; 25.09.2013
comment
Итак, для вашего тестового примера перемещение `tx.success(); tx.finish();` прямо перед утверждением должно решить проблему - person PhilBa; 25.09.2013
comment
Транзакция в тесте предназначена только для сохранения объекта Person. Тест запускает updateFamily(), который является служебным вызовом с использованием @Transactional. Последнее - это то, что терпит неудачу. - person bonez; 25.09.2013
comment
Я думаю, что у вас не настроено управление транзакциями. Я не очень хорошо знаком с конфигурацией на основе Java, но вы можете попробовать это: @EnableTransactionManagement (spring.io/blog/2011/06/10/) - person PhilBa; 25.09.2013