Springboot 整合 Neo4j

1,606 阅读6分钟

一、搭建 Neo4j 数据库服务

方式一:Docker 启动 Neo4j 服务

# # docker 启动服务命令(账号:neo4j、密码:secret)
docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:4.4.8

方式二:Podman 启动 Neo4j 服务

# podman 部署启动服务命令(账号:neo4j、密码:secret)
podman run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' docker.io/library/neo4j:4.4.8

# 操作示例:带用户名/密码启动的服务实例
[root@localhost ~]# podman run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' docker.io/library/neo4j:4.4.8
Trying to pull docker.io/library/neo4j:latest...
Getting image source signatures
Copying blob 75f2c7b97846 done
Copying blob 461246efe0a7 done
Copying blob d294696aee8f done
Copying blob 53a09311990d done
Copying blob e092b02b7004 done
Copying blob e693ef00b582 done
Copying blob feb9a9dd0fc3 done
Copying config 7e0519e020 done
Writing manifest to image destination
Storing signatures
Changed password for user 'neo4j'. IMPORTANT: this change will only take effect if performed before the database is started for the first time.
2022-07-14 02:53:23.724+0000 INFO  Starting...
2022-07-14 02:53:24.752+0000 INFO  This instance is ServerId{4613b08a} (4613b08a-5229-423b-8052-6597516949cd)
2022-07-14 02:53:27.283+0000 INFO  ======== Neo4j 4.4.8 ========
2022-07-14 02:53:31.058+0000 INFO  Initializing system graph model for component 'security-users' with version -1 and status UNINITIALIZED
2022-07-14 02:53:31.070+0000 INFO  Setting up initial user from `auth.ini` file: neo4j
2022-07-14 02:53:31.070+0000 INFO  Creating new user 'neo4j' (passwordChangeRequired=false, suspended=false)
2022-07-14 02:53:31.089+0000 INFO  Setting version for 'security-users' to 3
2022-07-14 02:53:31.095+0000 INFO  After initialization of system graph model component 'security-users' have version 3 and status CURRENT
2022-07-14 02:53:31.101+0000 INFO  Performing postInitialization step for component 'security-users' with version 3 and status CURRENT
2022-07-14 02:53:31.492+0000 INFO  Bolt enabled on [0:0:0:0:0:0:0:0%0]:7687.
2022-07-14 02:53:33.550+0000 INFO  Remote interface available at http://localhost:7474/
2022-07-14 02:53:33.562+0000 INFO  id: C592E2927232647D56DCD80C5DA2DFF71D811376198C180AF9026EA42E2D1C7B
2022-07-14 02:53:33.562+0000 INFO  name: system
2022-07-14 02:53:33.562+0000 INFO  creationDate: 2022-07-14T02:53:28.416Z
2022-07-14 02:53:33.563+0000 INFO  Started.

1. 访问 Neo4j GUI 首页

image.png

相关说明:

  1. ConnectURL:数据库连接地址;
  2. Database-leave empty for default:数据库;
  3. Authentication type:鉴权方式(有三种:① 账号/密码登录;②无密码登录;③单点登录SSO)
  4. ⚠注意:由于我们通过 docker/podman 的方式启动时指定了 neo4j 的账号/密码(neo4j/secret),所以此处必须用第①种方式登录到 neo4j;

2. 登陆首页

image.png

二、SpringBoot 整合 Neo4j

(1)引入依赖

① Maven方式

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>

② gradle 方式

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
}

(2) 配置application.properties

# This is the bare minimum of what you need to connect to a Neo4j instance.
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

(3)编写实体和实体关系类

注意:

  1. @Node :定义节点
  2. @Relationship:定义关系
  3. @Propert:定义节点或关系属性
  4. @RelationshipProperties:定义关系属性集合
  5. @Id:定义SDN的主键
  6. @TargetNode:定义关系的另一端节点对象

① 实体类:电影信息

import edu.study.module.springbootneo4j.relation.Roles;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import org.springframework.data.neo4j.core.schema.Relationship;

import java.util.List;

/**
 * 电影实体类
 */
@Data
@NoArgsConstructor
@Node("Movie")
public class MovieEntity {
   @Id
   private String title;

   @Property("description")
   private String description;

   @Relationship(type = "ACTED_IN", direction = Relationship.Direction.OUTGOING)
   private List<Roles> roles;

   @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)
   private List<PersonEntity> directors;

   public MovieEntity(String title, String description) {
      this.title = title;
      this.description = description;
   }
}

② 实体类:人员信息

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;

@Data
@NoArgsConstructor
@Node("Person")
public class PersonEntity {
   @Id
   private String name;

   @Property("born")
   private Integer born;

   public PersonEntity(Integer born, String name) {
      this.born = born;
      this.name = name;
   }
}

③ 关系类:电影和人员关系

import edu.study.module.springbootneo4j.model.PersonEntity;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.neo4j.core.schema.*;

import java.util.List;

@Data
@NoArgsConstructor
@RelationshipProperties
public class Roles {
   @Id
   @GeneratedValue(GeneratedValue.InternalIdGenerator.class)
   private Long id;

   @Property("roles")
   private List<String> roleList;

   @TargetNode
   private PersonEntity person;


   public Roles(Long id, PersonEntity person, List<String> roleList) {
      this.id = id;
      this.person = person;
      this.roleList = roleList;
   }
}

(4)编写DAO层

注意:repository 优点类似 jpa,就是继承的类不同。

import edu.study.module.springbootneo4j.model.MovieEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, String> {

   /**
    * 自定义查询方式:根据电影名称查询
    *
    * @param title 电影名称
    * @return 电影集合
    */
//    @Query("MATCH (n:Movie) where n.title contains $title RETURN n.title,n.description LIMIT 1")
//    @Query("MATCH (n:Movie) where n.title contains $title RETURN n LIMIT 1")
   @Query("MATCH (n:Movie) where n.title contains $title RETURN n")
   List<MovieEntity> findByTitle(@Param("title") String title);

   /**
    * 注意:统计所有的数据库neo4j下的所有的系欸但数据
    * @return 数量
    */
   @Query("MATCH (n) RETURN count(n)")
   Long countAllNodes();
}

(5)编写业务层

① 电影业务接口

import edu.study.module.springbootneo4j.model.MovieEntity;

import java.util.List;

public interface MovieService {
   /**
    * 店家电影名称
    *
    * @param movie 电影信息
    */
   MovieEntity add(MovieEntity movie);

   /**
    * 获取所有的电影信息
    *
    * @return 电影即可
    */
   List<MovieEntity> findAll();

   /**
    * 自定义查询:根据电影标题模糊查询电影
    *
    * @return 电影集合
    */
   List<MovieEntity> findByTitle(String movieTitle);

   /**
    * 根据电影名称查询
    *
    * @param movieTitle 电影名称
    * @return 电影信息
    */
   MovieEntity findMovieByTitle(String movieTitle);

   MovieEntity testNeo4jTemplate(String movieTitle);

   /**
    * 统计电影数量
    *
    * @return 电影数量
    */
   Long count();

   /**
    * 统计neo4j下,所有节点数量
    *
    * @return 节点数量
    */
   Long countAllNodes();

   /**
    * 删除所有的电影信息
    */
   void deleteAll(MovieEntity movie);
}

② 电影业务实现接口

import edu.study.module.springbootneo4j.dao.MovieRepository;
import edu.study.module.springbootneo4j.model.MovieEntity;
import edu.study.module.springbootneo4j.service.MovieService;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

@Service("MovieService")
public class MovieServiceImpl implements MovieService {
   // 【核心】操作类
   @Resource
   private MovieRepository movieRepository;
   // 【核心】操作类
   @Resource
   private Neo4jTemplate neo4jTemplate;


   @Override
   public MovieEntity add(MovieEntity movie) {
      MovieEntity newEntity = movieRepository.save(movie);
      System.out.println("新的电影信息:" + newEntity);
      return newEntity;
   }

   @Override
   public List<MovieEntity> findAll() {
      return movieRepository.findAll();
   }

   @Override
   public List<MovieEntity> findByTitle(String movieTitle) {
      return movieRepository.findByTitle(movieTitle);
   }


   @Override
   public MovieEntity findMovieByTitle(String movieTitle) {
      return movieRepository.findById(movieTitle).orElse(null);
   }

   @Override
   public MovieEntity testNeo4jTemplate(String movieTitle) {
      return neo4jTemplate.findById(movieTitle, MovieEntity.class).orElse(null);
   }

   @Override
   public Long count() {
      return movieRepository.count();
   }

   @Override
   public Long countAllNodes() {
      return movieRepository.countAllNodes();
   }

   @Override
   public void deleteAll(MovieEntity movie) {
      if (Objects.isNull(movie)) {
         movieRepository.deleteAll();
      } else {
         movieRepository.delete(movie);
      }
   }
}

(6)测试

① 接口测试

import edu.study.module.springbootneo4j.model.MovieEntity;
import edu.study.module.springbootneo4j.model.PersonEntity;
import edu.study.module.springbootneo4j.relation.Roles;
import edu.study.module.springbootneo4j.service.MovieService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RestController
@RequestMapping("/neo4j")
public class Neo4jController {
   @Autowired
   MovieService movieService;

   @GetMapping("/addMovie")
   public void addMovie() {
      MovieEntity movie = new MovieEntity("我爱我的祖国", "该片讲述了新中国成立70年间普通百姓与共和国息息相关的故事");

      List<Roles> rolesList = new ArrayList<>(8);
      rolesList.add(new Roles(1L, new PersonEntity(1974, "黄渤"), List.of("林治远")));
      rolesList.add(new Roles(2L, new PersonEntity(1978, "张译"), List.of("高远")));
      rolesList.add(new Roles(3L, new PersonEntity(1974, "吴京"), List.of("陈冬冬")));
      rolesList.add(new Roles(4L, new PersonEntity(1985, "杜江"), List.of("朱涛")));
      rolesList.add(new Roles(5L, new PersonEntity(1957, "葛优"), List.of("张北京")));
      rolesList.add(new Roles(6L, new PersonEntity(1997, "刘昊然"), List.of("沃德乐")));

      movie.setRoles(rolesList);
      movie.setDirectors(Collections.singletonList(new PersonEntity(1952, "陈凯歌")));
      movieService.add(movie);
   }

   @GetMapping("/")
   public List<MovieEntity> findAll() {
      return movieService.findAll();
   }

   @GetMapping("/findByTitle")
   public List<MovieEntity> findByTitle(@RequestParam("title") String movieTitle) {
      return movieService.findByTitle(movieTitle);
   }

   @GetMapping("/allNodes")
   public Long countAllNodes() {
      return movieService.countAllNodes();
   }

}

② 单元测试

import edu.study.module.springbootneo4j.model.MovieEntity;
import edu.study.module.springbootneo4j.model.PersonEntity;
import edu.study.module.springbootneo4j.relation.Roles;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SpringBootTest
class MovieServiceImplTest {

   @Resource
   private MovieServiceImpl movieService;

   @Test
   void add() {
      // 1. 电影信息
      MovieEntity movie = new MovieEntity("我爱我的祖国1", "该片讲述了新中国成立70年间普通百姓与共和国息息相关的故事");
      // 1.1 电影相关演员角色
      List<Roles> rolesList = new ArrayList<>(8);
      rolesList.add(new Roles( 1L, new PersonEntity(1974, "黄渤"), List.of("林治远")));
      rolesList.add(new Roles(2L, new PersonEntity(1978, "张译"), List.of("高远")));
      rolesList.add(new Roles(3L, new PersonEntity(1974, "吴京"), List.of("陈冬冬")));
      rolesList.add(new Roles(4L, new PersonEntity(1985, "杜江"), List.of("朱涛")));
      rolesList.add(new Roles(5L, new PersonEntity(1957, "葛优"), List.of("张北京")));
      rolesList.add(new Roles(6L, new PersonEntity(1997, "刘昊然"), List.of("沃德乐")));
      movie.setRoles(rolesList);
      // 1.2 电影导演
      movie.setDirectors(Collections.singletonList(new PersonEntity(1952, "陈凯歌")));
      // 2. 添加电影信息
      movieService.add(movie);
   }

   @Test
   void deleteAllMovies() {
      MovieEntity movieEntity = new MovieEntity();
      movieEntity.setTitle("我爱我的祖国");
      movieService.deleteAll(movieEntity);
   }

   @Test
   void findMovieByTitle() {
      MovieEntity movie = movieService.findMovieByTitle("我爱我的祖国");
      System.out.println("电影信息实体:" + movie);
      Assertions.assertNotNull(movie);

   }

   @Test
   void testNeo4jTemplate() {
      MovieEntity movie = movieService.testNeo4jTemplate("我爱我的祖国");
      System.out.println("电影信息实体:" + movie);
      Assertions.assertNotNull(movie);
   }

   @Test
   void findAllMovies() {
      System.out.println("获取所有的电影信息:");
      movieService.findAll().forEach(System.out::println);
   }

   @Test
   void count() {
      Long count = movieService.count();
      System.out.println("电影数量:count = " + count);
      Assertions.assertTrue(count > 0);
   }
}

③ 测试效果

提醒:观察 neo4j 数据库里面的数据是否添加成功,数据量是否统计正常

image.png

附录

(1)相关文章

  1. neo4j 官网:Graph Data Platform | Graph Database Management System | Neo4jneo4j.com/
  2. springboot-neo4j 整合官网:Spring Data Neo4jdocs.spring.io/spring-data…
  3. What is a graph database? - Getting Started (neo4j.com)neo4j.com/docs/gettin…
  4. 其他整合博客:【Neo4j权威指南】SpringBoot 集成 Neo4j 教程SDN_lytao123的博客-CSDN博客_neo4j springboot