3

I'm having a problem querying based on an Enum property of my NodeEntity.

The NodeEntity in question is defined:

@NodeEntity(label = "Entity")
public class MyEntity {

    @GraphId
    private Long internalId;

    ....

    private State state;

    @Transient
    public enum State {
        STATEONE, STATETWO, STATETHREE
    }
    ....

It saves without a problem, the state Enum represented perfectly, and I can query using other properties (Strings) with no problem at all. However the problem is the following query in a repository:

@Query("MATCH (entity:Entity {state:{0}})" +
       "RETURN entity")
List<MyEntity> findByState(MyEntity.State state)

i.e. find all entities with the given state.

There's no exception, however using this simply returns a List of 0 Entities.

I've tried all kinds of variations on this, using a WHERE clause for example, with no luck.

The Entities are persisted properly, using findAll() in the same test returns the expected List of Entities with their states exactly as I would expect.

Any thoughts?

R.B.
  • 255
  • 1
  • 4
  • 11
  • 1
    Hi @R.B!Are you sure that the attribute state is correctly saved in neo4j database? I've tried to reproduce your use case and declaring the enum @Transient, attribute state is not persisted. Removing the transient annotation status is presisted correctly and the query is working fine as well. Could you try it? – troig Aug 27 '16 at 09:59
  • 1
    It was definitely being persisted. For me the entities could be saved without problem, and the "state" property could even be modified using a Transaction method. However Transient on the enum definition was indeed causing the problems. Thank you, without it I can query, no problem. – R.B. Aug 28 '16 at 12:22

2 Answers2

5

Not quite sure what the value @Transient adds to the enum. It is anyway not persistable as a node or relationship in Neo4j. It is sufficient to define the field as one that should persist with

private State state;

and leave off the @Transient annotation from the enum. With it, SDN ignores the field sent to the derived query.

However, if you have a good reason to mark the enum @Transient, please do share it and we'll re-visit this case.

Luanne
  • 19,145
  • 1
  • 39
  • 51
  • Thank you, this is the answer. Transient was causing the problems. It was added as a precaution to ensure that SDN didn't try to do anything with the enum. However it makes perfect sense that it behaves in this way without @Transient anyway. Thank you for your guidence. – R.B. Aug 28 '16 at 12:18
2

There is a general problems using spring data rest interface to search on enum fields. Just using the enum-to-string converter cannot work for search where you want to find if the value is IN a collection of values:

public interface AppointmentRepository extends Neo4jRepository<Appointment, Long> {

  Page<Appointment> findByDayOfWeekIn(@Param("days") List<DayOfWeek> days, Pageable pageable);

}

The above does not work out of the box because neo4j will try to convert a List to your property type: DayOfWeek

In order to work around this I needed a custom converter that handles both requests providing collection of values (the search) and single values (the normal read and write entity):

@SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class SearchQueryEnumConverter<T extends Enum> {
    private Class<T> enumType;

    public SearchQueryEnumConverter() {
        enumType = (Class<T>) ((ParameterizedType) this.getClass()).getActualTypeArguments();
    }

    public Object toGraphProperty(Object value) {
        if (Collection.class.isAssignableFrom(value.getClass())) {
            List<T> values = (List<T>) value;
            return values.stream().map(Enum::name).collect(Collectors.toList());
        }
        return ((Enum) value).name();
    }

    public Object toEntityAttribute(Object value) {
        if (Collection.class.isAssignableFrom(value.getClass())) {
            List<String> values = (List<String>) value;
            return values.stream().map(v -> (T) T.valueOf(enumType, v)).collect(Collectors.toList());
        }
        return (T) T.valueOf(enumType, value.toString());
    }

}

The abstract converter can be reified by all enums, and used as parameter of the @Convert annotation:

 public enum EnumType {
     VALUE_A, VALUE_B;

     public static class Converter extends SearchQueryEnumConverter<EnumType> implements AttributeConverter {
     }
 }

 @NodeEntity
 public Entity {
     @Property
     @Convert(EnumType.Converter.class)
     EnumType type;
 }
digital illusion
  • 497
  • 3
  • 19