Reputation: 5857
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
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