图数据库Neo4j的基本使用及与SpringBoot集成


Neo4j

官网地址:https://neo4j.com/

下载地址:https://neo4j.com/download-center/#community

官方入门文档:https://neo4j.com/docs/getting-started

安装

Window安装

window下安装下载对应的安装包,然后配置环境变量即可。

增加系统环境变量NEO4J_HOME;

修改path环境变量增加%NEO4J_HOME%\bin

cmd 窗口执行 :neo4j console 便可以启动数据库。

C:\Users\lich0>neo4j console
2021-02-24 07:24:33.778+0000 INFO  ======== Neo4j 3.5.26 ========
2021-02-24 07:24:33.795+0000 INFO  Starting...
2021-02-24 07:24:37.133+0000 INFO  Bolt enabled on 127.0.0.1:7687.
2021-02-24 07:24:38.110+0000 INFO  Started.
2021-02-24 07:24:38.840+0000 INFO  Remote interface available at http://localhost:7474/

Linux安装

下载安装包,解压后,简单修改配置文件就可以使用。云服务器的话需要放开对应的端口。

配置文件修改:

# 可以读写
dbms.read_only=false
# 可以远程访问
dbms.connectors.default_listen_address=0.0.0.0

启动服务

[root@VM-0-2-centos bin]# ./neo4j start
Active database: graph.db
Directories in use:
  home:         /opt/neo4j-community-3.5.26
  config:       /opt/neo4j-community-3.5.26/conf
  logs:         /opt/neo4j-community-3.5.26/logs
  plugins:      /opt/neo4j-community-3.5.26/plugins
  import:       /opt/neo4j-community-3.5.26/import
  data:         /opt/neo4j-community-3.5.26/data
  certificates: /opt/neo4j-community-3.5.26/certificates
  run:          /opt/neo4j-community-3.5.26/run
Starting Neo4j.
Started neo4j (pid 20669). It is available at http://localhost:7474/
There may be a short delay until the server is ready.
See /opt/neo4j-community-3.5.26/logs/neo4j.log for current status.

查看状态

[root@VM-0-2-centos bin]# ./neo4j status
Neo4j is running at pid 20669

关闭服务

[root@VM-0-2-centos bin]# ./neo4j stop
Stopping Neo4j.. stopped

访问服务即可:http://xxxxx:7474/browser/

Docker部署

拉取镜像,启动就行了呀

拉取镜像

[root@VM-0-2-centos ~]# docker pull neo4j:3.5.26
[root@VM-0-2-centos ~]# docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
docker.io/neo4j           3.5.26              27b84ff7b769        6 weeks ago         333 MB

启动容器,建议写个启动的脚本:neo4j_start.sh

这里需要注意脚本权限,映射的目录的访问权限

sudo docker run -it --name neo4j -e NEO4J_AUTH=neo4j/123456 -p 7474:7474 -p 7687:7687 \
-v /data/neo4j/data:/data \
-v /data/neo4j/neo4j/logs:/logs \
-v /data/neo4j/conf:/conf \
-v /data/neo4j/metrics:/metrics \
-v /data/neo4j/plugins:/plugins \
-v /data/neo4j/import:/import \
-d neo4j:3.5.26

查看启动的容器

[root@VM-0-2-centos neo4j]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                      NAMES
b92f8beb7d82        neo4j:3.5.26        "/sbin/tini -g -- ..."   3 minutes ago       Up 3 minutes        0.0.0.0:7474->7474/tcp, 7473/tcp, 0.0.0.0:7687->7687/tcp   neo4j

访问服务即可:http://xxxxx:7474/browser/

基本概念

Node 节点

图的基本单位主要是节点和关系,节点和关系都可以包含多个k-v结构的属性。

Relationships 关系

关系的主要功能就是连接节点,一个关系连接两个节点,一个开始和一个结束节点,这种关系是有进出方向的。

Properties 属性

属性就可以理解为关系型数据库中表中的所有字段了,节点和关系都可以有多个属性,并且属性的key为字符串,值可以是其他类型例如数值,布尔

Lables 标签

标签和ES的别名是一样的作用,可以用来划分一类的节点,查询的时候可以通过标签来分类查询。

Travelsal 遍历

查询的时候往往都是遍历图谱,遍历的过程中会有一个开始节点,然后根据Cypher语句,遍历相关的节点和关系得到最终的结果

Paths 路径

路径是一个或者多个节点通过关系连接起来,例如通过Cypher查询得到的结果

Schema 模式

neo4j是类似ES一样的无模式图谱数据库,使用它无需要定义schema

Indexes 索引

和关系型数据库中的索引一样为了加快检索的速度,这里构建索引是一个异步的过程,只有创建成功后才会生效。如果创建失败可以删除后从新创建。

Constraints 约束

约束可以定义在某个字段上,限制字段值唯一,创建约束会自动创建索引

Cypher

语句cypher是neo4j官网提供的声明式查询语言,非常强大,用它可以完成任意的图谱里面的查询过滤。就好像关系型数据库中的SQL语句一样

创建节点

创建电影相关的节点

-- 创建一个节点:黑客帝国; 标签:电影;属性:标题-The Matrix,发布年份-1997
CREATE (matrix:Movie { title:"The Matrix",released:1997 })
-- 下面的同理
CREATE (cloudAtlas:Movie { title:"Cloud Atlas",released:2012 })
CREATE (forrestGump:Movie { title:"Forrest Gump",released:1994 })

创建演员相关的节点

CREATE (keanu:Person { name:"Keanu Reeves", born:1964 }) 
CREATE (robert:Person { name:"Robert Zemeckis", born:1951 }) 
CREATE (tom:Person { name:"Tom Hanks", born:1956 })
CREATE (jerry:Person { name:"Jerry", born:1986 })

创建关系

-- Tom Hanks在电影Forrest Gump中出演Forrest角色
CREATE (tom)-[:ACTED_IN { roles: ["Forrest"]}]->(forrestGump) 
-- Tom Hanks在电影Forrest Gump中出演Forrest角色
CREATE (tom)-[:ACTED_IN { roles: ['Zachry']}]->(cloudAtlas) 
-- jerry 在电影 The Matrix 出演 Jerry角色
CREATE (jerry)-[:ACTED_IN { roles: ["J"]}]->(matrix)
-- Robert Zemeckis指导电影Forrest Gump
CREATE (robert)-[:DIRECTED]->(forrestGump)

注意:上面创建节点,关系的语句需要同时执行后,节点才会有关联关系,如下所示:

如果你的创建节点和创建关系的语句是分开执行的话,会是如下的结果:因为创建的关系的时候没有指定节点是属性会被认为是新的节点

如果是分开创建的话,创建关系的语句应该是如下所示:

-- Tom Hanks在电影Forrest Gump中出演Forrest角色
MATCH (tom:Person{name:"Tom Hanks"}),(forrestGump:Movie{title:"Forrest Gump"}) CREATE (tom)-[:ACTED_IN { roles: ["Forrest"]}]->(forrestGump) 

-- Tom Hanks在电影Forrest Gump中出演Forrest角色
MATCH (tom:Person{name:"Tom Hanks"}),(cloudAtlas:Movie{title:"Cloud Atlas"}) CREATE (tom)-[:ACTED_IN { roles: ['Zachry']}]->(cloudAtlas) 

-- jerry 在电影 The Matrix 出演 Jerry角色
MATCH (jerry:Person{name:"Jerry"}),(matrix:Movie{title:"The Matrix"}) CREATE (jerry)-[:ACTED_IN { roles: ["J"]}]->(matrix)

-- Robert Zemeckis指导电影Forrest Gump
MATCH (robert:Person{name:"Robert Zemeckis"}),(forrestGump:Movie{title:"Forrest Gump"}) CREATE (robert)-[:DIRECTED]->(forrestGump)

过滤查询

查询所有节点

MATCH (nodes) return nodes

结果:

╒════════════════════════════════════════╕
│"nodes"                                 │
╞════════════════════════════════════════╡
│{"title":"The Matrix","released":1997}  │
├────────────────────────────────────────┤
│{"title":"Cloud Atlas","released":2012} │
├────────────────────────────────────────┤
│{"title":"Forrest Gump","released":1994}│
├────────────────────────────────────────┤
│{"name":"Keanu Reeves","born":1964}     │
├────────────────────────────────────────┤
│{"name":"Robert Zemeckis","born":1951}  │
├────────────────────────────────────────┤
│{"name":"Tom Hanks","born":1956}        │
└────────────────────────────────────────┘

查询标签是Movie的节点

MATCH (m:Movie) return m

结果:

╒════════════════════════════════════════╕
│"m"                                     │
╞════════════════════════════════════════╡
│{"title":"The Matrix","released":1997}  │
├────────────────────────────────────────┤
│{"title":"Cloud Atlas","released":2012} │
├────────────────────────────────────────┤
│{"title":"Forrest Gump","released":1994}│
└────────────────────────────────────────┘

根据标题查询

-- 查询标题是The Matrix的节点
MATCH (m:Movie) WHERE m.title = "The Matrix" RETURN m

结果:

╒══════════════════════════════════════╕
│"m"                                   │
╞══════════════════════════════════════╡
│{"title":"The Matrix","released":1997}│
└──────────────────────────────────────┘

多个条件查询

-- 条件查询 和SQL语句一样
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) WHERE p.name =~ "K.+" OR m.released > 2000 OR "Neo" IN r.roles RETURN p,r,m

结果:

╒═════════════════════════════════════════════════════════════════════════════════════════════╕
│"p"                             │"r"                 │"m"                                    │
╞═════════════════════════════════════════════════════════════════════════════════════════════╡
│{"name":"Tom Hanks","born":1956}│{"roles":["Zachry"]}│{"title":"Cloud Atlas","released":2012}│
└─────────────────────────────────────────────────────────────────────────────────────────────┘

删除

删除所有节点和所有关系

MATCH (r)
DETACH DELETE r

根据ID删除节点及所有关系

MATCH (r)
WHERE id(r) = 493
DETACH DELETE r

结果增加列

和sql语句一样,可以给返回结果增加列

MATCH (p:Person)
RETURN p, p.name AS name, toUpper(p.name), coalesce(p.nickname,"n/a") AS nickname,
  { name: p.name, label:head(labels(p))} AS person

JSON结果:

[
  {
    "p": {
      "identity": 65,
      "labels": [
        "Person"
      ],
      "properties": {
        "name": "Keanu Reeves",
        "born": 1964
      }
    },
    "name": "Keanu Reeves",
    "toUpper(p.name)": "KEANU REEVES",
    "nickname": "n/a",
    "person": {
      "name": "Keanu Reeves",
      "label": "Person"
    }
  },
  {
    "p": {
      "identity": 66,
      "labels": [
        "Person"
      ],
      "properties": {
        "name": "Robert Zemeckis",
        "born": 1951
      }
    },
    "name": "Robert Zemeckis",
    "toUpper(p.name)": "ROBERT ZEMECKIS",
    "nickname": "n/a",
    "person": {
      "name": "Robert Zemeckis",
      "label": "Person"
    }
  },
  {
    "p": {
      "identity": 67,
      "labels": [
        "Person"
      ],
      "properties": {
        "name": "Tom Hanks",
        "born": 1956
      }
    },
    "name": "Tom Hanks",
    "toUpper(p.name)": "TOM HANKS",
    "nickname": "n/a",
    "person": {
      "name": "Tom Hanks",
      "label": "Person"
    }
  }
]

排序分页

演员按照出演电影的数量排序取前10个

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
RETURN a, count(*) AS appearances
ORDER BY appearances DESC SKIP 0 LIMIT 10;

结果:

╒════════════════════════════════╤═════════════╕
│"a"                             │"appearances"│
╞════════════════════════════════╪═════════════╡
│{"name":"Tom Hanks","born":1956}│2            │
├────────────────────────────────┼─────────────┤
│{"name":"Jerry","born":1986}    │1            │
└────────────────────────────────┴─────────────┘

聚合统计查询

查看演员的总数量

MATCH (:Person)
RETURN count(*) AS people

结果:

╒════════╕
│"people"│
╞════════╡
│4       │
└────────┘

统计出演电影的演员

MATCH (m:Movie)<-[:ACTED_IN]-(a:Person)
RETURN m.title AS movie, collect(a.name) AS cast, count(*) AS actors

结果:

╒══════════════╤═════════════╤════════╕
│"movie"       │"cast"       │"actors"│
╞══════════════╪═════════════╪════════╡
│"The Matrix"  │["Jerry"]    │1       │
├──────────────┼─────────────┼────────┤
│"Cloud Atlas" │["Tom Hanks"]│1       │
├──────────────┼─────────────┼────────┤
│"Forrest Gump"│["Tom Hanks"]│1       │
└──────────────┴─────────────┴────────┘

UNION / WITH

union 用来组合查询的结果,查询电影的演职人员表

MATCH (actor:Person)-[r:ACTED_IN]->(movie:Movie)
RETURN actor.name AS name, type(r) AS type, movie.title AS title
UNION
MATCH (director:Person)-[r:DIRECTED]->(movie:Movie)
RETURN director.name AS name, type(r) AS type, movie.title AS title

结果:

╒═════════════════╤══════════╤══════════════╕
│"name"           │"type"    │"title"       │
╞═════════════════╪══════════╪══════════════╡
│"Jerry"          │"ACTED_IN"│"The Matrix"  │
├─────────────────┼──────────┼──────────────┤
│"Tom Hanks"      │"ACTED_IN"│"Cloud Atlas" │
├─────────────────┼──────────┼──────────────┤
│"Tom Hanks"      │"ACTED_IN"│"Forrest Gump"│
├─────────────────┼──────────┼──────────────┤
│"Robert Zemeckis"│"DIRECTED"│"Forrest Gump"│
└─────────────────┴──────────┴──────────────┘

或者和下面的查询是等价的

MATCH (actor:Person)-[r:ACTED_IN|DIRECTED]->(movie:Movie)
RETURN actor.name AS name, type(r) AS type, movie.title AS title

WITH 关键字可以看成是查询的语句的一个子条件,主要是用来过滤数据用的,比如

查询出演次数大于一次的演员和相关的电影

MATCH (person:Person)-[:ACTED_IN]->(m:Movie)
WITH person, count(*) AS appearances, collect(m.title) AS movies
WHERE appearances > 1
RETURN person.name, appearances, movies

结果:

╒═════════════╤═════════════╤══════════════════════════════╕
│"person.name"│"appearances"│"movies"                      │
╞═════════════╪═════════════╪══════════════════════════════╡
│"Tom Hanks"  │2            │["Cloud Atlas","Forrest Gump"]│
└─────────────┴─────────────┴──────────────────────────────┘

Springboot 集成

测试版本:2.0.3.RELEASE

引入依赖


    org.springframework.boot
    spring-boot-starter-data-neo4j

配置文件application.yml

注意格式的正确

spring:
  data:
    neo4j:
      uri: bolt://localhost:7687
      username: neo4j
      password: 123456

定义节点、关系对象

(省略了Get/Set等方法)

/**
 * @author chenghao.li
 * @Description 电影节点
 */
@NodeEntity(label = "Movie")
public class MovieGraph implements Serializable {
    /**
     * 主键
     */
    @Id
    @GeneratedValue
    private Long id;

    /**
     * 电影标题
     */
    @Property(name = "title")
    private String title;

    /**
     * 发布年份
     */
    @Property(name = "released")
    private Integer released;
}
/**
 * @author chenghao.li
 * @Description 演员节点
 */
@NodeEntity(label = "People")
public class PeopleGraph implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    @Property(name = "name")
    private String name;

    @Property(name = "born")
    private Integer born;
}
/**
 * @author chenghao.li
 * @Description 出演关系
 */
@RelationshipEntity(type = "ACTED_IN")
public class ActedInRelationShip implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    @StartNode
    private PeopleGraph startNode;

    @EndNode
    private MovieGraph endNode;

    @Property(name = "roles")
    private String roles;
}
/**
 * @author chenghao.li
 * @Description 指导关系
 */
@RelationshipEntity(type = "DIRECTED")
public class DirectedRelationShip implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @StartNode
    private PeopleGraph startNode;

    @EndNode
    private MovieGraph endNode;
}

涉及到的一些注解说明:

@NodeEntity(label = "People") 表示图中的一个节点

@Id 主键ID,使用Long类型

@GeneratedValue 自动生成主键ID的值

@Property(name = "name") 表示图中节点的一个属性

@RelationshipEntity(type = "ACTED_IN") 表示图中连线的关系

定义Repository

以演员为例,继承Neo4jRepository 即可实现增删改查分页排序等操作;其他的都是如此

/**
 * @author chenghao.li
 */
@Repository
public interface PeopleRepository extends Neo4jRepository {

    /**
     * 根据姓名查找
     *
     * @param name 姓名
     * @return PeopleGraph
     */
    @Query("MATCH (p:People) where p.name={0} return p")
    PeopleGraph findByName(String name);

}

单元测试

@SpringBootTest
@RunWith(SpringRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Neo4jApplicationTests {

    @Autowired
    private PeopleRepository peopleRepository;
    @Autowired
    private MovieRepository movieRepository;
    @Autowired
    private ActedInRelationShipRepository actedInRelationShipRepository;
    @Autowired
    private DirectedRelationShipRepository directedRelationShipRepository;

    /**
     * 添加演员节点
     */
    @Test
    public void t1() {
        PeopleGraph peopleGraph1 = new PeopleGraph();
        peopleGraph1.setName("Keanu Reeves");
        peopleGraph1.setBorn(1964);

        PeopleGraph peopleGraph2 = new PeopleGraph();
        peopleGraph2.setName("Robert Zemeckis");
        peopleGraph2.setBorn(1951);

        PeopleGraph peopleGraph3 = new PeopleGraph();
        peopleGraph3.setName("Tom Hanks");
        peopleGraph3.setBorn(1956);

        PeopleGraph peopleGraph4 = new PeopleGraph();
        peopleGraph4.setName("Jerry");
        peopleGraph4.setBorn(1986);

        List peopleGraphList = new ArrayList<>();
        peopleGraphList.add(peopleGraph1);
        peopleGraphList.add(peopleGraph2);
        peopleGraphList.add(peopleGraph3);
        peopleGraphList.add(peopleGraph4);

        Iterable peopleGraphs = peopleRepository.saveAll(peopleGraphList);
        Assert.assertNotNull(peopleGraphs);
    }

    /**
     * 添加电影节点
     */
    @Test
    public void t2() {
        MovieGraph movieGraph1 = new MovieGraph();
        movieGraph1.setTitle("The Matrix");
        movieGraph1.setReleased(1997);

        MovieGraph movieGraph2 = new MovieGraph();
        movieGraph2.setTitle("Cloud Atlas");
        movieGraph2.setReleased(2012);

        MovieGraph movieGraph3 = new MovieGraph();
        movieGraph3.setTitle("Forrest Gump");
        movieGraph3.setReleased(1994);

        List movieGraphs = new ArrayList<>();
        movieGraphs.add(movieGraph1);
        movieGraphs.add(movieGraph2);
        movieGraphs.add(movieGraph3);

        Iterable movieGraphIterable = movieRepository.saveAll(movieGraphs);
        Assert.assertNotNull(movieGraphIterable);
    }

    /**
     * 添加关系
     */
    @Test
    public void t3() {

        // Tom Hanks在电影Forrest Gump中出演Forrest角色
        PeopleGraph tom_hanks = peopleRepository.findByName("Tom Hanks");
        MovieGraph forrest_gump = movieRepository.findByTitle("Forrest Gump");

        ActedInRelationShip tom_ActedIn_relationShip_forrest = new ActedInRelationShip();
        tom_ActedIn_relationShip_forrest.setStartNode(tom_hanks);
        tom_ActedIn_relationShip_forrest.setEndNode(forrest_gump);
        tom_ActedIn_relationShip_forrest.setRoles("Forrest");

        ActedInRelationShip save = actedInRelationShipRepository.save(tom_ActedIn_relationShip_forrest);
        Assert.assertNotNull(save);

        // Tom Hanks在电影Forrest Gump中出演Forrest角色
        MovieGraph cloud = movieRepository.findByTitle("Cloud Atlas");

        ActedInRelationShip tom_ActedIn_relationShip_Cloud = new ActedInRelationShip();
        tom_ActedIn_relationShip_Cloud.setStartNode(tom_hanks);
        tom_ActedIn_relationShip_Cloud.setEndNode(cloud);
        tom_ActedIn_relationShip_Cloud.setRoles("Zachry");
        ActedInRelationShip save1 = actedInRelationShipRepository.save(tom_ActedIn_relationShip_Cloud);
        Assert.assertNotNull(save1);

        // Robert Zemeckis指导电影Forrest Gump
        PeopleGraph robert_zemeckis = peopleRepository.findByName("Robert Zemeckis");
        DirectedRelationShip directedRelationShip = new DirectedRelationShip();
        directedRelationShip.setStartNode(robert_zemeckis);
        directedRelationShip.setEndNode(forrest_gump);
        DirectedRelationShip save2 = directedRelationShipRepository.save(directedRelationShip);
        Assert.assertNotNull(save2);

        // jerry 在电影 The Matrix 出演 J 角色
        PeopleGraph jerry = peopleRepository.findByName("Jerry");
        MovieGraph the_matrix = movieRepository.findByTitle("The Matrix");
        ActedInRelationShip jerry_ActedIn_relationShip_matrix = new ActedInRelationShip();
        jerry_ActedIn_relationShip_matrix.setStartNode(jerry);
        jerry_ActedIn_relationShip_matrix.setEndNode(the_matrix);
        jerry_ActedIn_relationShip_matrix.setRoles("J");
        actedInRelationShipRepository.save(jerry_ActedIn_relationShip_matrix);
    }
}

附报错信息

报错:检查下配置文件application.yml 是不是有多余的空格等格式问题

org.springframework.transaction.CannotCreateTransactionException: Could not open Neo4j Session for transaction; nested exception is org.neo4j.driver.v1.exceptions.AuthenticationException: Unsupported authentication token, scheme 'none' is only allowed when auth is disabled.