neo4j / neo4j-ogm

Java Object-Graph Mapping Library for Neo4j
https://neo4j.com/docs/ogm-manual/
Apache License 2.0
333 stars 165 forks source link

SemanticError: Cannot merge node using null property value for key. #789

Closed gogolevao closed 4 years ago

gogolevao commented 4 years ago

Info

Neo4j version: 4.0.3. Enterprise Edition Java: spring-data-neo4j Driver: bolt-driver v3.2.9

Hello! I want to create node entity with a composite id, but I have an exception after save entity into repository. I hope for your answer

Steps to reproduce

  1. Create a node entity, id, CompositeAttributeConverter and repository:

ENTITY:

@NodeEntity
public class Entity {

    @Id
    @Index(unique = true)
    @Convert(KeyConverter.class)
    private Key key;
    private String some;

    public static Entity from(final String first, final String second) {
        return new Entity(Key.build(first, second));
    }

}

KEY:

public class Key implements Serializable {
    private String first;
    private String second;

    public static Key build(final String first, final String second) {
        return new Key(first, second);
    }
}

CONVERTER:

public class KeyConverter implements CompositeAttributeConverter<<Key>> {

    @Override
    public Map<String, String> toGraphProperties(Key value) {
        Map<String, String> properties = new HashMap<>();
        properties.put("first", value.getFirst());
        properties.put("second", value.getSecond());
        return properties;
    }

    @Override
    public Key toEntityAttribute(Map<String, ?> value) {
        String first = (String) value.get("first");
        String second = (String) value.get("second"));
        return new Key(first, second);
    }

REPOSITORY:

public interface EntityRepository extends Neo4jRepository<Entity, Key> {
}
  1. Save to repository: entityRepository.save(Entity.from("first", "second"));

Expected behavior

Entity was saved. Because first and second are not null and key was created.

Actual behavior

org.neo4j.ogm.exception.CypherException: Cypher execution failed with code 'Neo.ClientError.Statement.SemanticError': Cannot merge node using null property value for key. at org.neo4j.ogm.drivers.bolt.request.BoltRequest.execute(BoltRequest.java:110) at org.neo4j.ogm.session.request.RequestExecutor.executeStatements(RequestExecutor.java:131) at org.neo4j.ogm.session.request.RequestExecutor.lambda$executeSave$2(RequestExecutor.java:87) at org.neo4j.ogm.session.Neo4jSession.lambda$doInTransaction$1(Neo4jSession.java:558) at org.neo4j.ogm.session.Neo4jSession.doInTransaction(Neo4jSession.java:590) at org.neo4j.ogm.session.Neo4jSession.doInTransaction(Neo4jSession.java:557) at org.neo4j.ogm.session.request.RequestExecutor.executeSave(RequestExecutor.java:80) at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:90) at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:51) at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:480) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:567) at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)

michael-simons commented 4 years ago

Thanks for reporting this, @gogolevao.

michael-simons commented 4 years ago

Again, thanks for reporting this. This is now implemented and will be released with the next patch.

Please take note that you need to define a @CompositeIndex in your domain to make things work:

import org.neo4j.ogm.annotation.CompositeIndex;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.typeconversion.Convert;

/**
 * @author Michael J. Simons
 */
@NodeEntity
@CompositeIndex(properties = { "key.first", "key.second" }, unique = true)
public class Entity {

    @Id
    @Convert(KeyConverter.class)
    private Key key;
    private String some;

    public static Entity from(final String first, final String second) {
        return new Entity(Key.build(first, second));
    }

    private Entity(Key key) {
        this.key = key;
    }

    public Entity() {
    }

    public Key getKey() {
        return key;
    }

    public String getSome() {
        return some;
    }

    public void setSome(String some) {
        this.some = some;
    }
}

In that index, you have to prefix the properties with the name of the field (in your case, key).

gogolevao commented 4 years ago

Thanks! 😊