I want to model a graph containing companies and their business relations in Neo4j using Spring-Data Neo4j. So I created nodes for companies and edges for business relations. However there are a bunch of different types of relations (buyer, supplier, manufacturer, etc and one generic verions "DOES_BUSINESS_WITH"). Each relation has exactly one property which is the internal ID of the source company for the target company, e.g. a buyer has an internal ID for each supplier. And each pair of companies can have exactly one relation of each type per direction.
The way I set this up is a @NodeEntity for Companies and one @RelationshipEntity for each of the different business relations. Since all these relationships have identical properties they all extend the generic class and simply add the correct label to the edge:
@RelationshipEntity(type="DOES_BUSINESS_WITH")
public class BusinessRelation {
@GraphId
private Long id;
@Fetch
@StartNode
private Company from;
@Fetch
@EndNode
private Company to;
@Indexed
private String knownAsId;
// getters and setters omitted
}
@RelationshipEntity(type="SUPPLIER")
public class SupplierRelation extends BusinessRelation {
public SupplierRelation() {
}
}
Since I have quite a few of those different relations I wanted to use a single repository interface to handle all of them.
Now when I get a relationship I want to either create a new relationship between the (already existing) companies or modify the existing one if necessary. So I decided to go with the CREATE UNIQUE cypher clause like such:
public interface BusinessRelationRepository extends GraphRepository<BusinessRelation> {
@Query(value="match (source:Company {orgId:{0}}),(target:Company {orgId:{1}}) create unique (source)-[r:SUPPLIER]->(target) return r", elementClass=SupplierRelation.class)
SupplierRelation createSupplierMarker(String sourceOrgId, String targetOrgId);
// ...
}
Unfortunately this throws an exception:
org.springframework.dao.DataRetrievalFailureException: No such property, '__type__'.; nested exception is org.neo4j.graphdb.NotFoundException: No such property, '__type__'.
at org.springframework.data.neo4j.support.Neo4jExceptionTranslator.translateExceptionIfPossible(Neo4jExceptionTranslator.java:66)
at org.springframework.data.neo4j.support.Neo4jTemplate.translateExceptionIfPossible(Neo4jTemplate.java:448)
at org.springframework.data.neo4j.support.Neo4jTemplate.doExecute(Neo4jTemplate.java:459)
at org.springframework.data.neo4j.support.Neo4jTemplate.access$000(Neo4jTemplate.java:87)
at org.springframework.data.neo4j.support.Neo4jTemplate$2.doInTransaction(Neo4jTemplate.java:471)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at org.springframework.data.neo4j.support.Neo4jTemplate.exec(Neo4jTemplate.java:468)
at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.execute(GraphRepositoryQuery.java:82)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy65.createSupplierMarker(Unknown Source)
I was hoping that adding the elementClass property to the @Query annotation would give SDN enough information to map the result back to the desired SupplierRelation, but it didn't. It actually had no effect whatsoever.
I can solve that problem by adding the __type__ property to the Cypher query myself, but then my code has more knowledge about the inner workings of Spring Data Neo4j than I'd like. So this works:
@Query(value="match (source:Company {orgId:{0}}),(target:Company {orgId:{1}}) create unique (source)-[r:SUPPLIER {__type__:\"SupplierRelation\"}]->(target) return r")
SupplierRelation createSupplierMarker(String sourceOrgId, String targetOrgId);
Is there any better solution that doesn't require me the __type__ property to be set in my code?