1. CQL函数

1.1 字符串函数

image.png

MATCH (p:Person) RETURN ID(p),LOWER(p.character)

match(p:Person) return p.character,lower(p.character),p.name,substring(p.name,2),replace(p.name,"子","z i")

1.2 聚合函数

image.png

MATCH (p:Person) RETURN MAX(p.money),SUM(p.money)

1.3 关系函数

image.png

match p = (:Person {name:"林婉儿"})-[r:Couple]-(:Person) 
RETURN STARTNODE(r)

1.4 shortestPath函数返回最短的path

MATCH p=shortestPath((node1)-[*]-(node2)) RETURN length(p), nodes(p)
MATCH p=shortestPath((person:Person {name:"王启年"})-[*]-(person2:Person {name:"九品射手燕小乙"})) RETURN length(p), nodes(p)

2 CQL多深度关系节点

1.使用with关键字
查询三层级关系节点如下:with可以将前面查询结果作为后面查询条件
match (na:Person)-[re]->(nb:Person) where na.name="范闲" WITH na,re,nb match (nb:Person)- [re2]->(nc:Person) return na,re,nb,re2,nc

match (na:Person)-[re]->(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)- [re2]->(nc:Person) return na,re,nb,re2,nc match (na:Person)-[re]-(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)- [re2]->(nc:Person) return na,re,nb,re2,nc match (na:Person)-[re]-(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)- [re2:Friends]->(nc:Person) return na,re,nb,re2,nc

2.直接拼接关系节点查询
match (na:Person{name:"范闲"})-[re]->(nb:Person)-[re2]->(nc:Person) return na,re,nb,re2,nc
为了方便,可以将查询结果赋给变量,然后返回
match data=(na:Person{name:"范闲"})-[re]->(nb:Person)-[re2]->(nc:Person) return data

3.使用深度运算符
当实现多深度关系节点查询时,显然使用以上方式比较繁琐。
可变数量的关系->节点可以使用-[:TYPEminHops..maxHops]-。
查询:
match data=(na:Person{name:”范闲”})-[
1..2]-(nb:Person) return data

3. 事务

为了保持数据的完整性和保证良好的事务行为,Neo4j也支持ACID特性 。
注意:

(1)所有对Neo4j数据库的数据修改操作都必须封装在事务里。 
(2)默认的isolation level是READ_COMMITTED。
(3)死锁保护已经内置到核心事务管理 。 (Neo4j会在死锁发生之前检测死锁并抛出异常。在异常抛出之 前,事务会被标志为回滚。当事务结束时,事务会释放它所持有的锁,则该事务的锁所引起的死锁也就是解 除,其他事务就可以继续执行。当用户需要时,抛出异常的事务可以尝试重新执行) 
(4)除特别说明,Neo4j的API的操作都是线程安全的,Neo4j数据库的操作也就没有必要使用外部的同步方 法。

4.索引

4.1 简介

Neo4j CQL支持节点或关系属性上的索引,以提高应用程序的性能。
可以为具有相同标签名称的属性上创建索引。
可以在MATCH或WHERE等运算符上使用这些索引列来改进CQL的执行。

4.2 创建单一索引

CREATE INDEX ON :Label(property)
例如:
CREATE INDEX ON :Person(name)

4.3 创建复合索引

CREATE INDEX ON :Person(age, gender)

4.4 全文模式索引

之前的常规模式索引只能对字符串进行精确匹配或者前后缀索引(startswith,endswith,contains),全文索引将标记化索引字符串值,因此它可以匹配字符串中任何位置的术语。索引字符串如何被标记化并分解为术语,取决于配置全文模式索引的分析器。索引是通过属性来创建,便于快速查找节点或者关系。

 创建和配置全文模式索引
 使用db.index.fulltext.createNodeIndex和db.index.fulltext.createRelationshipIndex创建全文模式索引。在创建索引时,每个索引必须为每个索引指定一个唯一的名称,用于在查询或删除索引时引用相关的特定索引。然后,全文模式索引分别应用于标签列表或关系类型列表,分别用于节点和关系索引,然后应用于属性名称列表。
call db.index.fulltext.createNodeIndex("索引名",[Label,Label],[属性,属性]) 
call db.index.fulltext.createNodeIndex("nameAndDescription",["Person"],["name", "description"])
call db.index.fulltext.queryNodes("nameAndDescription", "范闲") YIELD node, score RETURN node.name, node.description, score

4.5 查看和删除索引

call db.indexes或者:schema DROP INDEX ON :Person(name) DROP INDEX ON :Person(age, gender) call db.index.fulltext.drop("nameAndDescription")

5. 约束

5.1 唯一性约束

作用
避免重复记录。
强制执行数据完整性规则
创建唯一性约束
CREATE CONSTRAINT ON (变量:<label_name>) ASSERT变量.<property_name> IS UNIQUE
具体实例:
CREATE CONSTRAINT ON (person:Person) ASSERT person.name IS UNIQUE
删除唯一性约束
DROP CONSTRAINT ON (cc:Person) ASSERT cc.name IS UNIQUE

5.2 属性存在约束(企业版中可用)

CREATE CONSTRAINT ON (p:Person) ASSERT exists(p.name)

5.3 查看约束

call db.constraints :schema

6. Neo4j之Admin管理员操作

6.1 Neo4j-数据库备份和恢复

在对Neo4j数据进行备份、还原、迁移的操作时,首先要关闭neo4j

./bin/neo4j stop

数据备份到文件
./bin/neo4j-admin dump --database=graph.db --to=/root/qyn.dump

还原、迁移之前 ,关闭neo4j服务。操作同上
./bin/neo4j-admin load --from=/root/qyn.dump --database=graph.db --force

重启服务
./bin/neo4j start

注意,运行数据备份可能会警告
WARNING: Max 1024 open fifiles allowed, minimum of 40000 recommended. See the Neo4j manual
1.编辑这个文件
vi /etc/security/limits.conf
在文件最后加入下面这段 修改最大打开文件限制
* soft nofile 65535
* hard nofile 65535

2.重启服务器
再次执行上面的步骤 警告就没有了

6.2 调优思路

1.增加服务器内存 和 调整neo4j配置文件

# java heap 初始值
dbms.memory.heap.initial_size=1g
# java heap 最大值,一般不要超过可用物理内存的80% 
dbms.memory.heap.max_size=16g 
# pagecache大小,官方建议设为:(总内存-dbms.memory.heap.max_size)/2, 
dbms.memory.pagecache.size=2g

2.neo4j刚启动数据是冷的需要预热
MATCH (n) 
OPTIONAL MATCH (n)-[r]->() 
RETURN count(n.name) + count(r);

3.查看执行计划进行索引优化
Cypher查询计划程序将每个查询转换为执行计划。 执行计划告诉Neo4j在执行查询时要执行哪些操作。

对执行计划的生成,Neo4j使用的都是基于成本的优化器(Cost Based Optimizer,CBO),用于制订精确的执行过程。可以采用如下两种不同的方式了解其内部的工作机制:
EXPLAIN:是解释机制,加入该关键字的Cypher语句可以预览执行的过程但并不实际执行,所以也不会产生任何结果。

PROFILE:则是画像机制,查询中使用该关键字,不仅能够看到执行计划的详细内容,也可以看到查询的执行结果。

关注指标:
   estimated rows: 需要被扫描行数的预估值 
   dbhits: 实际运行结果的命中绩效
   两个值都是越小越好

使用索引和不使用索引对比
MATCH (p { name : '范闲' }) RETURN p
在之前加上profifile来进行查询,可以查看查询计划

7. Neo4j数据访问

7.1 Neo4j访问的两种方式

  • 嵌入式数据库
  • 服务器模式(通过REST的访问)

它是由应用程序的性质(neo4j是独立服务器 还是和程序在一起),性能,监控和数据安全性来决定架构
选择。

(1)An embedded database(嵌入式数据库)
嵌入式Neo4j数据库是性能的最佳选择。 通过指定数据存储的路径以编程方式访问嵌入式数据库。
我们选择嵌入式数据库出于以下原因:

  • 使用Java作为我们项目的编程语言时
  • 应用程序是独立的
  • 程序追求很高的性能

(2)Neo4j Server(服务器模式)
Neo4j Server是相互操作性,安全性和监控的最佳选择。 实际上,REST接口允许所有现代平台和编程 语言与它进行互操作。 此外,作为独立应用程序,它比嵌入式配置更安全(客户端中的潜在故障不会影响服务器),并且更易于监控。 如果我们选择使用这种模式,我们的应用程序将充当Neo4j服务器的客户端。要连接到Neo4j服务器,可以使用任何编程语言的REST 访问数据库。

7.2 Java客户端操作Neo4j

1.嵌入式模式

<dependency> 
  <groupId>org.neo4j</groupId> 
  <artifactId>neo4j</artifactId>
  <version>3.5.5</version> 
</dependency>

package org.example;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;

import java.io.File;

/**
 * Hello world!
 *
 */
public class App 
{
    private static final File DATABASE_DIRECTORY = new File("target/graph.db");
    public static void main( String[] args )
    {
        //创建数据库服务对象
        GraphDatabaseService graphDatabaseService = new GraphDatabaseFactory().newEmbeddedDatabase(DATABASE_DIRECTORY);
        System.out.println("database load!");
        //获取事务对象 开启事务
        Transaction tx  = graphDatabaseService.beginTx();
        Node n1 = graphDatabaseService.createNode();
        n1.setProperty("name", "zhangsan");
        n1.setProperty("age", 23);
        n1.setProperty("money", 1234);
        n1.setProperty("character", 'A');
        n1.addLabel(new Label() {
            @Override
            public String name() {
                return "Person";
            }
        });
        //定义CQL
        String cql = "CREATE(p:Person {name:'李四', character:'B',money:2334})";
        graphDatabaseService.execute(cql);
        tx.success();
        tx.close();
        graphDatabaseService.shutdown();
        System.out.println( "Hello World! shutdown" );
    }
}

package org.example;

import org.neo4j.cypher.internal.v3_5.ast.Where;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/17 10:31
 * @description:
 * @modified By:
 * @version: $
 */
public class EmbeddedNeo4jQueryAll {
    private static final File DATABASE_DIRECTORY = new File("target/graph.db");
    public static void main(String[] args) {
        //创建数据库服务对象
        GraphDatabaseService graphDatabaseService = new GraphDatabaseFactory().newEmbeddedDatabase(DATABASE_DIRECTORY);
        System.out.println("database load!");
        //定义CQL
        String cql = "MATCH(p:Person) where p.money <$money return p";
        Map<String,Object> stringObjectMap = new HashMap<>();
        stringObjectMap.put("money",25000);
        //获取事务对象 开启事务
        Transaction tx  = graphDatabaseService.beginTx();

        final Result result = graphDatabaseService.execute(cql, stringObjectMap);
        while (result.hasNext()){
            Map<String,Object> row = result.next();
            for(String key : result.columns()){
                Node nd = (Node)row.get(key);
                System.out.printf("%s=%s:%s%n", key, nd.getProperties("name"), nd.getProperties("money"));
            }
        }
        tx.success();
        tx.close();
        graphDatabaseService.shutdown();
        System.out.println( "Hello World! shutdown" );
    }
}

2.服务器模式
<dependency>
           <groupId>org.neo4j</groupId>
           <artifactId>neo4j-ogm-bolt-driver</artifactId>
           <version>3.2.10</version>
       </dependency>

package org.example;

import org.neo4j.driver.*;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        Driver driver = GraphDatabase.driver("bolt://192.168.2.118:7687", AuthTokens.basic("neo4j", "123456"));
        //获取会话对象
        final Session session = driver.session();
        String cql = "MATCH(p:Person) WHERE p.money>$money return p.money AS money, p.name AS name order by p.money";
        final Result result = session.run(cql, Values.parameters("money", 1000));
        while ( result.hasNext() ) {
            Record record = result.next();
            System.out.println( record.get( "name" ).asString() + " " + record.get( "money" ).asDouble() ); }
        System.out.println( "Hello World!" );
    }
}

package org.example;

import org.neo4j.driver.*;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        Driver driver = GraphDatabase.driver("bolt://192.168.2.118:7687", AuthTokens.basic("neo4j", "123456"));
        //获取会话对象
        final Session session = driver.session();
//        String cql = "MATCH(p:Person) WHERE p.money>$money return p.money AS money, p.name AS name order by p.money";
//        final Result result = session.run(cql, Values.parameters("money", 1000));
//        while ( result.hasNext() ) {
//            Record record = result.next();
//            System.out.println( record.get( "name" ).asString() + " " + record.get( "money" ).asDouble() ); }
//

        String  cql = "MATCH p=shortestPath((person:Person {name:$startName})-[*]-(person2:Person {name:$endName})) RETURN p";
        final Result result = session.run( cql, Values.parameters("startName","王启年","endName","九品射手燕小乙") );
        while (result.hasNext()) {
            Record record = result.next();
            System.out.println(record);
        }
        System.out.println( "Hello World!" );
    }
}

7.3 SpringBoot 整合Neo4j

image.png
1.导入jar包

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-neo4j-demo</artifactId>

    <name>spring-boot-neo4j-demo</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.neo4j</groupId>
            <artifactId>neo4j-ogm-bolt-driver</artifactId>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2.建立实体类

package org.example.pojo;

import org.neo4j.ogm.annotation.*;

import java.util.Set;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/17 10:59
 * @description:
 * @modified By:
 * @version: $
 */
@NodeEntity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    @Property("cid")
    private int pid;
    @Property
    private String name;
    private String character;
    private double money;
    private int gender;
    private int age;
    private String description;

    public Person() {
    }

    @Relationship(type = "Friends",direction = Relationship.INCOMING)
    private Set<Person> relationPersons;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCharacter() {
        return character;
    }

    public void setCharacter(String character) {
        this.character = character;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Set<Person> getRelationPersons() {
        return relationPersons;
    }

    public void setRelationPersons(Set<Person> relationPersons) {
        this.relationPersons = relationPersons;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", pid=" + pid +
                ", name='" + name + '\'' +
                ", character='" + character + '\'' +
                ", money=" + money +
                ", gender=" + gender +
                ", age=" + age +
                ", description='" + description + '\'' +
                ", relationPersons=" + relationPersons +
                '}';
    }

    public Person(Long id, int pid, String name, String character, double money, int gender, int age, String description, Set<Person> relationPersons) {
        this.id = id;
        this.pid = pid;
        this.name = name;
        this.character = character;
        this.money = money;
        this.gender = gender;
        this.age = age;
        this.description = description;
        this.relationPersons = relationPersons;
    }
}

3.数据持久化类
package org.example.repository;

import org.example.pojo.Person;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/17 11:04
 * @description:
 * @modified By:
 * @version: $
 */
@Repository
public interface PersonRepository extends Neo4jRepository<Person, Long> {
    @Query("match(p:Person) where p.money > {0} return p")
    List<Person> personList(double money);
    @Query("MATCH p=shortestPath((person:Person {name:{0}})-[*1..4]- (person2:Person {name:{1}}) ) RETURN p")
    List<Person> shortestPath(String startName, String endName);


//    @Query("match(p:Person) where p.money>{money} return p")
//    List<Person> personList(@Param("money") double money);
//    /** 指定开始的名字 和 结束的名字 查询最短路径 限定深度为4以层包含4*/ 
//    @Query("match p=shortestPath((person:Person{name:{startName}})-[*1..4]- (person2:Person {name:{endName}})) return p") 
//    List<Person> shortestPath(@Param("startName") String startName,@Param("endName") String endName);
}

4. 配置文件 application.yml
spring:
  data:
    neo4j:
      username: neo4j
      password: 123456
      uri: bolt://192.168.2.118:7687
      #uri: http://192.168.2.118:7474 
      #uri: file:///target/graph.db

5.编写服务类
package org.example.service;

import org.example.pojo.Person;
import org.example.repository.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/17 11:06
 * @description:
 * @modified By:
 * @version: $
 */
@Service("personService")
public class Neo4jPersonService {
    @Autowired
    private PersonRepository personRepository;

    public List<Person> getAll(){
        List<Person> datas = new ArrayList<>();
        personRepository.findAll().forEach(x->datas.add(x));

        return datas;
    }

    public Person save(Person person){
        return personRepository.save(person);
    }

    public List<Person> personList(double money){
        return    personRepository.personList(money);
    }

    public   List<Person> shortestPath(String startName, String endName){
        return personRepository.shortestPath(startName,endName);
    }
}

6.编写测试类
package org.example;

import org.example.pojo.Person;
import org.example.service.Neo4jPersonService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.List;

/**
 * Hello world!
 *
 */
@SpringBootApplication
public class App 
{

    public static void main( String[] args )
    {
        final ApplicationContext context = SpringApplication.run(App.class, args);
        final Neo4jPersonService personService = context.getBean(Neo4jPersonService.class);
//        Person person = new Person();
//        person.setName("王五");
//        person.setMoney(23444);
//        person.setAge(34);
//        person.setCharacter("C");
//        final Person save = personService.save(person);
//        System.out.println(save);


        List<Person> datas = personService.personList(1000);
        System.out.println(datas);
        System.out.println(personService.shortestPath("王启年", "九品射手燕小乙"));
    }
}