Search code examples
springneo4jspring-data-neo4jembeddable

How to define in spring model to embed an object using Neo4j


I would to store the following structure in a Neo4J database which has the embedded object oweMe and soldFor are of class Payment.

{
  "id":"id23",
  "oweMe": {
    "cost": 23
    "currency":"USD"
  }
  "soldFor": {
    "cost": 34
    "currency":"EUR"
  }
}

In spring boot, I can use the @embedded and @Embeddable annotations with the JPArespository on an H2 database and it works perfectly.

I want to repeat this structure but store it in a Neo4J database. As the Neo4j does not support the JPARepositry I cannot use those annotations however I see that it uses the @NodeEntity and @Property/@Properties to achieve the same result according to the reference material.

The reference documentation shows an example that looks like the following where the address is prefixed to the embedded object properties.

address.street=Downing Street
address.number=10

Thanks everyone for your help,

However when I run the app, I get an error saying "Required identifier property not found for class ... Payment" and refers to the embedded object. While that is true that there is no @Id for the embedded object, as the embedded object is not a List<> and there is no @Node/@NodeEntity definition, I expected that should not be needing an id.

While I can overcome this by writing custom getters and setters to serialise the embedded object so that it can be stored as a JSON string, that has become overwhelming.

As I expect that this sort of embedding is done all the time, can someone please point me to an example that I can use?


Solution

  • Neo4j does not support embedded objects. This is why there is no other way than using @CompositeProperty with a custom converter to "embed" your objects.

    For example for soldFor with a property defined as:

    @CompositeProperty(prefix="soldFor", converter = SoldForConverter.class)
    private SoldFor soldFor;
    

    and a converter like

    class SoldForConverter implements Neo4jPersistentPropertyToMapConverter<String, SoldFor> {
    
        @NonNull @Override
        public Map<String, Value> decompose(@Nullable SoldFor property, Neo4jConversionService conversionService) {
    
            final HashMap<String, Value> decomposed = new HashMap<>();
            if (property == null) {
                decomposed.put("cost", Values.NULL);
                decomposed.put("currency", Values.NULL);
            } else {
                decomposed.put("cost", Values.value(property.getCost()));
                decomposed.put("currency", Values.value(property.getCurrency()));
            }
            return decomposed;
        }
    
        @Override
        public SoldFor compose(Map<String, Value> source, Neo4jConversionService conversionService) {
            return source.isEmpty() ?
                    null :
                    new SoldFor(source.get("cost").asInt(), source.get("currency").asString());
        }
    }
    

    You will get

    soldFor.cost=34
    soldFor.currency="EUR"
    

    Example taking from

    Converter: https://github.com/spring-projects/spring-data-neo4j/blob/c5d3458217124e1867f12bc47454eab85551c72b/src/test/java/org/springframework/data/neo4j/integration/shared/conversion/ThingWithCompositeProperties.java#L235

    Property definition: https://github.com/spring-projects/spring-data-neo4j/blob/c5d3458217124e1867f12bc47454eab85551c72b/src/test/java/org/springframework/data/neo4j/integration/shared/conversion/RelationshipWithCompositeProperties.java#L44 and simplified.