Hi, I have a similar question. There are two node types: MyJob and MyTable. The relationship is MyJob read from or write to MyTable and there are many table options lives in the relationship, such as 'connectorType' = 'mysql', 'username' = 'aaa', 'password' = 'pAss' etc. Different connectorType may have different table options and need to query table option to find related MyTable and MyJob nodes. How to implement it with spring data neo4j? Thanks!
Drawing out the domain model first:
@Node
public class MyJob {
@Id
private final String name;
@Relationship("READS_FROM")
private final List<TableRelationship> readFromTables;
@Relationship("WRITES_TO")
private final List<TableRelationship> writesToTables;
public MyJob(String name, List<TableRelationship> readFromTables, List<TableRelationship> writesToTables) {
this.name = name;
this.readFromTables = readFromTables;
this.writesToTables = writesToTables;
}
// getters if needed
}
@RelationshipProperties
public class TableRelationship {
@RelationshipId
private String id;
@TargetNode
private MyTable table;
@CompositeProperty
private Map<String, String> properties; // (1)
private String connectorType; // (2)
// Getters and setters if needed
}
@Node
public class MyTable {
@Id
private final String tableName;
public MyTable(String tableName) {
this.tableName = tableName;
}
// getters if needed
}
It is a working feature in Spring Data Neo4j, to make use of CompositeProperties
(1) to store arbitrary properties. (Docs: Conversions :: Spring Data Neo4j)
What does not right now is to query those properties with the default derived queries from repository methods, like findAllByWritesToTablesProperties(Map<String, String> ...)
or similar. This is restricted by Cypher right now because we cannot parameterise the property name in the query.
While I thought about an approach to use find-by-example (FAQ :: Spring Data Neo4j), I saw that there is also much more confusion in SDN how to handle this for the given Map
. In the end it will boil down to the restriction on parameterised properties again :(
So, the only working solution I can offer is based on fixed property names, like the connectorType
(2) in combination with a derived query in the repository like this:
public interface MyJobRepository extends Neo4jRepository<MyJob, String> {
List<MyJob> findAllByWritesToTablesConnectorType(String connectorType);
}
Of course you can still go with custom Cypher, either by using Neo4jTemplate
/Neo4jClient
or the @Query
annotation on the repository methods. While the first both will also not be able to have arbitrary property names, the @Query
method will allow you to use Spring Expression Language as a pre-processor for the query. (Custom queries :: Spring Data Neo4j)
public interface MyJobRepository extends Neo4jRepository<MyJob, String> {
@Query("MATCH (j:MyJob)-[r:WRITES_TO]->(t:MyTable) WHERE r.`properties.:#{literal(#property)}` = :#{#value} RETURN j, collect(r), collect(t)")
List<MyJob> customQueryFun(@Param("property") String property, @Param("value") String value);
}
Please note that you have to respect the prefix for the composite property conversion (in this case it defaults to properties
).
Also keep in mind that the loaded data (if there is more connected to table or job) was loaded only with the boundaries defined in the query. Saving such entities back, will remove unknown relationships in the loaded entities.