Neo4j 演员-电影关系系统 (SpringBoot 实现)
下面是一个完整的 SpringBoot 项目,使用 Neo4j 数据库实现演员演电影的关系模型,采用三层架构,使用 MyBatis 进行 Neo4j 的查询操作(不使用 Spring Data Neo4j 的 ORM 风格)。
项目结构
css
src/main/java/com/example/neo4jdemo/
├── config
│ └── Neo4jConfig.java
├── controller
│ ├── ActorController.java
│ └── MovieController.java
├── entity
│ ├── Actor.java
│ ├── Movie.java
│ └── Relationship.java
├── mapper
│ ├── ActorMapper.java
│ ├── MovieMapper.java
│ └── RelationshipMapper.java
├── service
│ ├── impl
│ │ ├── ActorServiceImpl.java
│ │ └── MovieServiceImpl.java
│ ├── ActorService.java
│ └── MovieService.java
└── Neo4jDemoApplication.java
1. 添加依赖 (pom.xml)
xml
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Neo4j JDBC Driver -->
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>4.4.5</version>
</dependency>
<!-- For testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置类 (Neo4jConfig.java)
java
package com.example.neo4jdemo.config;
import org.neo4j.driver.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Neo4jConfig {
@Value("${spring.neo4j.uri}")
private String uri;
@Value("${spring.neo4j.authentication.username}")
private String username;
@Value("${spring.neo4j.authentication.password}")
private String password;
@Bean
public Driver neo4jDriver() {
return GraphDatabase.driver(uri, AuthTokens.basic(username, password));
}
@Bean
public Session neo4jSession(Driver driver) {
return driver.session();
}
}
3. 实体类
Actor.java
java
package com.example.neo4jdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Actor {
private Long id;
private String name;
private Integer born;
}
Movie.java
java
package com.example.neo4jdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Movie {
private Long id;
private String title;
private Integer released;
private String tagline;
private List<Actor> actors;
}
Relationship.java
java
package com.example.neo4jdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Relationship {
private Long actorId;
private Long movieId;
private List<String> roles;
}
4. Mapper 接口
ActorMapper.java
java
package com.example.neo4jdemo.mapper;
import com.example.neo4jdemo.entity.Actor;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ActorMapper {
void createActor(Actor actor);
Actor findActorById(Long id);
List<Actor> findAllActors();
void deleteActor(Long id);
List<Actor> findActorsByMovieId(Long movieId);
}
MovieMapper.java
java
package com.example.neo4jdemo.mapper;
import com.example.neo4jdemo.entity.Movie;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MovieMapper {
void createMovie(Movie movie);
Movie findMovieById(Long id);
List<Movie> findAllMovies();
void deleteMovie(Long id);
List<Movie> findMoviesByActorId(Long actorId);
}
RelationshipMapper.java
java
package com.example.neo4jdemo.mapper;
import com.example.neo4jdemo.entity.Relationship;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RelationshipMapper {
void createRelationship(Relationship relationship);
void deleteRelationshipsByActorId(Long actorId);
void deleteRelationshipsByMovieId(Long movieId);
}
5. Mapper XML 文件 (resources/mapper/)
ActorMapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.neo4jdemo.mapper.ActorMapper">
<insert id="createActor">
CREATE (a:Actor {id: #{id}, name: #{name}, born: #{born}})
</insert>
<select id="findActorById" resultType="com.example.neo4jdemo.entity.Actor">
MATCH (a:Actor {id: #{id}}) RETURN a.id as id, a.name as name, a.born as born
</select>
<select id="findAllActors" resultType="com.example.neo4jdemo.entity.Actor">
MATCH (a:Actor) RETURN a.id as id, a.name as name, a.born as born
</select>
<delete id="deleteActor">
MATCH (a:Actor {id: #{id}}) DELETE a
</delete>
<select id="findActorsByMovieId" resultType="com.example.neo4jdemo.entity.Actor">
MATCH (m:Movie {id: #{movieId}})<-[:ACTED_IN]-(a:Actor)
RETURN a.id as id, a.name as name, a.born as born
</select>
</mapper>
MovieMapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.neo4jdemo.mapper.MovieMapper">
<insert id="createMovie">
CREATE (m:Movie {id: #{id}, title: #{title}, released: #{released}, tagline: #{tagline}})
</insert>
<select id="findMovieById" resultType="com.example.neo4jdemo.entity.Movie">
MATCH (m:Movie {id: #{id}})
OPTIONAL MATCH (m)<-[r:ACTED_IN]-(a:Actor)
RETURN m.id as id, m.title as title, m.released as released, m.tagline as tagline,
collect({id: a.id, name: a.name, born: a.born}) as actors
</select>
<select id="findAllMovies" resultType="com.example.neo4jdemo.entity.Movie">
MATCH (m:Movie)
OPTIONAL MATCH (m)<-[r:ACTED_IN]-(a:Actor)
RETURN m.id as id, m.title as title, m.released as released, m.tagline as tagline,
collect({id: a.id, name: a.name, born: a.born}) as actors
</select>
<delete id="deleteMovie">
MATCH (m:Movie {id: #{id}}) DELETE m
</delete>
<select id="findMoviesByActorId" resultType="com.example.neo4jdemo.entity.Movie">
MATCH (a:Actor {id: #{actorId}})-[:ACTED_IN]->(m:Movie)
RETURN m.id as id, m.title as title, m.released as released, m.tagline as tagline
</select>
</mapper>
RelationshipMapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.neo4jdemo.mapper.RelationshipMapper">
<insert id="createRelationship">
MATCH (a:Actor {id: #{actorId}}), (m:Movie {id: #{movieId}})
CREATE (a)-[r:ACTED_IN {roles: #{roles}}]->(m)
</insert>
<delete id="deleteRelationshipsByActorId">
MATCH (a:Actor {id: #{actorId}})-[r]->() DELETE r
</delete>
<delete id="deleteRelationshipsByMovieId">
MATCH ()-[r]->(m:Movie {id: #{movieId}}) DELETE r
</delete>
</mapper>
6. Service 层
ActorService.java
java
package com.example.neo4jdemo.service;
import com.example.neo4jdemo.entity.Actor;
import java.util.List;
public interface ActorService {
Actor createActor(Actor actor);
Actor getActorById(Long id);
List<Actor> getAllActors();
void deleteActor(Long id);
List<Actor> getActorsByMovieId(Long movieId);
}
ActorServiceImpl.java
java
package com.example.neo4jdemo.service.impl;
import com.example.neo4jdemo.entity.Actor;
import com.example.neo4jdemo.mapper.ActorMapper;
import com.example.neo4jdemo.mapper.RelationshipMapper;
import com.example.neo4jdemo.service.ActorService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ActorServiceImpl implements ActorService {
private final ActorMapper actorMapper;
private final RelationshipMapper relationshipMapper;
@Override
public Actor createActor(Actor actor) {
actorMapper.createActor(actor);
return actor;
}
@Override
public Actor getActorById(Long id) {
return actorMapper.findActorById(id);
}
@Override
public List<Actor> getAllActors() {
return actorMapper.findAllActors();
}
@Override
@Transactional
public void deleteActor(Long id) {
relationshipMapper.deleteRelationshipsByActorId(id);
actorMapper.deleteActor(id);
}
@Override
public List<Actor> getActorsByMovieId(Long movieId) {
return actorMapper.findActorsByMovieId(movieId);
}
}
MovieService.java
java
package com.example.neo4jdemo.service;
import com.example.neo4jdemo.entity.Movie;
import java.util.List;
public interface MovieService {
Movie createMovie(Movie movie);
Movie getMovieById(Long id);
List<Movie> getAllMovies();
void deleteMovie(Long id);
List<Movie> getMoviesByActorId(Long actorId);
void addActorToMovie(Long actorId, Long movieId, List<String> roles);
}
MovieServiceImpl.java
java
package com.example.neo4jdemo.service.impl;
import com.example.neo4jdemo.entity.Movie;
import com.example.neo4jdemo.entity.Relationship;
import com.example.neo4jdemo.mapper.MovieMapper;
import com.example.neo4jdemo.mapper.RelationshipMapper;
import com.example.neo4jdemo.service.MovieService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieMapper movieMapper;
private final RelationshipMapper relationshipMapper;
@Override
public Movie createMovie(Movie movie) {
movieMapper.createMovie(movie);
return movie;
}
@Override
public Movie getMovieById(Long id) {
return movieMapper.findMovieById(id);
}
@Override
public List<Movie> getAllMovies() {
return movieMapper.findAllMovies();
}
@Override
@Transactional
public void deleteMovie(Long id) {
relationshipMapper.deleteRelationshipsByMovieId(id);
movieMapper.deleteMovie(id);
}
@Override
public List<Movie> getMoviesByActorId(Long actorId) {
return movieMapper.findMoviesByActorId(actorId);
}
@Override
@Transactional
public void addActorToMovie(Long actorId, Long movieId, List<String> roles) {
Relationship relationship = new Relationship(actorId, movieId, roles);
relationshipMapper.createRelationship(relationship);
}
}
7. Controller 层
ActorController.java
java
package com.example.neo4jdemo.controller;
import com.example.neo4jdemo.entity.Actor;
import com.example.neo4jdemo.service.ActorService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/actors")
@RequiredArgsConstructor
public class ActorController {
private final ActorService actorService;
@PostMapping
public ResponseEntity<Actor> createActor(@RequestBody Actor actor) {
return ResponseEntity.ok(actorService.createActor(actor));
}
@GetMapping("/{id}")
public ResponseEntity<Actor> getActorById(@PathVariable Long id) {
return ResponseEntity.ok(actorService.getActorById(id));
}
@GetMapping
public ResponseEntity<List<Actor>> getAllActors() {
return ResponseEntity.ok(actorService.getAllActors());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteActor(@PathVariable Long id) {
actorService.deleteActor(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/by-movie/{movieId}")
public ResponseEntity<List<Actor>> getActorsByMovieId(@PathVariable Long movieId) {
return ResponseEntity.ok(actorService.getActorsByMovieId(movieId));
}
}
MovieController.java
java
package com.example.neo4jdemo.controller;
import com.example.neo4jdemo.entity.Movie;
import com.example.neo4jdemo.service.MovieService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/movies")
@RequiredArgsConstructor
public class MovieController {
private final MovieService movieService;
@PostMapping
public ResponseEntity<Movie> createMovie(@RequestBody Movie movie) {
return ResponseEntity.ok(movieService.createMovie(movie));
}
@GetMapping("/{id}")
public ResponseEntity<Movie> getMovieById(@PathVariable Long id) {
return ResponseEntity.ok(movieService.getMovieById(id));
}
@GetMapping
public ResponseEntity<List<Movie>> getAllMovies() {
return ResponseEntity.ok(movieService.getAllMovies());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteMovie(@PathVariable Long id) {
movieService.deleteMovie(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/by-actor/{actorId}")
public ResponseEntity<List<Movie>> getMoviesByActorId(@PathVariable Long actorId) {
return ResponseEntity.ok(movieService.getMoviesByActorId(actorId));
}
@PostMapping("/{movieId}/actors/{actorId}")
public ResponseEntity<Void> addActorToMovie(
@PathVariable Long movieId,
@PathVariable Long actorId,
@RequestBody List<String> roles) {
movieService.addActorToMovie(actorId, movieId, roles);
return ResponseEntity.noContent().build();
}
}
8. 主应用类
java
package com.example.neo4jdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.neo4jdemo.mapper")
public class Neo4jDemoApplication {
public static void main(String[] args) {
SpringApplication.run(Neo4jDemoApplication.class, args);
}
}
9. 配置文件 (application.yml)
yaml
spring:
neo4j:
uri: bolt://localhost:7687
authentication:
username: neo4j
password: your_password
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
10. 初始化数据 (可选)
你可以创建一个 CommandLineRunner bean 来初始化一些测试数据:
java
package com.example.neo4jdemo;
import com.example.neo4jdemo.entity.Actor;
import com.example.neo4jdemo.entity.Movie;
import com.example.neo4jdemo.service.ActorService;
import com.example.neo4jdemo.service.MovieService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {
private final ActorService actorService;
private final MovieService movieService;
@Override
public void run(String... args) throws Exception {
// 初始化演员
Actor tomHanks = new Actor(1L, "Tom Hanks", 1956);
Actor keanuReeves = new Actor(2L, "Keanu Reeves", 1964);
Actor laurenceFishburne = new Actor(3L, "Laurence Fishburne", 1961);
actorService.createActor(tomHanks);
actorService.createActor(keanuReeves);
actorService.createActor(laurenceFishburne);
// 初始化电影
Movie theMatrix = new Movie(1L, "The Matrix", 1999, "Welcome to the Real World");
Movie forrestGump = new Movie(2L, "Forrest Gump", 1994, "Life is like a box of chocolates");
movieService.createMovie(theMatrix);
movieService.createMovie(forrestGump);
// 建立关系
movieService.addActorToMovie(keanuReeves.getId(), theMatrix.getId(), List.of("Neo"));
movieService.addActorToMovie(laurenceFishburne.getId(), theMatrix.getId(), List.of("Morpheus"));
movieService.addActorToMovie(tomHanks.getId(), forrestGump.getId(), List.of("Forrest Gump"));
}
}
使用说明
- 确保已安装 Neo4j 数据库并运行
- 更新 application.yml 中的 Neo4j 连接信息
- 启动应用程序
- 可以使用以下 API 端点:
演员相关:
- POST /api/actors - 创建演员
- GET /api/actors/{id} - 获取指定ID的演员
- GET /api/actors - 获取所有演员
- DELETE /api/actors/{id} - 删除指定ID的演员
- GET /api/actors/by-movie/{movieId} - 获取参演指定电影的演员
电影相关:
- POST /api/movies - 创建电影
- GET /api/movies/{id} - 获取指定ID的电影
- GET /api/movies - 获取所有电影
- DELETE /api/movies/{id} - 删除指定ID的电影
- GET /api/movies/by-actor/{actorId} - 获取指定演员参演的电影
- POST /api/movies/{movieId}/actors/{actorId} - 添加演员到电影中
这个实现方案:
- 使用 SpringBoot + MyBatis + Lombok
- 完整的三层架构 (Controller-Service-DAO)
- 使用 XML 查询语句
- 不使用 Spring Data Neo4j 的 ORM 风格
- 包含 service 的 impl 实现