使用 spring-data-neo4j 时出现 MappingException

MappingException when using spring-data-neo4j

最近用spring-data-neo4j写了一个电影推荐小demo。 但我坚持使用奇怪的 MappingException。 异常堆栈跟踪如下。

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:675)
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:690)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
    at com.liudonghua.apps.neo4j_data_importer.Application.main(Application.java:202)
Caused by: org.springframework.data.mapping.model.MappingException: Setting property user to com.liudonghua.apps.neo4j_data_importer.domain.Movie@66286c8 on com.liudonghua.apps.neo4j_data_importer.domain.Rating@618519cf
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.setProperty(SourceStateTransmitter.java:85)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyEntityStatePropertyValue(SourceStateTransmitter.java:91)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.access[=11=]0(SourceStateTransmitter.java:40)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.doWithAssociation(SourceStateTransmitter.java:61)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesFrom(SourceStateTransmitter.java:57)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.loadEntity(Neo4jEntityConverterImpl.java:112)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.read(Neo4jEntityConverterImpl.java:104)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.read(Neo4jEntityPersister.java:170)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.createEntityFromState(Neo4jEntityPersister.java:192)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:250)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:231)
    at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:357)
    at org.springframework.data.neo4j.fieldaccess.RelatedToViaCollectionFieldAccessorFactory$RelatedToViaCollectionFieldAccessor.persistEntities(RelatedToViaCollectionFieldAccessorFactory.java:99)
    at org.springframework.data.neo4j.fieldaccess.RelatedToViaCollectionFieldAccessorFactory$RelatedToViaCollectionFieldAccessor.setValue(RelatedToViaCollectionFieldAccessorFactory.java:93)
    at org.springframework.data.neo4j.fieldaccess.DefaultEntityState.setValue(DefaultEntityState.java:113)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.setEntityStateValue(SourceStateTransmitter.java:70)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.access0(SourceStateTransmitter.java:40)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.doWithAssociation(SourceStateTransmitter.java:113)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesTo(SourceStateTransmitter.java:109)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.write(Neo4jEntityConverterImpl.java:167)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.write(Neo4jEntityPersister.java:179)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:243)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:231)
    at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:357)
    at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:351)
    at org.springframework.data.neo4j.repository.AbstractGraphRepository.save(AbstractGraphRepository.java:91)
    at org.springframework.data.neo4j.repository.AbstractGraphRepository.save(AbstractGraphRepository.java:98)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:416)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:401)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:373)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(RepositoryFactorySupport.java:486)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    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.$Proxy52.save(Unknown Source)
    at com.liudonghua.apps.neo4j_data_importer.Application.run(Application.java:109)
    at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:672)
    ... 5 common frames omitted
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type com.liudonghua.apps.neo4j_data_importer.domain.Movie to type com.liudonghua.apps.neo4j_data_importer.domain.User
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:311)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:173)
    at org.springframework.data.mapping.model.BeanWrapper.getPotentiallyConvertedValue(BeanWrapper.java:157)
    at org.springframework.data.mapping.model.BeanWrapper.setProperty(BeanWrapper.java:75)
    at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.setProperty(SourceStateTransmitter.java:83)
    ... 53 common frames omitted

下面是一些相关的代码。

@NodeEntity
public class Movie {

    @GraphId
    Long nodeId;

    @Indexed
    private long id;

    @Indexed
    private String title;
    private Date releaseDate;
    private String imdbUrl;

    @RelatedTo(type="HAS_GENRE")
    @Fetch
    Set<Genre> genres;

    @RelatedToVia(type="RATED" ,direction=Direction.INCOMING)
    @Fetch
    Set<Rating> ratings;

    public Movie() {
    }

    public Movie(long id, String title, Date releaseDate, String imdbUrl,
            Set<Genre> genres) {
        this.id = id;
        this.title = title;
        this.releaseDate = releaseDate;
        this.imdbUrl = imdbUrl;
        this.genres = genres;
    }

    // getters and setters are omitted here.
}

@RelationshipEntity(type = "RATED")
public class Rating {

    @GraphId
    Long relationId;

    @Indexed
    private long id;

    private int rate;

    @StartNode
    private User user;

    @EndNode
    private Movie movie;

    public Rating() {
    }

    public Rating(User user, Movie movie, int rate) {
        this.user = user;
        this.movie = movie;
        this.rate = rate;
    }

    // getters and setters are omitted here.
}

@NodeEntity
public class User {

    @GraphId
    Long nodeId;

    @Indexed
    private long id;
    private int age;
    private String gender;
    private String occupation;
    private String zipCode;

    // 
    @RelatedTo(type="RATED")
    @Fetch
    private Set<Movie> movies;

    @RelatedToVia(type="RATED")
    @Fetch
    private Set<Rating> ratings;


    public User() {
    }

    public User(long id, int age, String gender, String occupation,
            String zipCode) {
        this.id = id;
        this.age = age;
        this.gender = gender;
        this.occupation = occupation;
        this.zipCode = zipCode;
    }

    public Rating rate(Movie movie, int rate) {
        Rating rating = new Rating(this, movie, rate);
        ratings.add(rating);
        return rating;
    }

    // getters and setters are omitted here.
}

public interface UserRepository extends GraphRepository<User> {

}

Movie movie = movieRepository.save(new Movie(...));
User user = userRepository.save(new User(...));
...
user.rate(movie, 3);
userRepository.save(user);  // This line throws exception

下面是我的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.liudonghua.apps</groupId>
 <artifactId>neo4j-data-importer</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>neo4j-data-importer</name>
 <url>http://maven.apache.org</url>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <jdk.version>1.8</jdk.version>
 </properties>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.2.2.RELEASE</version>
 </parent>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-tx</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-neo4j</artifactId>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>${jdk.version}</source>
     <target>${jdk.version}</target>
    </configuration>
   </plugin>
  </plugins>

 </build>

 <repositories>
  <repository>
   <id>spring-releases</id>
   <name>Spring Releases</name>
   <url>http://repo.spring.io/libs-release</url>
  </repository>
  <repository>
   <id>neo4j</id>
   <name>Neo4j</name>
   <url>http://m2.neo4j.org/</url>
  </repository>
 </repositories>

 <pluginRepositories>
  <pluginRepository>
   <id>spring-releases</id>
   <name>Spring Releases</name>
   <url>http://repo.spring.io/libs-release</url>
  </pluginRepository>
 </pluginRepositories>
</project>

最后,我通过覆盖正确的 equals、hashCode 方法并提供完整的 getter 和 setter.

解决了这个问题