Spring Data Neo4j

文章目录

Spring Data Neo4j简介

  • Spring Data Neo4j官网学习地址
  • Spring Data Neo4j简称SDN,SDN建立在Neo4j OGM基础之上,使用SDN可以帮助简化OGM的使用,更加容易与Spring框架整合应用。
  • SDN 完全依赖于 Neo4j Java 驱动程序,而无需在映射框架和驱动程序之间引入另一个"驱动程序"或"传输"层。Neo4j Java 驱动程序(有时称为 Bolt 或 Bolt 驱动程序)用作协议,与 JDBC 用于关系数据库非常相似。
  • SDN 是一个对象图映射 (OGM) 库。OGM 将图形中的节点和关系映射到域模型中的对象和引用。对象实例映射到节点,而对象引用使用关系进行映射,或序列化为属性(例如,对 Date 的引用)。JVM 原语映射到节点或关系属性。OGM 抽象了数据库,并提供了一种便捷的方法,可以将域模型持久保存在图形中并对其进行查询,而无需直接使用低级驱动程序。它还为开发人员提供了灵活性,可以在 SDN 生成的查询不足的情况下提供自定义查询。
  • 驱动程序用于连接数据库。包括三种连接方式:嵌入式、HTTP、和二进制Bolt。
  • Neo4j对象图映射器(neo4j-OGM)
  • Spring Data Neo4j在Neo4j-OGM上提供代码,帮助快速构建基于Spring的Neo4j应用程序。

Neo4j-OGM与SDN的区别

  • Spring Data Neo4i(SDN)使用Neo4j-OGM执行与数据库之间的映射。在Spring Data JPA的上下文中,可以将OGM与Hibernate之类的PA实现在同一级别上看待。

开发体验

版本说明

组件 版本
Spring Boot 3.4.2
neo4j 5.26.1
IDEA 2024.2.1
spring-data-neo4j 7.4.2

项目地址

项目结构

bash 复制代码
.
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── yang
│   │   │           └── stu
│   │   │               ├── StuApplication.java
│   │   │               ├── configuration
│   │   │               │   └── Neo4jConfiguration.java
│   │   │               ├── pojo
│   │   │               │   ├── ActedIn.java
│   │   │               │   ├── Movie.java
│   │   │               │   ├── Person.java
│   │   │               │   └── PersonInfo.java
│   │   │               ├── repository
│   │   │               │   ├── ActedRepository.java
│   │   │               │   ├── MovieRepository.java
│   │   │               │   └── PersonRepository.java
│   │   │               └── service
│   │   │                   └── PersonService.java
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── com
│               └── yang
│                   └── stu
│                       ├── ActedInRepositoryTest.java
│                       ├── PersonServiceTest.java
│                       └── StuApplicationTests.java

创建项目


配置连接信息

bash 复制代码
spring.application.name=Stu
spring.neo4j.uri=bolt://IP:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=neo4j
spring.neo4j.pool.max-connection-pool-size=1000
spring.neo4j.pool.max-connection-lifetime=2h
logging.level.org.springframework.data.neo4j=ERROR

激活事务管理器

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement //激活事务管理器
public class Neo4jConfiguration {

}

创建实体类

Movie类

java 复制代码
import lombok.*;
import org.springframework.data.neo4j.core.schema.*;

import java.io.Serializable;
import java.util.List;

@Node //spring-data-neo4j 7.4.2中使用Node注解标记实体类
@NoArgsConstructor
@ToString
@Data 
public class Movie implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private Long released;

    @Property("tagline")
    private String tagLine;

    //设置关系并且指定方向
    @Relationship(type = "ACTED_IN",direction = Relationship.Direction.INCOMING)
    private List<ActedIn> actedIns;
}    

Person类

java 复制代码
import lombok.*;
import org.springframework.data.neo4j.core.schema.*;
import java.io.Serializable;
import java.util.List;

@Node
@Data
@ToString
public class Person implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private Long born;

    private String sex;

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

    //导演关系
    @Relationship(type = "DIRECTED")
    private List<Movie> directedMovies;

    //出品关系
    @Relationship(type = "PRODUCED")
    private List<Movie> producedMovies;

    //写作关系
    @Relationship(type = "WROTE")
    private List<Movie> wroteMovies;
}

ActedIn关系类

  • 使用
sql 复制代码
import lombok.*;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;


@RelationshipProperties
@ToString(exclude = {"person","movie"})
@Data
public class ActedIn {
    @Id
    @GeneratedValue
    private Long id;

    //关系中的开始节点 自动推断不用注解
    private Person person;

    @TargetNode  //关系中的结束节点 使用注解TargetNode
    private Movie movie;

    private String[] roles;
}    

创建Dao层

  • Repository 是一个重要的组件,用于定义与 Neo4j 数据库的交互。管理实体的 CRUD(创建、读取、更新、删除)操作。
  • Dao层的作用:
    • 数据访问层:Repository 作为一个接口,负责定义数据访问层,其中的方法通常与实体的数据库操作(例如存储、查找、更新和删除)相关联。
    • 自动化 CRUD 操作:通过继承 Neo4jRepository<ActedIn, Long>,XRepository 自动获得一些基本的 CRUD 方法,如 save(), findById(), delete(), 以及可以通过定义其他方法实现查询功能。
    • 自定义查询:除了继承基本的 CRUD 方法,还可以在 XRepository 中定义查询方法
  • @Repository 注解:这个注解表明这是一个 DAO(数据访问对象) 类;Spring 会自动识别这个类,并进行相关的异常转换。
java 复制代码
import com.yang.stu.pojo.ActedIn;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ActedRepository extends Neo4jRepository<ActedIn,Long> {

}
java 复制代码
import com.yang.stu.pojo.Movie;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {

    Movie findByTitle(String 西红柿首富);
}
java 复制代码
import com.yang.stu.pojo.Person;
import com.yang.stu.pojo.PersonInfo;
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 PersonRepository extends Neo4jRepository<Person,Long> {
    Person findByName(String name);

    List<Person> findByNameContains(String tom);

    @Query("match (m:Movie) <-[:ACTED_IN]-(p) where m.title=$movieName return p")
    List<Person> findByMovie(@Param("movieName") String movieName);

    @Query("match (n:Person {name:$name})-[:ACTED_IN]->(x) return n.name as name,$year-n.born as age,collect(x) as movies")
    PersonInfo findAgeAndMovieCountByName(@Param("name") String name, @Param("year") Integer year);
}

service层

sql 复制代码
import com.yang.stu.pojo.Person;
import com.yang.stu.repository.PersonRepository;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Random;

@Service
public class PersonService {
    @Resource
    private PersonRepository personRepository;

    @Transactional //使用事务注解 确保操作的原子性
    public void addSexToAllPerson(){
        List<Person> personList = personRepository.findAll();
        String[] sexs = {"man", "woman"};
        for (Person person : personList) {
            person.setSex(sexs[new Random().nextInt(2)]);
            personRepository.save(person);
        }
    }
}

测试案例

CRUD Test

  • 普通的CRUD操作
java 复制代码
@SpringBootTest
class StuApplicationTests {
	@Autowired
	private PersonRepository personRepository;

	@Resource
	private MovieRepository movieRepository;

	@Test
	public void existPersonById() {
		boolean exists = personRepository.existsById(34L);
		System.out.println(exists);
	}

	@Test
	public void queryPersonAll() {
		List<Person> personList = personRepository.findAll();
		personList.forEach(x-> System.out.println(x.toString()));
	}

	@Test
	public void queryPersonById() {
		Optional<Person> optionalPerson = personRepository.findById(34L);
		System.out.println(optionalPerson.get().getName());
	}


	@Test
	public void queryPersonByName() {
		String name = "Tom Hanks";  // 假设我们要查询的名字是"张三"
		Person person = personRepository.findByName(name);
		System.out.println(person);
	}

	@Test
	public void queryMovieById() {
		Optional<Movie> optionalMovie = movieRepository.findById(32L);
		System.out.println(optionalMovie.get().getTitle());
	}

	@Test
	public void findByNameContains() {
		List<Person> personList = personRepository.findByNameContains("Tom");
		personList.forEach(x-> System.out.println(x.toString()));
	}

	@Test
	public void testFindPage(){
		PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Order.asc("name")));
		Page<Person> page = personRepository.findAll(pageRequest);
		List<Person> personList = page.getContent();
		System.out.println("数据总量 :"+page.getTotalElements());
		System.out.println("总页数 :"+page.getTotalPages());
		personList.forEach(x-> System.out.println(x.toString()));
	}

	@Test
	public void fineActorsByMovieName(){
		List<Person> actors = personRepository.findByMovie("Johnny Mnemonic");
		actors.forEach(x-> System.out.println(x.toString()));
	}

	@Test
	public void findAgeAndMovieCountByName(){
		PersonInfo result = personRepository.findAgeAndMovieCountByName("Tom Hanks", 2025);
		System.out.println(result);
	}

	@Test
	public void savePerson(){
		Person person = new Person();
		person.setName("沈崇");
		person.setBorn(1980L);
		Person savedPerson = personRepository.save(person);
		System.out.println(savedPerson);
	}

	@Test
	public void SaveMovie(){
		Movie movie = new Movie();
		movie.setTitle("西红柿首富");
		movie.setReleased(2019L);
		movie.setTagLine("搞笑");

		Movie savedMovie = movieRepository.save(movie);
		System.out.println(savedMovie);
	}

	@Test
	public void findByName(){
		Movie movie = movieRepository.findByTitle("西红柿首富");
		System.out.println(movie);
	}

	@Test
	public void UpdataMovie(){
		Movie movie = movieRepository.findByTitle("西红柿首富");
		movie.setReleased(2018L); //更新值
		Movie saved = movieRepository.save(movie);
		System.out.println(saved);
	}

	/**
	 * 根据id删除
	 */
	@Test
	public void deleteMovieById(){
		movieRepository.deleteById(32L);
	}

	/**
	 * 根据实体删除
	 */
	@Test
	public void deleteMovieByTitle(){
		Movie movie = movieRepository.findByTitle("西红柿首富");
		movieRepository.delete(movie);
	}
}

PersonService Test

  • 测试PersonService添加性别的方法
java 复制代码
@SpringBootTest
public class PersonServiceTest {
    @Resource
    private PersonService personService;

    @Test
    public void testAddSex() {
        personService.addSexToAllPerson();
    }
}

ActedIn Test

  • 测试级联保存和批量保存
java 复制代码
@SpringBootTest
public class ActedInRepositoryTest {

    @Resource
    private PersonRepository personRepository;

    @Resource
    private MovieRepository movieRepository;

    @Test
    public void saveActed() {
        // 查询 Person 和 Movie 节点
        Person person = personRepository.findByName("沈崇");
        Movie movie = movieRepository.findByTitle("西红柿首富");

        // 创建关系
        ActedIn actedIn = new ActedIn();
        actedIn.setPerson(person);
        actedIn.setMovie(movie);
        actedIn.setRoles(new String[]{"王多鱼"});

        // 将 ActedIn 添加到 Person 的关系列表中
        List<ActedIn> actedIns = person.getActedIns();
        actedIns.add(actedIn);
        person.setActedIns(actedIns);

        // 保存 Person,实现级联保存
        personRepository.save(person);
    }

    @Test
    public void saveAll(){
        Movie movie = movieRepository.findByTitle("西红柿首富");
        List<Person> personList=new ArrayList<>();
        Person person = new Person();
        person.setName("艾伦");
        person.setBorn(1981L);

        ActedIn actedIn = new ActedIn();
        actedIn.setPerson(person);
        actedIn.setMovie(movie);
        actedIn.setRoles(new String[]{"高然"});

        person.setActedIns(Arrays.asList(actedIn));
        personList.add(person);

        Person person2 = new Person();
        person2.setName("张一鸣");
        person2.setBorn(1986L);

        ActedIn actedIn2 = new ActedIn();
        actedIn2.setPerson(person2);
        actedIn2.setMovie(movie);
        actedIn2.setRoles(new String[]{"庄强"});

        person2.setActedIns(Arrays.asList(actedIn2));

        personList.add(person2);

        List<Person> saveAll = personRepository.saveAll(personList);
        saveAll.forEach(System.out::println);

    }
}

执行结果查询

  • 查看参演西红柿首富的演员
sql 复制代码
match (n:Person)-->(m:Movie) where m.title="西红柿首富" return *
  • 删除节点和关系
sql 复制代码
match (n:Movie)<-[r]-(m) where n.title="西红柿首富" delete m,r,n
相关推荐
shuair39 分钟前
idea 2023.3.7常用插件
java·ide·intellij-idea
小安同学iter1 小时前
使用Maven将Web应用打包并部署到Tomcat服务器运行
java·tomcat·maven
Yvonne9781 小时前
创建三个节点
java·大数据
不会飞的小龙人2 小时前
Kafka消息服务之Java工具类
java·kafka·消息队列·mq
是小崔啊3 小时前
java网络编程02 - HTTP、HTTPS详解
java·网络·http
brevity_souls3 小时前
Spring Boot 内置工具类
java·spring boot
小钊(求职中)3 小时前
Java开发实习面试笔试题(含答案)
java·开发语言·spring boot·spring·面试·tomcat·maven
shix .4 小时前
什么是tomcat
java·tomcat
java技术小馆4 小时前
Deepseek整合SpringAI
java·spring cloud
天荒地老笑话么4 小时前
Mac安装配置Tomcat 8
java·macos·tomcat