Neo4j图数据库入门与Spring Boot整合案例

文章目录

简介

Neo4j是一个开源的NoSQL数据库,使用scala和Java开发。

  • 是世界上最先进的图数据库之一,提供原生的图数据存储、检索、处理
  • 采用属性图模型,极大的完善和丰富图数据模型
  • 专属查询语言Cypher,直观、高效

特性

对比

为了解决关系型数据库中表示关系需要创建中间表且查询效率低的问题。思想,每个用户看成一个节点,节点和节点之间是有关系的。

安装(docker容器)

shell 复制代码
docker run -d -p 7474:7474 -p 7687:7687 --name neo4j -e "NEO4J_AUTH=neo4j/admin123" -v D/dockerv/neo4j/data:/mydata/data -v D/dockerv/neo4j/logs:/mydata/logs -v D/dockerv/neo4j/conf:/var/lib/neo4j/conf -v D/dockerv/neo4j/import:/var/lib/neo4j/import neo4j

连接数据库

  1. 访问,并输入密码
    http://localhost:7474/browser/
  2. 连接成功

Neo4j-CQL语句使用

Neo4j的Cypher语言是为处理图形数据而创建的,CQL代表Cypher查询语句。

  • 是Neo4j图形数据库的查询语句
  • 是一种声明式的模糊匹配语句
  • 遵循SQL语法
  • 简单、人性化、可读性高

()里面的是节点,[]里面的是关系,{}里面的是属性,>表示关系的方向

sql 复制代码
-- 创建一个数据模型,A与B是朋友,B与C是朋友,但A听说了C,C不知道A
create (A:Person{name:'huathy'})-[:Friend]->
	 (B:Person{name:'DY'})-[:Friend]->
	 (C:Person{name:'JJ'}), 
	 (A)-[:Know]->(C)
sql 复制代码
-- 查询A的朋友,名字叫DY的
match (a)-:[:Friend]->(b)
where b.name = 'DY'
return b
sql 复制代码
-- 删除标签为Person的
MATCH (p:Person) DETACH DELETE p
-- Deleted 3 nodes, deleted 3 relationships

Neo4j整合SpringBoot

创建maven项目并引入依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.25</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-neo4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.34</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

编写数据库层 PersonRepository

java 复制代码
package com.hx.neo4j.dao;

import com.hx.neo4j.pojo.Person;
import com.hx.neo4j.vo.PersonVo;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface PersonRepository extends Neo4jRepository<Person, Long> {

    @Query("MATCH (p:user)-[:Friend]->(f:user) WHERE p.name = $name RETURN f")
    List<Person> findFriendsByName(String name);

    @Query("MATCH (p:user)-[:Friend]->(f:user) RETURN p.name as personName, collect(f.name) as friends")
    List<PersonVo> listAllFriendRelationships();

}

编写 PersonService

java 复制代码
package com.hx.neo4j.service;

import com.hx.neo4j.dao.PersonRepository;
import com.hx.neo4j.pojo.Person;
import lombok.RequiredArgsConstructor;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class PersonService {

    private final PersonRepository personRepository;
    private final Neo4jClient neo4jClient;


    @Transactional
    public void createPersonWithFriend(Person person, Person friend) {
        person.setFriends(List.of(friend));
        personRepository.save(friend);
        // 如果失败,事务会回滚
        throw new RuntimeException("test rollback");
    }

    public List<String> findPersonNamesByCustomLogic() {
        return (List<String>) neo4jClient.query("MATCH (p:User) WHERE size((p)-[:Friend]->()) >= 1 RETURN p.name")
                .fetchAs(String.class)
                .mappedBy((typeSystem, record) -> record.get("p.name").asString())
                .all();
    }
}

编写测试类

java 复制代码
package com.hx.neo4j;

import cn.hutool.json.JSONUtil;
import com.hx.neo4j.dao.PersonRepository;
import com.hx.neo4j.pojo.KnowShip;
import com.hx.neo4j.pojo.Person;
import com.hx.neo4j.service.PersonService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;

import java.util.List;
import java.util.Map;

@SpringBootTest
class Neo4jApplicationTests {
    @Autowired
    private PersonRepository personRepository;
    @Autowired
    private PersonService personService;

    /**
     * 测试创建用户与关系
     */
    @Test
    void createUser() {
        Person personC = Person.builder().name("JJ").build();
        Person personB = Person.builder().name("DY")
                .friends(List.of(personC))
                .build();
        Person personA = Person.builder().name("Huathy")
                .age(18)
                .friends(List.of(personB)).build();
        personA.setKnowshipList(List.of(
                KnowShip.builder().person(personC).build()
        ));
        personRepository.saveAll(List.of(personA, personB));
    }

    /**
     * 测试删除
     */
    @Test
    void deleteUser() {
        Person personB = Person.builder().name("DY").build();
        Person personA = Person.builder().name("Huathy").build();
        personRepository.deleteAll(List.of(personA, personB));
    }

    /**
     * 测试删除所有
     */
    @Test
    void deleteUserAll() {
        personRepository.deleteAll();
    }

    /**
     * 测试查询所有关系
     */
    @Test
    void listAllFriendRelationships() {
        Object maps = personRepository.listAllFriendRelationships();
        System.out.println("maps ==> " + JSONUtil.toJsonPrettyStr(maps));
    }

    /**
     * 测试查询用户
     */
    @Test
    void findUser() {
        // 采用Spring Data JPA的Example方式,会自动的返回Friends关系
        Example<Person> example = Example.of(Person.builder().name("Huathy").build());
        List<Person> all = personRepository.findAll(example);
        System.out.println("user ==> " + JSONUtil.toJsonPrettyStr(all));
        // 采用手写CQL语句的方式不会返回关系
        List<Person> all2 = personRepository.findFriendsByName("Huathy");
        System.out.println("user2 ==> " + JSONUtil.toJsonPrettyStr(all2));
    }

    /**
     * 测试事务回滚
     */
    @Test
    void createPersonWithFriend() {
        Person person1 = Person.builder().name("张三").build();
        Person person2 = Person.builder().name("李四").build();
        personService.createPersonWithFriend(person1,person2);
    }

    /**
     * Neo4JClient 自定义测试
     */
    @Test
    void findPersonNamesByCustomLogic() {
        List<String> list = personService.findPersonNamesByCustomLogic();
        System.out.println("list ==> " + JSONUtil.toJsonPrettyStr(list));
    }

}

多标签、多关系、多类型节点使用

创建实体类

java 复制代码
@Node("Company")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
}

@Node("Customer")
@Data
public class Customer extends Person {
    private String contactInfo;

    @Relationship(type = "BUYS", direction = Relationship.Direction.OUTGOING)
    private List<Product> products;

    @Relationship(type = "ASSIGNED_EMPLOYEE", direction = Relationship.Direction.INCOMING)
    private Employee accountManager; // 反向关系
}

@Node({"Person", "Employee"})
@Data
@Accessors(chain = true)
public class Employee extends Person {
    private String position;

    @Relationship(type = "WORKS_FOR")
    private Company company;

    @Relationship(type = "SERVES", direction = Relationship.Direction.OUTGOING)
    private List<Customer> customers;

}

@Node("Product")
@Data
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
}

编写dao数据库层

java 复制代码
package com.hx.neo4j.dao;

import com.hx.neo4j.pojo.Company;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CompanyRepository extends Neo4jRepository<Company, Long> {
}

@Repository
public interface CustomerRepository extends Neo4jRepository<Customer, Long> {
}
@Repository
public interface EmployeeRepository extends Neo4jRepository<Employee, Long> {
}
@Repository
public interface ProductRepository extends Neo4jRepository<Product, Long> {
}

编写测试用例

java 复制代码
package com.hx.neo4j;

import cn.hutool.json.JSONUtil;
import com.hx.neo4j.dao.*;
import com.hx.neo4j.pojo.*;
import com.hx.neo4j.service.PersonService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.Example;

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

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class Neo4jApplicationTests2 {
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private CompanyRepository companyRepository;
    @Autowired
    private CustomerRepository customerRepository;
    @Autowired
    private EmployeeRepository employeeRepository;
    private static Long employeeId;
    private static Long customerId;

    /**
     * 创建公司
     */
    @Test
    @Order(1)
    public void testCreateCompany() {
        Company company = new Company();
        company.setName("OpenAI");
        Company saved = companyRepository.save(company);
        System.out.println(saved.getId());
        Assertions.assertNotNull(saved.getId());
    }

    /**
     * 员工入职
     */
    @Test
    @Order(2)
    public void testEmployeeOnboarding() {
        Example<Company> companyExample = Example.of(Company.builder()
                .name("OpenAI")
                .build());
        Company company = companyRepository.findOne(companyExample).orElseThrow();
        Employee employee = new Employee();
        employee.setName("Alice");
        employee.setPosition("Engineer");
        employee.setCompany(company);

        Employee saved = employeeRepository.save(employee);
        employeeId = saved.getNodeId();

        Assertions.assertNotNull(employeeId);
        Assertions.assertEquals("Engineer", saved.getPosition());
    }

    /**
     * 创建客户
     */
    @Test
    @Order(3)
    public void testAddCustomer() {
        Customer customer = new Customer();
        customer.setName("Bob");
        customer.setContactInfo("bob@example.com");

        Customer saved = customerRepository.save(customer);
        customerId = saved.getNodeId();

        Assertions.assertNotNull(customerId);
    }

    /**
     * 分配客户给员工
     */
    @Test
    @Order(4)
    public void testAssignCustomerToEmployee() {
        Employee employeeParam = new Employee();
        employeeParam.setName("Alice");
        Employee employee = employeeRepository.findOne(Example.of(employeeParam)).get();

        Customer customerParam = new Customer();
        customerParam.setName("Bob");
        Customer customer = customerRepository.findOne(Example.of(customerParam)).get();

        employee.setCustomers(Collections.singletonList(customer));
        employeeRepository.save(employee);

        Employee updated = employeeRepository.findOne(Example.of(employeeParam)).get();
        Assertions.assertFalse(updated.getCustomers().isEmpty());
        Assertions.assertEquals("Bob", updated.getCustomers().get(0).getName());
    }

    /**
     * 客户购买产品
     */
    @Test
    @Order(5)
    public void testCreateProductAndCustomerBuy() {
        Product product = new Product();
        product.setName("GPT Box");
        Product savedProduct = productRepository.save(product);

        Customer customer = customerRepository.findById(customerId).orElseThrow();
        customer.setProducts(Collections.singletonList(savedProduct));
        customerRepository.save(customer);

        Customer updated = customerRepository.findById(customerId).orElseThrow();
        Assertions.assertFalse(updated.getProducts().isEmpty());
        Assertions.assertEquals("GPT Box", updated.getProducts().get(0).getName());
    }
}
相关推荐
huazhixuthink31 分钟前
PostgreSQL三种关闭方式的区别
数据库·postgresql
阿里小阿希5 小时前
Vue3 + Element Plus 项目中日期时间处理的最佳实践与数据库设计规范
数据库·设计规范
白鹭6 小时前
MySQL源码部署(rhel7)
数据库·mysql
666和7776 小时前
Struts2 工作总结
java·数据库
还听珊瑚海吗7 小时前
SpringMVC(一)
数据库
星期天要睡觉8 小时前
MySQL 综合练习
数据库·mysql
Y4090018 小时前
数据库基础知识——聚合函数、分组查询
android·数据库
JosieBook9 小时前
【数据库】MySQL 数据库创建存储过程及使用场景详解
数据库·mysql