文章目录
- [Spring Data Neo4j简介](#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