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("[email protected]");

        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());
    }
}
相关推荐
南城尽相思1 分钟前
neo4j导入csv文件
neo4j
m0_653031363 分钟前
腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(PostgreSQL版)
运维·数据库·腾讯云
朱小勇本勇22 分钟前
Clang Code Model: Error: The clangbackend executable “D:\Soft\Qt5.12.12\Tool
运维·服务器·数据库·qt·nginx
代码的余温33 分钟前
B树与B+树:数据库索引背后的秘密
数据结构·数据库·b树
noravinsc38 分钟前
django filter 筛选 取出全部用户id
数据库·mysql
不恋水的雨1 小时前
解决sql查询中in查询项过多时很慢的问题
数据库·sql·mysql
betazhou1 小时前
Oracle goldengate参数:TRANLOGOPTIONS DBLOGREADER
数据库·oracle·dblogreader
云心雨禅1 小时前
Spring Boot热更新技巧:节省90%重启时间
java·数据库·spring boot
码上库利南1 小时前
详细讲解Redis为什么被设计成单线程
数据库·redis·缓存
.似水2 小时前
MySQL 索引和select优化
数据库·mysql