# 基本介绍
Neo4j (opens new window) 是一个开源的NoSQL图形数据库,它以“节点”和“关系”为核心存储单元,擅长处理复杂的关联数据,尤其在社交网络、知识图谱、推荐系统、供应链分析等场景中表现出色。
# 核心概念与特点
- 图数据模型
节点(Nodes)# 表示实体(如用户、产品、地点等),包含属性(键值对)
关系(Relationships)# 表示节点之间的连接(如“关注”“购买”“属于”等),有方向、类型和属性
标签(Labels)# 用于分组节点(如“用户”“商品”)
属性(Properties)# 为节点和关系添加描述性数据(如用户姓名、关系强度)
- 原生图存储
# 直接使用图结构存储数据,而非将图数据映射到传统关系型数据库的表结构,避免了“连接查询”的性能损耗
# 关系查询速度与数据规模无关(仅取决于路径长度),适合处理高关联度数据
- Cypher 查询语言
# 类似 SQL 的声明式查询语言,简洁易读,支持模式匹配、路径查找等操作
# 示例:查询“用户A关注的所有用户”
MATCH (user:User {name: "A"})-[:FOLLOWS]->(followed:User)
RETURN followed.name;
- 高扩展性
# 支持集群部署(企业版),通过复制和分片提升吞吐量和可用性
# 提供 ACID 事务特性,确保数据一致性
- 生态与工具
# 支持多种编程语言驱动(Java、Python、JavaScript 等)
# 提供桌面版管理工具 Neo4j Browser,支持可视化查询和图结构展示
# 集成 ETL 工具(如 Apache NiFi)和数据分析框架(如 Spark)
# 安装使用
# 安装卸载社区版
Neo4j 依赖 Java 运行环境,需先安装 OpenJDK11 或 17(Neo4j 4.4+ 支持)
- Ubuntu/Debian(使用 APT)
# 添加 Neo4j 仓库密钥
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add -
# 添加仓库源
echo 'deb https://debian.neo4j.com stable 4.4'|sudo tee -a /etc/apt/sources.list.d/neo4j.list
# 安装 Neo4j
sudo apt update
sudo apt install neo4j=1:4.4.15
# 启动服务
sudo systemctl start neo4j
sudo systemctl enable neo4j # 设置开机自启
# 卸载 Neo4j
sudo apt remove neo4j
sudo apt purge neo4j # 彻底删除配置文件
- Windows(使用安装包)
# 从 Neo4j 官网 https://neo4j.com/download/ 下载 Windows 安装包(.exe)
# 运行安装程序,按向导提示完成安装
# 启动 Neo4j Desktop,创建并启动数据库实例
# 注意:先设置环境变量:
# 变量名 NEO4J_DESKTOP_DATA_PATH
# 变量值 你想要将数据移动到的位置,例 D:\Neo4j\Data
- Docker 快速安装
# 拉取官方镜像
docker pull neo4j:4.4-community
# 启动容器
docker run \
--name neo4j \
-p 7474:7474 -p 7687:7687 \
-d \
-v $HOME/neo4j/data:/data \
-v $HOME/neo4j/logs:/logs \
-v $HOME/neo4j/import:/var/lib/neo4j/import \
-v $HOME/neo4j/plugins:/plugins \
--env NEO4J_AUTH=neo4j/password \
neo4j:4.4-community
# 访问 http://localhost:7474,使用 neo4j/password 登录
# APOC库安装
- Neo4j Desktop版本
# 在您的项目下,找到正在使用的数据库管理系统的 Plugins 选项卡
# 在插件列表中查找 APOC 并进行安装。这能确保安装与您Neo4j版本兼容的APOC版本
- 对于非Desktop版本
# 需要手动将APOC的JAR文件下载到Neo4j的 plugins 目录中
# 请务必从官方渠道(如GitHub Releases)下载与您Neo4j版本匹配的APOC版本
- 当APOC正确安装并配置后,您就可以使用 apoc.create.addLabels 过程了。它的基本用法如下
MATCH (n) # 匹配您想要添加标签的节点
CALL apoc.create.addLabels(n, [n.type]) # 假设节点的属性名为 'type'
YIELD node
RETURN node
注意
- 安装APOC插件或修改配置文件后,都需要重启Neo4j数据库服务器
- 对于Neo4j Desktop,只需停止再启动数据库即可
- APOC库和Neo4j数据库的主版本号需要严格匹配
- 例如,Neo4j 5.x 通常需要APOC 5.x,Neo4j 4.x 需要APOC 4.x
- 检查您的Neo4j版本(可在Neo4j Browser中执行 :sysinfo 查看)
# 云服务版
登录 (opens new window)查看 (opens new window)
# 常见问题
端口冲突 # 检查是否有其他服务占用 7474(HTTP)或 7687(Bolt)端口
内存不足 # 调整 neo4j.conf 中的堆内存参数
权限问题 # 确保数据目录有写入权限
防火墙 # 开放 7474 和 7687 端口(生产环境建议限制访问)
# 配置 Neo4j
编辑配置文件 conf/neo4j.conf(Linux/macOS)或通过 Neo4j Desktop 图形界面配置:
- 启用远程访问(默认仅本地访问)
# 取消注释以下行
dbms.default_listen_address=0.0.0.0
- 调整内存分配(根据服务器内存调整)
dbms.memory.heap.initial_size=1G
dbms.memory.heap.max_size=2G
- 配置密码(首次启动后设置)
# 初始默认密码为 "neo4j"
# 启动后访问 http://localhost:7474,按提示修改密码
# Cypher 语言
####基础增删改查
- 创建数据(CREATE)
# 创建单个节点(带标签和属性)
CREATE (:User {name: "Alice", age: 30})
# 创建两个节点并建立关系
CREATE (u:User {name: "Bob"})-[r:FRIENDS_WITH {since: 2020}]->(v:User {name: "Charlie"})
RETURN u, r, v;
# 批量创建节点(使用UNWIND)
UNWIND [{name: "Dave", age: 25}, {name: "Eve", age: 28}] AS data
CREATE (user:User) SET user = data;
- 查询数据(MATCH)
# 查询所有用户节点
MATCH (u:User) RETURN u;
# 查询名为Alice的用户
MATCH (u:User {name: "Alice"}) RETURN u;
# 查询朋友关系(含关系属性)
MATCH (u:User)-[r:FRIENDS_WITH]->(v:User)
RETURN u.name, r.since, v.name;
# 查询特定路径(Bob的朋友的朋友)
MATCH (b:User {name: "Bob"})-[:FRIENDS_WITH*2]-(fof:User)
RETURN fof.name;
- 更新数据(SET、REMOVE)
# 修改节点属性
MATCH (u:User {name: "Alice"})
SET u.age = 31, u.city = "New York"
RETURN u;
# 添加标签
MATCH (u:User {name: "Bob"})
SET u:Developer
RETURN u;
# 删除属性
MATCH (u:User {name: "Charlie"})
REMOVE u.age
RETURN u;
- 删除数据(DELETE、DETACH DELETE)
# 删除无关系节点
MATCH (u:User {name: "Eve"})
DELETE u;
# 删除节点及其所有关系(危险操作!)
MATCH (u:User {name: "Dave"})
DETACH DELETE u;
# 删除所有节点和关系(清空数据库)
MATCH (n)
DETACH DELETE n;
- 聚合统计
# 计算用户数量
MATCH (u:User)
RETURN count(u) AS total_users;
# 计算每个用户的朋友数量
MATCH (u:User)-[:FRIENDS_WITH]->(f:User)
RETURN u.name, count(f) AS friend_count
ORDER BY friend_count DESC;
- 路径查询
# 查找两个用户之间的最短路径
MATCH p=shortestPath((a:User {name: "Alice"})-[:FRIENDS_WITH*]-(b:User {name: "Charlie"}))
RETURN p;
- 条件过滤(WHERE)
# 查询年龄大于25的用户
MATCH (u:User)
WHERE u.age > 25
RETURN u.name, u.age;
# 复杂条件(AND、OR、IN)
MATCH (u:User)
WHERE u.age > 25 AND u.name IN ["Alice", "Bob"]
RETURN u;
# 完整案例:社交网络分析
- 数据导入
# 创建用户节点
CREATE (:User {id: 1, name: "Alice", city: "New York"})
CREATE (:User {id: 2, name: "Bob", city: "London"})
CREATE (:User {id: 3, name: "Charlie", city: "Paris"})
CREATE (:User {id: 4, name: "Dave", city: "New York"})
CREATE (:User {id: 5, name: "Eve", city: "London"});
# 创建朋友关系
MATCH (a:User {id: 1}), (b:User {id: 2})
CREATE (a)-[:FRIENDS_WITH {since: 2019}]->(b);
MATCH (a:User {id: 1}), (c:User {id: 3})
CREATE (a)-[:FRIENDS_WITH {since: 2020}]->(c);
MATCH (b:User {id: 2}), (d:User {id: 4})
CREATE (b)-[:FRIENDS_WITH {since: 2018}]->(d);
MATCH (c:User {id: 3}), (e:User {id: 5})
CREATE (c)-[:FRIENDS_WITH {since: 2021}]->(e);
- 查询示例
# 查询纽约用户及其朋友
MATCH (u:User {city: "New York"})-[:FRIENDS_WITH]->(f:User)
RETURN u.name AS user, f.name AS friend, f.city AS friend_city;
# 查找有共同朋友的用户对
MATCH (u1:User)-[:FRIENDS_WITH]->(f:User)<-[:FRIENDS_WITH]-(u2:User)
WHERE u1.id < u2.id # 避免重复对
RETURN u1.name, u2.name, count(f) AS common_friends
ORDER BY common_friends DESC;
- 删除示例
# 删除单个关系
# 假设你要删除Person节点和Movie节点之间名为ACTED_IN的关系,可按如下方式操作:
# 此查询会找出由演员Tom Hanks到电影Forrest Gump的ACTED_IN关系并将其删除
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name = 'Tom Hanks' AND m.title = 'Forrest Gump'
DELETE r
# 删除特定类型的所有关系
# 若要删除图中所有FOLLOWS类型的关系,可使用下面的查询:
# 执行这个查询后,所有FOLLOWS类型的关系都会被删除,但相关节点不会受到影响
MATCH ()-[r:FOLLOWS]->()
DELETE r
# 删除节点及与之关联的所有关系
# 当你要删除某个节点,同时删除该节点的所有入站和出站关系时,可以这样做:
# 该查询会删除名为Alice的节点以及与它相关的所有关系
MATCH (p:Person {name: 'Alice'})-[r]-()
DELETE p, r
# 使用 DETACH DELETE 直接删除节点及其关系
# 使用DETACH DELETE语句能更简洁地删除节点及其所有关系:
# 此操作会自动删除与Bob节点相关的所有关系,然后再删除Bob节点
MATCH (p:Person {name: 'Bob'})
DETACH DELETE p
# 性能考量
# 进行大量关系删除操作时,建议采用分批处理的方式,防止内存不足
# 可以使用LIMIT和APOC程序来实现分批删除,示例如下:
# 这个方法每次会删除1000个OLD_RELATIONSHIP类型的关系,直至全部删除完毕
CALL apoc.periodic.commit("
MATCH ()-[r:OLD_RELATIONSHIP]->()
WITH r LIMIT 1000
DELETE r
RETURN count(*)
")
# 索引与约束
# 创建唯一约束(确保用户名唯一)
(neo4j version <4.0)
CREATE CONSTRAINT ON (u:User) ASSERT u.name IS UNIQUE;
(neo4j version >= 4.0)
CREATE CONSTRAINT FOR (u:User) REQUIRE u.name IS UNIQUE;
# 创建索引(加速属性查询)
(neo4j version <4.0)
CREATE INDEX ON :User(age);
(neo4j version >= 4.0)
CREATE INDEX FOR (u:User) ON (u.age);
# 执行建议
- 使用 Neo4j Browser
# 启动 Neo4j,访问 http://localhost:7474,输入用户名和密码(默认 neo4j/neo4j,首次登录需修改)
- 可视化结果
# Neo4j Browser 会自动将查询结果以图形方式展示,节点用图标表示,关系用箭头连接
- 性能提示
# 避免全图扫描(如 MATCH (n) RETURN n),始终使用标签和索引。
# 使用 EXPLAIN 或 PROFILE 分析查询性能:
EXPLAIN MATCH (u:User)-[:FRIENDS_WITH]->(f:User) RETURN u, f;
# Spring Boot 整合 Neo4j
# 集成步骤
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
- 配置 Neo4j 连接信息
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
# 实体类定义
- 节点实体类:使用 @Node 注解定义节点实体类
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
@Node
public class Person {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
// 空构造方法、带参数构造方法、getter 和 setter 方法
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter 和 setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 关系实体类:使用 @Relationship 注解定义关系实体类
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;
@Node
public class Movie {
@Id @GeneratedValue
private Long id;
private String title;
private String genre;
// 空构造方法、带参数构造方法、getter 和 setter 方法
public Movie() {}
public Movie(String title, String genre) {
this.title = title;
this.genre = genre;
}
// getter 和 setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
// 定义从 Movie 到 Person 的关系(演员参演)
@Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)
private Person actor;
public Person getActor() {
return actor;
}
public void setActor(Person actor) {
this.actor = actor;
}
}
# 仓库接口
- 创建一个仓库接口来操作节点实体类
import org.springframework.data.neo4j.repository.Neo4jRepository;
public interface PersonRepository extends Neo4jRepository<Person, Long> {
// 自定义查询方法示例:根据姓名查询
Person findByName(String name);
}
# 服务层
- 创建一个服务类来调用仓库接口的方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PersonService {
@Autowired
private PersonRepository personRepository;
// 保存 Person
public Person savePerson(Person person) {
return personRepository.save(person);
}
// 根据姓名查询 Person
public Person findPersonByName(String name) {
return personRepository.findByName(name);
}
// 删除所有 Person
public void deleteAllPersons() {
personRepository.deleteAll();
}
}
# 控制器
- 创建一个控制器类来处理 HTTP 请求,并调用服务层的方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/persons")
public class PersonController {
@Autowired
private PersonService personService;
// 添加 Person
@PostMapping
public Person addPerson(@RequestBody Person person) {
return personService.savePerson(person);
}
// 根据姓名查询 Person
@GetMapping("/{name}")
public Person getPersonByName(@PathVariable String name) {
return personService.findPersonByName(name);
}
// 删除所有 Person
@DeleteMapping
public void deleteAllPersons() {
personService.deleteAllPersons();
}
}