Deleting a Neo4j node from a Spring application - neo4j

I'm trying to delete a node from an embedded graph.
I'm using SDN 3.3.2.RELEASE and my Neo4j is 2.2.4.
But the node is still around after the repository call:
org.junit.ComparisonFailure: expected:<null> but was:<Manufacturer [name=Siemens]>
Here is my test:
#Test
public void testDeleteById() {
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerRepository.deleteManufacturer(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).doesNotExist();
}
The repository is:
public interface Neo4JManufacturerRepository extends GraphRepository<Neo4JManufacturer> {
#Transactional
#Query("start u = node({id}) match u-[r]-() delete u,r")
public void deleteManufacturer(#Param("id") Long id);
Before trying to delete with the deleteManufacturer() method, I was trying to delete with the delete() method, as in:
neo4JManufacturerRepository.delete(manufacturer0.getId());
But I would get the exact same test failure:
org.junit.ComparisonFailure: expected:<null> but was:<Manufacturer [name=Siemens]>
This is my node class:
#NodeEntity
#SequenceGenerator(name = "id_generator", sequenceName = "sq_id_manufacturer")
public class Neo4JManufacturer extends BaseEntity {
#Column(nullable = false, unique = true)
#Indexed
private String name;
public Neo4JManufacturer() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Manufacturer [name=" + name + "]";
}
}
UPDATE:
Following the two comments, I added another find call after the delete call so as to trigger a flush to the data store:
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerRepository.delete(manufacturer0.getId());
loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).doesNotExist();
And I also added the optional clause in the query:
#Transactional
#Query("START u = node({id}) OPTIONAL MATCH u-[r]-() DELETE u,r")
public void deleteManufacturer(#Param("id") Long id);
Now, I get a different error:
No primary SDN label exists .. (i.e one starting with _)
I suppose the flush after the delete is being attempted, but fails for another reason ?
Also, I wonder if the flushing and transactin behavior is the same when using JPA and when using Neo4j. Indeed the following JPA test works just as expected:
#Test
public void testDeleteByUserId() {
User loadedUser = userRepository.findOne(user0.getId());
assertThatUser(loadedUser).exists();
loadedUser = userRepository.deleteByUserId(user0.getId());
loadedUser = userRepository.findOne(user0.getId());
assertThatUser(loadedUser).doesNotExist();
}
My idea is to do the same with the Neo4j database.
UPDATE: I'm now deleting on a transactional service instead of directly on the repository:
#Modifying
#Transactional(rollbackFor = EntityNotFoundException.class)
#Override
public Neo4JManufacturer delete(Long id) throws EntityNotFoundException {
Neo4JManufacturer manufacturer = findById(id);
if (manufacturer == null) {
throw new EntityNotFoundException();
} else {
neo4jManufacturerRepository.delete(manufacturer.getId());
return manufacturer;
}
}
The service is called like:
#Test
public void testDeleteById() {
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerService.delete(manufacturer0.getId());
Neo4JManufacturer anotherManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(anotherManufacturer).doesNotExist();
}
But I still get the same exception:
testDeleteById(it.kahoot.robot.data.neo4j.Neo4JManufacturerRepositoryTest) Time elapsed: 0.137 sec <<< ERROR!
org.springframework.dao.InvalidDataAccessApiUsageException: No primary SDN label exists .. (i.e one starting with _) ; nested exception is java.lang.IllegalStateException: No primary SDN label exists .. (i.e one starting with _)
at org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy.readAliasFrom(LabelBasedNodeTypeRepresentationStrategy.java:136)
at org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy.readAliasFrom(LabelBasedNodeTypeRepresentationStrategy.java:40)
at org.springframework.data.neo4j.support.mapping.TRSTypeAliasAccessor.readAliasFrom(TRSTypeAliasAccessor.java:36)
at org.springframework.data.neo4j.support.mapping.TRSTypeAliasAccessor.readAliasFrom(TRSTypeAliasAccessor.java:26)
at org.springframework.data.convert.DefaultTypeMapper.readType(DefaultTypeMapper.java:102)
UPDATE II: I have now a test that passes fine and behaves as expected. Still the test is under a #Transactional annotation. And it does not do any manual flush. The interesting part, and the reason the test stopped giving the error:
No primary SDN label exists .. (i.e one starting with _)
is that the findById or findOne calls were replaced by a findByName call. Doing either a findById or findOne call would trigger the above error.
Here is how the working test looks like:
#Test
public void testIsDelete() {
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerRepository.delete(manufacturer0.getId());
loadedManufacturer = neo4JManufacturerService.findByName(loadedManufacturer.getName());
assertThatManufacturer(loadedManufacturer).doesNotExist();
}
The repository findByName is:
Neo4JManufacturer findByName(String name);
I had a logger in place which showed the id being set above 0
DEBUG [Neo4JManufacturerRepositoryTest] ==========>> Id: 4 Name: Siemens
Still, using a findById instead of a findByName was giving the above error.
And I wonder why.
UPDATE III:
I have now removed the transactional annotation from the test.
Here is the test:
#Test
public void testDelete() {
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerRepository.delete(manufacturer0.getId());
loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).doesNotExist();
assertThatManufacturer(manufacturer0).exists();
manufacturer0 = neo4JManufacturerRepository.save(manufacturer0);
}
The console says:
2016-09-01 14:45:57,769 DEBUG [main] c.t.d.n.Neo4JManufacturerRepositoryTest ==========>> Before - Created manufacturer0 - Id: 0
2016-09-01 14:45:58,127 DEBUG [main] c.t.d.n.Neo4JManufacturerRepositoryTest ==========>> After - Deleted manufacturer0 - Id: 0
2016-09-01 14:45:58,320 DEBUG [main] c.t.d.n.Neo4JManufacturerRepositoryTest ==========>> Before - Created manufacturer0 - Id: 4
2016-09-01 14:45:58,850 DEBUG [main] c.t.d.n.Neo4JManufacturerRepositoryTest ==========>> After - Deleted manufacturer0 - Id: 4
2016-09-01 14:45:59,035 DEBUG [main] c.t.d.n.Neo4JManufacturerRepositoryTest ==========>> Before - Created manufacturer0 - Id: 8
Tests run: 4, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 23.625 sec <<< FAILURE! - in com.thalasoft.data.neo4j.Neo4JManufacturerRepositoryTest
testDelete(com.thalasoft.data.neo4j.Neo4JManufacturerRepositoryTest) Time elapsed: 0.334 sec <<< ERROR!
org.springframework.dao.DataRetrievalFailureException: Node 8 not found; nested exception is org.neo4j.graphdb.NotFoundException: Node 8 not found
It errors on the last source code line of the test, the one doing the save() operation.
Why does it look for a node ? Why does it complain it is not found ? Shouldn't it be not found since I'm trying to create it ?

Since your Cypher query is using MATCH u-[r]-(), it will fail if the specified node does not participate in any relationships.
You should use OPTIONAL MATCH instead, which allows the query to succeed even if the specified node does not participate in any relationships:
"START u = node({id}) OPTIONAL MATCH u-[r]-() DELETE u,r"

Within the same transaction the node is still visible, and as your test is very probably #Transactional per method, the tx is only finished (rolled back) after the method finishes
see: http://neo4j.com/docs/stable/transactions-delete.html
If you add a transactional handling (commit) after the delete without having the #Transactional on top of your test. You should not be able to see the node anymore.
Update
I ran your test, it is as I explained.
Your test (via its superclass) had a #Transactional annotation, so that all operations within the test method are executed in one transaction and afterwards rolled back.
If you want to see the real world behavior the deletion would have to happen in it's own tx (as part of your service call) after that transaction is finished, the node is no longer visible.
Also your assert tested that the previously loaded node would be null, which is never the case because the already existing variable is not changed as part of your delete operation.
I removed the global #Transactional annotation from your test-superclass and changed the test method to this:
#Test
public void testDeleteById() {
Neo4JManufacturer loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).exists();
neo4JManufacturerService.delete(manufacturer0.getId());
loadedManufacturer = neo4JManufacturerRepository.findOne(manufacturer0.getId());
// both would work
// loadedManufacturer = neo4JManufacturerService.findById(manufacturer0.getId());
assertThatManufacturer(loadedManufacturer).doesNotExist();
}
Now it passes and exhibits the expected behavior: after the tx with the delete is completed the node is gone.

Related

spock testing of endpoint and repository

Working on my Spring 2.67 and Spock 2.1-groovy-3.0 testing. I have the basic testing working but now trying some integration testing without success. I have a controller with:
private ApiService apiService;
#Autowired
public ApiController(ApiService apiService) {
this.apiService = apiService;
}
#GetMapping("api/{scannedId}")
#ResponseBody
public ResponseEntity getScannedId(#PathVariable String scannedId) {
try {
logger.info("ApiKey Controller received GET /api/" + scannedId);
ApiKey found = apiService.retrieveValidApiKey(scannedId);
...
}
...
The apiService has :
private ApiRepository apiRepository;
#Autowired
public ApiService(ApiRepository apiRepository) {
this.apiRepository = apiRepository;
}
public ApiKey retrieveValidApiKey(String uuid) {
ApiKey anApi = apiRepository.getApiKeyByApiKey(uuid);
if (anApi == null) {
logger.info("ApiService.retrieveValidApiKey({}) failed to find a matching ApiKey", uuid);
return null;
}
I have a Spock test that seeds the database with two values and then successfully calls the /api endpoint. I have code in the test that confirms the two values were inserted, but when the actual ApiService class is called, they are not found:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestDatabase
class ApiControllerTest extends Specification {
#Shared Logger logger = LoggerFactory.getLogger(this.getClass())
#Autowired
ApiController apiController
#Autowired
ApiRepository apiRepository
#Transactional
def "GET by scannedId using apikey #apiKey should be #resultCode"() {
given:
def foundList = apiRepository.findAll()
logger.info("{} apiKeys in repository", foundList.size())
for (int i = 0; i < foundList.size(); i++) {
logger.info("Found ApiKey #{} apiKey: {} & uuid: {}", i, foundList.get(i).apiKey, foundList.get(i).uuid)
}
when:
def foundListCount = apiRepository.getApiKeyByApiKey(apiKey)
logger.info("FoundList: {}", foundListCount)
ResponseEntity<ApiKey> result = restTemplate.getForEntity( "/api/{scannedId}", ApiKey.class, apiKeyValue1)
logger.info("TestRestTemplate returned apikey: {}", result)
then:
assert result.getStatusCode() == resultCode
where:
apiKey || resultCode
"testApiKey3" || HttpStatus.NOT_FOUND
apiKeyValue1 || HttpStatus.OK
apiKeyValue2 || HttpStatus.OK
}
def setup() {
def apiKey1 = new ApiKey(apiKey: apiKeyValue1, uuid: uuid1, beginDate: beginDate1, endDate: endDate1)
def apiKey2 = new ApiKey(apiKey: apiKeyValue2, uuid: uuid2, beginDate: beginDate2, endDate: endDate2)
apiRepository.saveAndFlush(apiKey1)
apiRepository.saveAndFlush(apiKey2)
}
When I run the test, the logger in the test method spits out all the persisted values. But the test fails because the ApiService.getScannedId fails because it does not see the values persisted in test setup.
I cannot use the #DataJpaTest because the ApplicationContext isn't loaded then, so the endpoints fail.
I am not sure why Spock sees the values persisted via Repository, but the ApiService doesn't. Is it a context issue? I really would like to test without mocks here if at all possible.
The problem is that your test is annotated with #Transactional that means that only things that run in that method can see the data. The rest request you are sending out, will be handled by another thread that doesn't have access to the transaction and thus will not see the data.
You'll have to remove the annotation if you want it to work, but then you'll also have to clean the inserted data manually at the end of the test/cleanup(), since you can't rely on the transaction rollback.

Deploying a transaction event listener in a Neo4jDesktop installation

I have created a project that contains an ExtensionFactory subclass annotated as #ServiceProvider that returns a LifecycleAdapter subclass which registers a transaction event listener in its start() method, as shown in this example. The code is below:
#ServiceProvider
public class EventListenerExtensionFactory extends ExtensionFactory<EventListenerExtensionFactory.Dependencies> {
private final List<TransactionEventListener<?>> listeners;
public EventListenerExtensionFactory() {
this(List.of(new MyListener()));
}
public EventListenerExtensionFactory(List<TransactionEventListener<?>> listeners) {
super(ExtensionType.DATABASE, "EVENT_LISTENER_EXT_FACTORY");
this.listeners = listeners;
}
#Override
public Lifecycle newInstance(ExtensionContext context, Dependencies dependencies) {
return new EventListenerLifecycleAdapter(dependencies, listeners);
}
#RequiredArgsConstructor
private static class EventListenerLifecycleAdapter extends LifecycleAdapter {
private final Dependencies dependencies;
private final List<TransactionEventListener<?>> listeners;
#Override
public void start() {
DatabaseManagementService managementService = dependencies.databaseManagementService();
listeners.forEach(listener -> managementService.registerTransactionEventListener(
DEFAULT_DATABASE_NAME, listener));
dependencies.log()
.getUserLog(EventListenerExtensionFactory.class)
.info("Registering transaction event listener for database " + DEFAULT_DATABASE_NAME);
}
}
interface Dependencies {
DatabaseManagementService databaseManagementService();
LogService log();
}
}
It works fine in an integration test:
public AbstractDatabaseTest(TransactionEventListener<?>... listeners) {
URI uri = Neo4jBuilders.newInProcessBuilder()
.withExtensionFactories(List.of(new EventListenerExtensionFactory(List.of(listeners))))
.withDisabledServer()
.build()
.boltURI();
driver = GraphDatabase.driver(uri);
session = driver.session();
}
Then I copy the jar file in the plugins directory of my desktop database:
$ cp build/libs/<myproject>.jar /mnt/c/Users/albert.gevorgyan/.Neo4jDesktop/relate-data/dbmss/dbms-7fe3cbdb-11b2-4ca2-81eb-474edbbb3dda/plugins/
I restart the database and even the whole desktop Neo4j program but it doesn't seem to identify the plugin or to initialize the factory: no log messages are found in neo4j.log after the start event, and the transaction events that should be captured by my listener are ignored. Interestingly, a custom function that I have defined in the same jar file actually works - I can call it in the browser. So something must be missing in the extension factory as it doesn't get instantiated.
Is it possible at all to deploy an ExtensionFactory in a Desktop installation and if yes, what am I doing wrong?
It works after I added a provider configuration file to META-INF/services, as explained in https://www.baeldung.com/java-spi. Neo4j finds it then.

NullPointerException while loading data from Neo4j database with OGM driver

I am using Neo4j OGM 2.0.4 driver with Java (embedded driver). When I do save(item) on session everything looks all right. Hovewer when I want to load items from database I get this exception:
16:01:23.182 [main] INFO org.neo4j.ogm.service.DriverService - Using driver: org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver
16:01:24.768 [main] DEBUG org.neo4j.ogm.service.Components - Setting driver to: org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver
16:01:24.782 [main] WARN org.neo4j.ogm.session.Neo4jSession - Thread 1: neo4jCMS.entity.Author is not an instance of a persistable class
16:01:24.782 [main] WARN org.neo4j.ogm.session.Neo4jSession - Thread 1: neo4jCMS.entity.Author is not an instance of a persistable class
Saved
Exception in thread "main" java.lang.NullPointerException
at org.neo4j.ogm.MetaData.entityType(MetaData.java:280)
at org.neo4j.ogm.session.Neo4jSession.entityType(Neo4jSession.java:486)
at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:60)
at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:108)
at org.neo4j.ogm.session.Neo4jSession.loadAll(Neo4jSession.java:152)
at neo4jCMS.TestSite.run(TestSite.java:35)
at neo4jCMS.Application.main(Application.java:20)
While executing:
Author author1 = new Author("Author no 1");
Author author2 = new Author("Author no 2");
session.save(author1);
session.save(author2);
System.out.println("Saved");
Iterable<Author> authors = session.loadAll(Author.class);
for (Author author : authors)
{
System.out.println("Author: " + author.getName());
}
My node class is:
#NodeEntity
public class Author
{
#GraphId
private Long id;
private String _name;
public Author() { _name = "";}
public Author(String name)
{
_name = name;
}
public String getName()
{
return _name;
}
}
First of all you will need to add a no-arg constructor to your Author class in order for the OGM to create objects.
If that does not solve it try rerunning the loadAll code segment again. I have seen the odd Exception on first execution. I thought this was fixed but maybe it's only in 2.0.5-SNAPSHOT.
The issue was in configuration. new SessionFactory() was without argument "myProject.subDirectory". After adding that it works.

Crashes related to GraphRepository#findAll() when using AspectJ

This line in TopLevelTransaction (neo4j-kernel-2.1.2) throws a NullPointerException every time I call next() on an iterator obtained via GraphRepository#findAll():
protected void markAsRollbackOnly()
{
try
{
transactionManager.getTransaction().setRollbackOnly(); // NPE here
}
catch ( Exception e )
{
throw new TransactionFailureException(
"Failed to mark transaction as rollback only.", e );
}
}
I found some threads about similar crashes with slightly different stack traces. The accepted solution on this question is to use "proxy" transaction management, but that seems like a band-aid solution. This question also mentions "proxy" transaction management and suggests that there might be something wrong with the #Transactional annotation when using AspectJ.
Is this legitimately a bug, or have I just set up my project incorrectly? My code is essentially the same as in my standalone hello world, with a slightly more complex main class:
#Component
public class Test2 {
#Autowired
FooRepository repo;
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext("test2");
Test2 test2 = context.getBean(Test2.class);
test2.doStuff();
}
public void doStuff() {
createFoo();
printFoos();
}
#Transactional
public Foo createFoo() {
Foo foo = new Foo();
foo.setName("Derp" + System.currentTimeMillis());
repo.save(foo);
System.out.println("saved " + foo.toString());
return foo;
}
#Transactional
public void printFoos() {
Iterable<Foo> foos = repo.findAll();
System.out.println("findAll() returned instance of " + foos.getClass().getName());
Iterator<Foo> iter = foos.iterator();
System.out.println("iterator is instance of " + iter.getClass().getName());
if(iter.hasNext()) {
iter.next(); // CRASHES HERE
}
}
}
I can post my POM if needed.
I didn't find a bug. Two or three things are required to make this work, depending on whether you want to use proxy or AspectJ transaction management.
First, transaction management must be enabled. Since I'm using annotation-based configuration, I did this by annotating my #Configuration class with #EnableTransactionManagement. Contrary to the docs, the default mode now seems to be AdviceMode.ASPECTJ, not AdviceMode.PROXY.
Next, you need to ensure that the Iterator is used within a transaction. In my example, if I use AdviceMode.PROXY the entire bean containing the #Autowired repository has to be annotated #Transactional. If I use AdviceMode.ASPECTJ I can annotate just the method. This is because the call to the method using the iterator is a self-call from within the bean, and proxy transaction management cannot intercept and manage internal calls.
Finally, if you're using AdviceMode.ASPECTJ you must set up weaving as discussed here.

Index name for class java.lang.String name rel: false idx: true must differ from the default name (SDNĀ 3.0)

After upgrading from SDN 2.3.4 to 3.0.0.RELEASE, I have two tests that fail with :
Index name for class java.lang.String name rel: false idx: true must differ from the default name: BesoinNode
In the BesoinNode class, I previously used #Indexed(indexName = "indexOfNeeds") but removed the indexName because of https://stackoverflow.com/a/22084251/1528942. Now my index declaration is simply :
#Indexed
private String name;
The failing test code is :
#Test
public final void t02_TestBesoinDAORetrieve() {
// We create a new object in the datastore
Besoin need1 = needDAO.create(new BesoinNode("t02 need"));
assertEquals(new Long(1), needDAO.count());
// We retrieve the object from the datastore using its id
Besoin need1bis = needDAO.retrieveById(need1.getId());
assertEquals(need1, need1bis);
assertEquals(need1.getId(), need1bis.getId());
assertEquals(need1.getName(), need1bis.getName());
// We retrieve the object from the datastore using its name
Besoin need1ter = needDAO.retrieveByName(need1.getName());
assertEquals(need1, need1ter);
assertEquals(need1.getId(), need1ter.getId());
assertEquals(need1.getName(), need1ter.getName());
}
The test succeeds if I comment out the call to retrieveByName. Code is below:
#Override
public Besoin retrieveByName(String name) {
return repository.findByPropertyValue("name", name);
}
How can I rewrite my retrieveByName method to solve the error? Thanks!
Note : while this may no be related to the problem, I see in the API that IndexRepository#findByPropertyValue(String, Object) is deprecated. Only AbstractGraphRepository#findByPropertyValue(String, Object) is not deprecated. The problem is that my repository extends GraphRepository and that GraphRepository extends IndexRepository, something I have no control over.
Note 2 : Using #Indexed(indexType = IndexType.SIMPLE) makes the test succeed. But this toggles the old index system (not the new one which is label-based).

Resources