触发器

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

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

有以下几种方法可以实现

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

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

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

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

安装、更新或删除触发器是一个最终一致性操作。因此,它们不会立即添加/更新/删除,而是有一个由 APOC 配置 apoc.trigger.refresh=<MILLISECONDS> 控制的刷新频率,默认值为 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

可用参数有

语句 描述

transactionId

返回事务的 ID。

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

commitTime

返回事务的日期(毫秒)

createdNodes

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

createdRelationships

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

deletedNodes

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

deletedRelationships

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

removedLabels

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

removedNodeProperties

当节点属性移除时触发(键到键、旧值、节点映射列表的映射)

removedRelationshipProperties

当关系属性移除时触发(键到键、旧值、关系映射列表的映射)

assignedLabels

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

assignedNodeProperties

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

assignedRelationshipProperties

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

metaData

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

阶段参数

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

表 2. 触发器阶段表

阶段

描述

before

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

rollback

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

after

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

afterAsync

触发器将在提交之后激活,并在新的事务和线程中运行,不会影响原始事务。繁重操作应在此阶段处理,而不会阻塞原始事务。请注意,“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. 结果
名称 查询 选择器 参数 已安装 已暂停

"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

© . All rights reserved.