0
votes

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?

1

1 Answers

0
votes

Volker, you're right and the default Type-Representation Strategies are a bit overconstraining here.

Actually we wanted to add a Relationship-TRS that just uses the rel-type to map to SDN but I haven't gotten to it.

You could also just add your property as

@Indexed(unique=true) private String knownAsId;

and then construct one of these relationship-entity instances and use

template.save(relation);

That should also work.