触发器

触发器允许注册 Cypher 查询,当 Neo4j 中的数据发生变更(创建、更新、删除)时触发。触发器可以在事务提交前或提交后运行。

apoc.trigger.* 过程旨在系统数据库中执行,因此必须通过打开系统数据库会话来执行。

有几种实现方法:

  • 使用 Cypher-shell 或 Neo4j Browser 时,在 Cypher 查询前加上 :use system

  • 使用 Fabric 时,在 Cypher 查询前加上 USE system

  • 使用驱动程序时,直接针对系统数据库打开会话

此外,apoc.trigger 过程接受数据库名称作为第一个参数,在该数据库中安装、更新或删除触发器。

安装、更新或删除触发器是最终一致性操作。因此,它们不会立即被添加/更新/删除,而是具有由 APOC 配置 apoc.trigger.refresh=<毫秒数> 处理的刷新率,默认值为 60000(毫秒)。

默认情况下,触发器是禁用的。我们可以通过在 apoc.conf 中设置以下属性来启用它们:

apoc.conf
apoc.trigger.enabled=true
apoc.trigger.refresh=60000
表 1. 说明
选项键 描述

apoc.trigger.enabled

true/false,默认 false

启用/禁用该功能

apoc.trigger.refresh

数字,默认 60000

触发集群节点间复制检查的间隔(毫秒)

限定名称 类型

apoc.trigger.drop
apoc.trigger.drop(databaseName STRING, name STRING) - 最终删除给定的触发器。

过程

apoc.trigger.dropAll
apoc.trigger.dropAll(databaseName STRING) - 最终从给定数据库中删除所有触发器。

过程

apoc.trigger.install
apoc.trigger.install(databaseName STRING, name STRING, statement STRING, selector MAP<STRING, ANY>, config MAP<STRING, ANY>) - 最终为给定数据库添加一个触发器,该触发器在成功发生事务时调用。

过程

apoc.trigger.list
apoc.trigger.list() - 列出当前为会话数据库安装的所有触发器。

过程

apoc.trigger.show
apoc.trigger.show(databaseName STRING) - 列出为数据库最终安装的所有触发器。

过程

apoc.trigger.start
apoc.trigger.start(databaseName STRING, name STRING) - 最终重新启动给定的暂停触发器。

过程

apoc.trigger.stop
apoc.trigger.stop(databaseName STRING, name STRING) - 最终停止给定的触发器。

过程

来自 Neo4j 的事务数据被转换为适当的数据结构,作为语句的参数(例如 $createdNodes)进行消费。

可用的参数有:

Statement 描述

transactionId

返回事务的 ID。

注意,此值仅适用于“after”和“afterAsync”阶段(参见 触发器阶段表)。否则,其值为 -1

commitTime

返回事务提交时间的毫秒数

createdNodes

当创建节点时触发(节点列表)

createdRelationships

当创建关系时触发(关系列表)

deletedNodes

当删除节点时触发(节点列表)

deletedRelationships

当删除关系时触发(关系列表)

removedLabels

当删除标签时触发(标签到节点列表的映射)

removedNodeProperties

当删除节点属性时触发(键到 [键, 旧值, 节点] 映射列表的映射)

removedRelationshipProperties

当删除关系属性时触发(键到 [键, 旧值, 关系] 映射列表的映射)

assignedLabels

当分配标签时触发(标签到节点列表的映射)

assignedNodeProperties

当分配节点属性时触发(键到 [键, 旧值, 新值, 节点] 映射列表的映射)

assignedRelationshipProperties

当分配关系属性时触发(键到 [键, 旧值, 新值, 关系] 映射列表的映射)

metaData

包含该事务元数据的映射。事务元数据可以在客户端设置,例如通过 /docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/TransactionConfig.html#metadata()

阶段参数

apoc.trigger.install() 的第三个参数是一个映射 {phase: PHASE},其中 PHASE 是一个字符串,可以是以下值之一:

表 2. 触发器阶段表

阶段 (PHASE)

描述

before

触发器将在提交 before(之前)激活。如果未指定阶段,则使用默认阶段。

rollback

触发器将在 rollback(回滚)后立即激活。

after

触发器将在提交 after(之后)激活。

afterAsync

触发器将在提交 after(之后)且在新的事务和线程中激活,不会影响原始事务。繁重的操作应在此阶段处理,而不阻塞原始事务。请注意,'after' 和 'before' 阶段有时会阻塞事务,因此通常首选 afterAsync 阶段。

与旧版本 Neo4j 不同,在 Neo4j 5 中无法修改在 “after” 阶段创建的实体。例如,以下查询将返回异常,消息为 can’t acquire ExclusiveLock…​

CALL apoc.trigger.install('neo4j', 'name',
  "UNWIND $createdNodes AS n SET n.txId = $transactionId'",
  {phase:'after'}
);
CREATE (f:Baz);

相反,必须使用其他阶段或仅执行读取操作。

触发器示例

设置与节点关联的属性

可以添加一个触发器,当在节点上设置特定属性时,该触发器会将相同的属性添加到连接到该节点的所有节点上。

数据集(在默认数据库 'neo4j' 中)

CREATE (d:Person {name:'Daniel', surname: 'Craig'})
CREATE (l:Person {name:'Mary', age: 47})
CREATE (t:Person {name:'Tom'})
CREATE (j:Person {name:'John'})
CREATE (m:Person {name:'Michael'})
CREATE (a:Person {name:'Anne'})
CREATE (l)-[:DAUGHTER_OF]->(d)
CREATE (t)-[:SON_OF]->(d)
CREATE (t)-[:BROTHER]->(j)
CREATE (a)-[:WIFE_OF]->(d)
CREATE (d)-[:SON_OF]->(m)
CREATE (j)-[:SON_OF]->(d)
apoc.trigger.add.setAllConnectedNodes.dataset

使用上述数据集,如果添加了触发器并执行以下查询:MATCH (n:Person) WHERE n.name IN ['Daniel', 'Mary'] SET n.age=55, n.surname='Quinn',触发器语句中使用的 $assignedNodeProperties 将如下所示(其中 NODE(1)(:Person {name: 'Daniel'}),而 NODE(2)(:Person {name: 'Mary'})

{
   age: [{
         node : NODE(1),
         new: 55,
         old: null,
         key: "age"
      },
      {
         node: NODE(2),
         new: 55,
         old: 47,
         key: "age"
      }],

   surname: [{
         node: NODE(1),
         new: "Quinn",
         old: "Craig",
         key: "surname"
      },
      {
         node: NODE(2),
         new: "Quinn",
         old: null,
         key: "surname"
      }]
}

结果是一个映射,其中键是已分配的属性,值是涉及的实体列表。列表中的每个元素包含节点本身、已更改属性的新值、旧值(如果属性不存在则为 null)以及属性名的键。

$removedNodeProperties 参数具有相同的结构和逻辑(在这种情况下,new 值将始终为 null)。

assignedRelationshipPropertiesremovedRelationshipProperties 的逻辑相同,唯一的区别在于 node: NODE(n) 键被替换为 relationship: RELATIONSHIP(n) 键。

例如,以下语句创建了一个触发器,对于每个 SET,都会使用当前日期更新 timelasts 两个属性:

CALL apoc.trigger.install('neo4j', 'setLastUpdate',
  "
    UNWIND keys($assignedNodeProperties) AS k
    UNWIND $assignedNodeProperties[k] AS map
    WITH map.node AS node, collect(map.key) AS propList
    MATCH (n)
    WHERE id(n) = id(node) AND NOT 'lasts' in propList // to prevent loops
    SET n.time = date(),  n.lasts = propList
  ",
  {phase: 'afterAsync'});

请注意,apoc.trigger.install 以及 apoc.trigger.dropapoc.trigger.dropAllapoc.trigger.stopapoc.trigger.start 必须在系统数据库中执行。

在上面的例子中,使用 MATCH (n) WHERE id(n) = id(node) 是为了演示节点先通过 ID 找到,然后再设置参数。然而,更高效的做法是删除此命令,改为将倒数第二行修改为:SET node.time = date(), node.lasts = propList。注意,必须添加 AND NOT 'lasts' IN propList 条件以防止无限循环,因为 SET 命令将再次触发此查询。

在经过由 apoc.trigger.refresh 配置定义的时间后,可以执行以下查询:

MATCH (n:Person {name: 'Daniel'}) set n.age = 123, n.country = 'Italy'

执行

MATCH (n:Person {name: 'Daniel'}) return n

可以设置属性 time 为今天的日期,并将 lasts=['country','age']

surname 属性添加到节点时,它也会被添加到所有连接到它的节点上(此例为一级深度)。

MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
在新节点上创建关系

要添加一个触发器,将每个标签为 Actor 的新节点连接起来并为 name 属性分配特定值,请运行以下查询:

CALL apoc.trigger.install('neo4j','create-rel-new-node',
  "
    UNWIND $createdNodes AS n
    MATCH (m:Movie {title:'Matrix'})
    WHERE n:Actor AND n.name IN ['Keanu Reeves','Laurence Fishburne','Carrie-Anne Moss']
    CREATE (n)-[:ACT_IN]->(m)
  ",
  {phase:'before'}
)
CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (a:Actor {name:'Tom Hanks'})
CREATE (m:Movie {title:'Matrix'})
apoc.trigger.add.create rel new node
访问已删除节点的属性

在带有 $deletedRelationships$deletedNodes 的触发器查询中,无法使用 Cypher 函数 properties() 获取已删除实体的属性,因为已删除的实体表示为虚拟节点和虚拟关系。相反,触发器查询必须使用 apoc.any.properties 函数来检索已删除节点和关系的属性。

CALL apoc.trigger.install('neo4j', 'createLogNodeOnDelete',
  "
    UNWIND $deletedNodes as deletedNode
    CREATE (log:Log) (1)
    SET log:$(labels(deletedNode)) (2)
    SET log += apoc.any.properties(deletedNode)  (3)
  ",
  {phase: 'afterAsync'});
1 为每个已删除的节点创建一个标签为 Log 的节点。
2 使用 动态标签 将已删除节点的标签添加到 Log 节点。注意:labels 是分配给已删除节点的。
3 使用 apoc.any.properties 函数将已删除节点的属性添加到 Log 节点。

有了这个触发器,已删除节点的标签和属性将被分配给 Log 节点,如下所示:

创建一个 Person 节点
CREATE (d:Person:Actor {name:'Daniel', surname: 'Craig'})
删除该 Person 节点
MATCH (n:Person {name: "Daniel"})
DETACH DELETE n
Log 节点返回关于已删除节点的信息
MATCH (n:Log) RETURN n
表 3. 结果
n

(:Log:Person:Actor {surname: "Craig",name: "Daniel"})

防止事务阻塞

为防止某些事务锁,通常建议使用 afterAsync 阶段。这将阻止查询无限期挂起。

暂停触发器

请注意,apoc.trigger.stopapoc.trigger.start 过程是最终一致的。因此,需要等待一段设置好的时间,以便更改能够传播。等待时间由 apoc.trigger.refresh 配置定义。

要暂停触发器而不将其删除(以便后续使用),请使用以下过程:

apoc.trigger.stop
恢复暂停的触发器

要恢复已暂停的触发器,请使用以下过程:

apoc.trigger.start
可选参数

添加 \{params: {parameterMaps}} 以插入附加参数。

CALL apoc.trigger.install('neo4j', 'timeParams',
  "UNWIND $createdNodes AS n SET n.time = $time",
  {}, {params: {time: timestamp()}}
);
其他示例
CALL apoc.trigger.install('neo4j', 'timestamp',
  "UNWIND $createdNodes AS n SET n.ts = timestamp()",
  {});
CALL apoc.trigger.install('neo4j', 'lowercase',
  "UNWIND $createdNodes AS n SET n.id = toLower(n.name)",
  {});
CALL apoc.trigger.install('neo4j', 'txInfo',
  "UNWIND $createdNodes AS n SET n.txId = $transactionId, n.txTime = $commitTime",
  {phase:'after'});
CALL apoc.trigger.install('neo4j', 'count-removed-rels',
  "MATCH (c:Counter) SET c.count = c.count + size([r IN $deletedRelationships WHERE type(r) = "X"])",
  {})

删除触发器

要在 'neo4j' 数据库中删除名为 'test' 的触发器,请运行以下查询:

CALL apoc.trigger.drop('neo4j', 'test')

要删除 'neo4j' 数据库中的所有触发器,请运行以下查询:

CALL apoc.trigger.dropAll('neo4j')

触发器列表

可以返回数据库中触发器的完整列表。例如,如果创建了以下查询中的触发器:

CALL apoc.trigger.install('neo4j', 'count-removals',
  "MATCH (c:Counter) SET c.count = c.count + size([f IN $deletedNodes WHERE id(f) > 0])",
  {})

然后可以运行(同样,在经过 apoc.trigger.refresh 配置定义的时间之后):

CALL apoc.trigger.show('neo4j')
表 4. 结果
名称 (name) query selector params installed paused

"count-removals"

MATCH (c:Counter) SET c.count = c.count + size([f IN $deletedNodes WHERE id(f)  0])

{}

{}

TRUE

FALSE

请注意,由于触发器操作是最终一致的(基于 apoc.trigger.refresh 配置),apoc.trigger.show 可能会返回一些尚未添加/更新/删除的触发器。要获取当前已安装触发器的完整列表,请针对会话数据库(上述示例中为 "neo4j")使用 apoc.trigger.list