Rens Verhage
Rens Verhage

Reputation: 5857

Transaction management not working in neo4j-omg when using SessionFactory

I wrote a small Spring Boot application that uses spring-boot-starter-data-neo4j to connect to a Neo4j instance.

I'd like to execute some updates through Cypher queries, but can't get transaction management to work when opening a Session using the SessionFactory. Transaction management works fine using OGM annotated entities through a Neo4jRepository.

For testing purposes, I wrote two simple services annotated with @Transactional:

Using OGM annotated class and a Neo4jRepository:

@Service
@Transactional
public class OgmServiceImpl implements OgmService {
    private final PersonRepository personRepository;

    public OgmServiceImpl(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @Override
    public void storeData() {
        personRepository.save(new Person("Jack"));
        if (true) {
            throw new IllegalStateException();
        }
        personRepository.save(new Person("Jill"));
    }
}

When executing the storeData() method, neither Jack nor Jill is saved to Neo4j. Works as expected.

The other service does the same by executing Cypher queries on the Neo4j Session:

@Service
@Transactional
public class CypherServiceImpl implements CypherService {
    private final SessionFactory sessionFactory;

    public CypherServiceImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    public void storeData() {
        Session session = sessionFactory.openSession();
        session.query("CREATE (p:Person { name: 'Jack' })", Map.of());
        if (true) {
            throw new IllegalStateException();
        }
        session.query("CREATE (p:Person { name: 'Jill'})", Map.of());
    }
}

However, transaction management fails, as Jack is stored in Neo4j.

To me, this behavior is quite unexpected. I can get transaction management to work by explicitly starting a transaction, but that is not the way I like it to be:

    public void storeData() {
        Session session = sessionFactory.openSession();
        try (Transaction tx = session.beginTransaction()) {
            session.query("CREATE (p:Person { name: 'Jack' })", Map.of());
            if (true) {
                throw new IllegalStateException();
            }
            session.query("CREATE (p:Person { name: 'Jill'})", Map.of());
            tx.commit();
        }
    }

Do I need to configure the SessionFactory myself to get this to work? Why doesn't Spring Boot take care of this?

Upvotes: 1

Views: 412

Answers (1)

Shababb Karim
Shababb Karim

Reputation: 3713

Just looked at the docs. According to the docs when you programatically call SessionFactory#openSession a completely new session is created which isn't necessarily in the transaction scope specified with the @Transactional annotation.

To further ensure this you can try checking for an assertion error with this code:

@Override
public void storeData() {
    Session session = sessionFactory.openSession();
    assert session.getTransaction() != null;

    session.query("CREATE (p:Person { name: 'Jack' })", Map.of());       
    session.query("CREATE (p:Person { name: 'Jill'})", Map.of());
} 

This should result in an assertion error. Unlike hibernate which provides a way of getting the current session I couldn't find neo4J-ogm way to do it though.

The code written below works because even though you are creating a completely new Session is created the transaction is managed by you. Here you are creating a self managed transaction, not the Spring container managed transaction, although it is generally not a good idea.

public void storeData() {
    Session session = sessionFactory.openSession();
    try (Transaction tx = session.beginTransaction()) {
        session.query("CREATE (p:Person { name: 'Jack' })", Map.of());
        if (true) {
            throw new IllegalStateException();
        }
        session.query("CREATE (p:Person { name: 'Jill'})", Map.of());
        tx.commit();
    }
}

Upvotes: 1

Related Questions