cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! Site migration is underway. Phase 2: migrate recent content

@RelationshipEntity doesn't map after executing a Query

Ok so I am currently working on a project which is an IMDB replica and I have an User that can review a Movie. User and Movie are 2 nodes and :REVIEWED is a relationship from User to Movie that contains the content of the review.

This is the User class

@Node("USER")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue
    private Long id;
    @AgeAnnotaion
    private Integer age;
    @Email(message = "Wrong email format")
    private String email;
    @NotBlank(message = "The User's name cannot be null or empty")
    private String name;
    @NotBlank(message = "The User's password cannot be null or empty")
    private String password; // TODO: create an annotation that verifies password for more details

    @Relationship(type = "REVIEWED", direction = Relationship.Direction.OUTGOING)
    private Collection<Reviewed> reviewedMovies;
    @Relationship(type = "IN_WATCHLIST", direction = Relationship.Direction.OUTGOING)
    private Collection<Movie> watchlistMovies;

}

This is the Movie class

@Node("MOVIE")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Movie {
    @Id
    @GeneratedValue
    private Long id;
    @NotBlank(message = "Movie's name cannot be null or blank")
    private String name;
    @NotBlank(message = "Movie's genre cannot be null or blank")
    private String genre;
    @ReleaseYearAnnotation
    private Integer releaseYear;

    @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)
    private Director director;

    @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)
    private Collection<Actor> actedInActors;

    @Relationship(type = "IN_WATCHLIST", direction = Relationship.Direction.INCOMING)
    private Collection<User> watchListUsers;
    
    @Relationship(type = "REVIEWED", direction = Relationship.Direction.INCOMING)
    private Reviewed reviewer;
}

And here is the :REVIEWED relationship class

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@RelationshipEntity(type = "REVIEWED")
public class Reviewed {
    @RelationshipId private Long id;
    @StartNode private User user;
    @EndNode private Movie movie;
    @Property  private String content;
}

I also added a ReviewController and ReviewRepository to test it

@RestController
@RequestMapping("/api/review")
public class ReviewController {

    private final ReviewRepository reviewRepository;

    public ReviewController(ReviewRepository reviewRepository) {
        this.reviewRepository = reviewRepository;
    }

    @GetMapping
    public String getReview(@RequestParam("movie") String movieName) {
        System.out.println("Movie name is " + movieName);
        Reviewed reviewed = this.reviewRepository.getReview();
        return reviewed.getContent();
    }
}
@Repository
public interface ReviewRepository extends Neo4jRepository<Reviewed, Long> {

    @Query("MATCH (u:USER)-[r:REVIEWED]->(m:MOVIE) RETURN r")
    Reviewed getReview();
}

I searched everywhere on how to map the relationship and I saw that this is the way to do it. Can someone help me please?
P.S.: The url is http://localhost:8080/api/review?movie=Venom
I also have the following dependecies in my pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.movie</groupId>
    <artifactId>MovieSite</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>MovieSite</name>
    <description>MovieSite</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <!--        VALIDATION          -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!--            LOMBOK              -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <!--              JSON                 -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20211205</version>
        </dependency>
        <!--             NEO4J               -->
        <dependency>
            <groupId>org.neo4j</groupId>
            <artifactId>neo4j-ogm-core</artifactId>
            <version>3.2.31</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.neo4j</groupId>
            <artifactId>neo4j-ogm-bolt-driver</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-neo4j</artifactId>
        </dependency>
        <!--             SPRING WEB             -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--             DEVTOOLS               -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--            SPRING BOOT TEST          -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
1 ACCEPTED SOLUTION

gerrit_meier
Neo4j
Neo4j

There are a few things to consider:
First of all, Spring Data Neo4j starting with version 6 does not support relationships as entities anymore. The only entities are @Nodes and if you need to get properties on your relationships, you will have to use @RelationshipProperties (Spring Data Neo4j).
Loading relationship only is not supported on Neo4jTemplate or repository level but if you want to do the mapping by hand while still being able to use Spring transactions, use Spring Data Neo4j's Neo4jClient. Otherwise you would have to load the Movie or User that/who has the REVIEWED relationship defined.

Next thing is, you are mixing SDN 6 with Neo4j-OGM and also SDN's @Node annotation with OGM's @RelationshipEntity annotation in code.
SDN has no dependency on Neo4j-OGM anymore since version 6 and you really should not include Neo4j-OGM if you are not planning to use also an older (unsupported) version of Spring Data Neo4j. (see Custom generated ID for relationships not supported? · Issue #2497 · spring-projects/spring-data-neo... for the discussion on doing this).

Helping you to get the data, that you want to have:

  1. remove every dependency from the OGM/SDN block except
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
  1. Change Reviewed into something like:
@RelationshipProperties
public class ReviewedMovie {
  @RelationshipId private Long id;
  @TargetNode Movie movie;
  String content;
}
  1. In the User class, use this for the REVIEWED relationship.
  2. If needed on Movie, create the opposite properties class e.g. ReviewedByUser with User as the TargetNode. (Yes, it's kind of a duplication but needed in SDN because we did not want to make our users create cyclic class dependencies for every relationship with properties)
  3. Create a MovieRepository and with the method:
@Query("MATCH (u:USER)-[r:REVIEWED]->(m:MOVIE) WHERE m.title = $title RETURN m, collect(u), collect(r)")
Movie movieWithReview(@Param("title") String title);

(you could remove the collect functions if you really only have one review per movie)
6. Return movie.reviewer.getContent()
7. smile

View solution in original post

2 REPLIES 2

gerrit_meier
Neo4j
Neo4j

There are a few things to consider:
First of all, Spring Data Neo4j starting with version 6 does not support relationships as entities anymore. The only entities are @Nodes and if you need to get properties on your relationships, you will have to use @RelationshipProperties (Spring Data Neo4j).
Loading relationship only is not supported on Neo4jTemplate or repository level but if you want to do the mapping by hand while still being able to use Spring transactions, use Spring Data Neo4j's Neo4jClient. Otherwise you would have to load the Movie or User that/who has the REVIEWED relationship defined.

Next thing is, you are mixing SDN 6 with Neo4j-OGM and also SDN's @Node annotation with OGM's @RelationshipEntity annotation in code.
SDN has no dependency on Neo4j-OGM anymore since version 6 and you really should not include Neo4j-OGM if you are not planning to use also an older (unsupported) version of Spring Data Neo4j. (see Custom generated ID for relationships not supported? · Issue #2497 · spring-projects/spring-data-neo... for the discussion on doing this).

Helping you to get the data, that you want to have:

  1. remove every dependency from the OGM/SDN block except
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
  1. Change Reviewed into something like:
@RelationshipProperties
public class ReviewedMovie {
  @RelationshipId private Long id;
  @TargetNode Movie movie;
  String content;
}
  1. In the User class, use this for the REVIEWED relationship.
  2. If needed on Movie, create the opposite properties class e.g. ReviewedByUser with User as the TargetNode. (Yes, it's kind of a duplication but needed in SDN because we did not want to make our users create cyclic class dependencies for every relationship with properties)
  3. Create a MovieRepository and with the method:
@Query("MATCH (u:USER)-[r:REVIEWED]->(m:MOVIE) WHERE m.title = $title RETURN m, collect(u), collect(r)")
Movie movieWithReview(@Param("title") String title);

(you could remove the collect functions if you really only have one review per movie)
6. Return movie.reviewer.getContent()
7. smile

It solved my problem. Thank you so much for your time

Nodes 2022
Nodes
NODES 2022, Neo4j Online Education Summit

All the sessions of the conference are now available online