SDN4 - MappingException thrown when using an interface as the end of a RelationshipEntity - neo4j

This functionality was working at one point but seems to have broken in the latest SDN4 snapshot (7-16-15)
I have two node classes, one representing intermediate, non-leaf nodes and one representing leaf vertex nodes of degree one. The two classes implement a common interface.
public interface Node {
...
}
#NodeEntity
public class SimpleNode implements Node {
...
}
#NodeEntity
public class SimpleLeafNode implements Node {
...
}
The former can be related to other intermediate nodes OR leaf nodes and I have modeled this relationship by mapping the SimpleNode class to the Node INTERFACE:
#RelationshipEntity
public class SimpleRelationship {
#StartNode
private SimpleNode parent;
#EndNode
private Node child;
}
When I try to start up my Spring Boot application, I receive an SDN mapping exception:
Caused by:
10:51:04.173 [DEBUG] org.neo4j.ogm.metadata.MappingException: No identity field found for class: com.sdn4demo.entity.Node
10:51:04.174 [DEBUG] at org.neo4j.ogm.metadata.info.ClassInfo.identityField(ClassInfo.java:291)
10:51:04.174 [DEBUG] at org.springframework.data.neo4j.mapping.Neo4jPersistentProperty.<init>(Neo4jPersistentProperty.java:76)
10:51:04.174 [DEBUG] at org.springframework.data.neo4j.mapping.Neo4jMappingContext.createPersistentProperty(Neo4jMappingContext.java:100)
Again, this was working before the 7-16-15 snapshot so my questions is - is this not supported functionality? Is this a bug?
A contrived example exists at:
https://github.com/simon-lam/sdn-4-demo
Reproduce-able by doing ./gradlew clean test --debug

It's a bug. We're currently working on sorting stuff out regarding SD-commons and Spring DATA REST integration and this is one of those consequences of using the bleeding edge stuff.
Using RC1 is probably the best bet for now. Keep an eye on this JIRA issue to see when it's completed: https://jira.spring.io/browse/DATAGRAPH-564

Related

How to implement bidirectional relationship in SDN 6

How should bidirectional relationships be implemented in SDN 6?
I have the following implementation of two entities and a repository
#Node("UserAccount")
class UserAccount(
#Id
val username: String,
) {
#Relationship(type = "HAS_ACCOUNT")
var person: Person? = null
}
#Node("Person")
class Person(
#Relationship(type = "HAS_ACCOUNT")
var userAccount: UserAccount? = null,
) {
#Id
#GeneratedValue
var personId: Long = -1
}
#Repository
interface PersonRepository : Neo4jRepository<Person, Long> {
fun findByPersonId(personId: Long): Optional<Person>
}
After fetching person by personId the following exception occurs:
org.springframework.data.mapping.MappingException: The node with id 646 has a logical cyclic mapping dependency. Its creation caused the creation of another node that has a reference to this.
The problem is rooted in the dependency chain (cycle) Person -> UserAccount -> Person, as you have already discovered.
Spring Data Neo4j has a dead-lock detection during mapping to avoid stack overflow / infinite recursion if it comes to cyclic mapping dependencies. This is why you see the exception.
What happens in detail?
SDN starts mapping the Person that depends on an UserAccount instance (constructor) and puts it in a set of "objects in construction".
The UserAccount instance gets created AND the properties of it populated. This has to be done because a property can -besides public visibility and setter- also be "set" by using a wither method (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#mapping.fundamentals.property-population) and would result in a new (and different instance of the UserAccount)
The person property needs the Person-in-construction -> Exception
To avoid this, you can either remove the constructor dependency and define a field.
Another option is to discard the bi-directional relationship definition completely.

How to correctly address "can't add a _parent field that points to an already existing type, that isn't already a parent" on app startup

I'm running into a problem with my Elasticsearch Document index creation failing on startup with "java.lang.IllegalArgumentException: can't add a _parent field that points to an already existing type, that isn't already a parent". I'm not sure if this is due to a version upgrade or b/c I am starting with a brand new Elasticsearch server install.
Contrived example that shows what I'm seeing:
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user")
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department")
public class DepartmentSearchResult implements Serializable {
...
}
When I start my application I get that exception. If I check the ElasticSearch server, I see the "hr_index" index and the "department" mapping, but the "user" mapping is not created.
If I understand the error, it's because "department" is being created and then when Spring tries to create "user" with "department" as its parent, it doesn't like that, since department wasn't previously marked as a parent when it was created.
Is there some way (via annotation?) to denote DepartmentSearchResult as being a parent when it's created somehow?
Or, is it possible to give a hint to Spring Data Elasticsearch as to what order it should create the indices/mappings? I have seen some other posts (Spring Data Elasticsearch Parent/Child Document Repositories / Test execution error) but disabling auto creation and then manually creating it myself (either as part of my Spring codebase or external to the app) seems kind of "un-Spring-y" to me?
Or, is there some other approach I should be taking?
(This is a working Spring application that had been using Spring 4.2.1 and Spring Data Release Train Gosling, that I'm attempting to upgrade to use Spring 5.0.0 and Spring Data Release Train Kay. As part of this I am starting with a fresh Elasticsearch install, and so I'm not sure if this error is coming from the upgrade or just b/c the install is clean).
In the SD ES, issues related to the parent-child relationship at now really poorly developed.
The problem is most likely due to the fact that you are using a clean installation of Elasticsearch. Before the update, the problem did not arise, because mappings have already been created. For the solution, you can use elasticsearchTemplate, which is part of SD ES, and ApplicationListener. It's simple. Just 3 steps.
Drop index in ES (it only needs one time):
curl -XDELETE [ES_IP]:9200/hr_index
Tell SD ES not to create indices and mappings automatically
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user", createIndex = false)
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department", createIndex = false)
public class DepartmentSearchResult implements Serializable {
...
}
Add a ApplicationListener:
#Component
public class ApplicationStartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//Mapping for child must be created only if mapping for parents doesn't exist
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
elasticsearchTemplate.createIndex(DepartmentSearchResult.class);
try {
elasticsearchTemplate.getMapping(DepartmentSearchResult.class);
} catch (ElasticsearchException e) {
elasticsearchTemplate.putMapping(UserSearchResult.class);
elasticsearchTemplate.putMapping(DepartmentSearchResult.class);
}
}
}
P.S. Among other things, it is worth paying attention to the fact that with the release of ES 5.6, a process for removing types began. This inevitably entails the removal of the parent-child relationship. In one of the next releases of the SD ES, we will provide the opportunity to work with joins. Working with parent-child relationships is unlikely to be improved

How to make node changes in a TransactionEventHandler that are returned within the same CREATE query

I am trying to implement a plugin for neo4j to add an autoincrement ID using GraphAware library. To this end, I've written the following classes:
public class ModuleBootstrapper implements RuntimeModuleBootstrapper
{
public RuntimeModule bootstrapModule(String moduleId, Map<String, String> config, GraphDatabaseService database)
{
return new MyModule(moduleId, config, database);
}
}
And:
public class MyModule extends BaseTxDrivenModule<Void>
{
int counter = 0;
public Void beforeCommit(ImprovedTransactionData transactionData)
throws DeliberateTransactionRollbackException
{
if (transactionData.mutationsOccurred()) {
for (Node newNode : transactionData.getAllCreatedNodes()) {
newNode.setProperty("id", counter++);
}
}
}
}
And for the testing I can execute:
CREATE (n);
And then:
MATCH (n) RETURN n;
And I can see the effect of my plugin as some id property added to the node. But when I run:
CREATE (n) RETURN n;
The returned node does not have the mentioned id property but again, when I match the node in a separate query, I see the things have worked out just fine. It's just that in the CREATE query, the returned node infos are the ones before my plugin modified them.
The questions are; why is that? Didn't I modify the nodes through my plugin within the transaction? Shouldn't the returned nodes be showing the modifications I've made on them? Is there any way I can make this happen?
While you're still within the transaction, the Cypher result is already computed and there is no clean way to add additional informations to it.
I guess a feature request on the neo4j repository could be cool but in total honesty this would require a serious change into the neo4j core codebase.
BTW, the incremental ID is already implemented in the graphaware-uuid plugin : https://github.com/graphaware/neo4j-uuid#specifying-the-generator-through-configuration

Spring Data Neo4j 4 very slow for large hierarchical queries

I am currently using SpringDataNeo4j–4.1.0.M1 and have a use case where I need to get a sub-graph (actually a tree) and return it in an hierarchical structure similar to this (these sub-graphs are usually around 500 nodes in size and have an unspecified depth):
<Pathway dbId="1" displayName="Name1">
<Pathway dbId="2" displayName="Name2">
<Pathway dbId="3" displayName="Name3">
<Reaction dbId="4" displayName="Name4" />
<Reaction dbId="5" displayName="Name5" />
<Pathway dbId="6" displayName="Name6">
<Reaction dbId="7" displayName="Name7" />
</Pathway>
</Pathway>
...
The data model:
#NodeEntity
public abstract class Event {
#GraphId
private Long id;
private Long dbId;
private String displayName;
... other relationships and properties
}
#NodeEntity
public class Pathway extends Event {
#Relationship(type="hasEvent", direction = Relationship.OUTGOING)
private List<Event> hasEvent;
... other relationships and properties
}
#NodeEntity
public class Reaction extends Event {
... other relationships and properties
}
To solve this I have created the following query:
String query = "Match (n:Pathway{dbId:123456})-[r:hasEvent*]->(m:Event) Return n,r,m";
Result result = neo4jTemplate.query(query,Collections.<~>emptyMap());
return result.iterator().next().get("n");
This is working and returns the result as expected. The only problem is the speed. While testing the query using the Neo4j-Browser I get a result within 50-100ms. Using SDN it takes over 2 seconds for Spring to map the result to objects.
The question now is: Is there any way I can speed up this query in Spring or is there a better solution where I can return this hierarchical structure in Cypher using something like nested Collections(since I only need the names and ids and not the objects themselves)?

Spring Data Neo4j - Unit Test - Transaction rollbacked but data not deleted

I am building an application using spring-boot (1.1.8.RELEASE), spring-data-neo4j (3.2.0.RELEASE) in order to connect to a stand alone neo4j server via rest api. I am using spring-test in order to test the application I have implemented a unit test to create a Node and retrieved it. It is working well but the new node remained in the database after the test is completed, however I expect the transaction to be rollbacked and the node deleted
However in the console I can see the following statement.
"Rolled back transaction after test execution for test context...
** I don't understand why based on the console the roll back seems to have occured but the transaction has been committed to the database. **
It would be really appreciated if somebody could help me to figure out where the issue is coming from.
Find below my spring configuration
#Configuration
#ComponentScan
#EnableTransactionManagement
#EnableAutoConfiguration
public class AppConfig extends Neo4jConfiguration {
public AppConfig() {
setBasePackage("demo");
}
#Bean
public GraphDatabaseService graphDatabaseService(Environment environment) {
return new SpringRestGraphDatabase("http://localhost:7474/db/data");
}
}
Find below my test class
#SuppressWarnings("deprecation")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = AppConfig.class)
#Transactional
public class AppTests {
#Autowired
private Neo4jTemplate template;
#Test
public void templateTest() {
Person person = new Person();
person.setName("Benoit");
person.setBorn(1986);
Person newPerson = template.save(person);
Person retrievedPerson = template.findOne(newPerson.getNodeId(),Person.class);
Assert.assertEquals("Benoit", retrievedPerson.getName());
}
}
I tried to add the following annotation in my unit test class but it did not change anything:
#TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
I also tried to add the following in my unit test based on what I have seen in other posts
implements ApplicationContextAware
Thank you for your help
Regards
The behavior you are experiencing is to be expected: there is nothing wrong with transaction support in the Spring TestContext Framework (TCF) in this regard.
The TCF manages transactions via the configured transactionManager.
So when you switched to an embedded database and configured the transaction manager with the data source for that embedded database, that works perfectly. The issue is that the transaction support in Neo4J-REST does not tie in with Spring's transaction management facilities. As Michael Hunger stated in the other thread you referenced, an upcoming version of the Neo4J-REST API should address this issue.
Note that annotating your test class with #TransactionConfiguration has zero effect since you are merely overriding the defaults with the defaults which achieves nothing. Furthermore, implementing ApplicationContextAware in a test class has no effect on transaction management.
Regards,
Sam (spring-test component lead)

Resources