跳到内容

模式和数据建模

每个JanusGraph图都有一个由其中使用的边标签、属性键和顶点标签组成的Schema。JanusGraph Schema可以显式或隐式定义。建议用户在应用程序开发期间显式定义图Schema。显式定义的Schema是健壮的图应用程序的重要组成部分,并大大提高了协作软件开发效率。请注意,JanusGraph Schema可以随着时间的推移而演变,而不会中断正常的数据库操作。扩展Schema不会减慢查询响应速度,也不需要数据库停机。

Schema类型——即边标签、属性键或顶点标签——在图中的元素首次创建时被分配给它们——即边、属性或顶点。一旦分配,特定元素的Schema类型不能更改。这确保了一个稳定且易于理解的类型系统。

除了本节中解释的Schema定义选项之外,Schema类型还提供性能调优选项,这些选项在高级Schema中讨论。

显示Schema信息

在管理API中,有方法可以查看图Schema的特定元素。这些方法是mgmt.printIndexes()mgmt.printPropertyKeys()mgmt.printVertexLabels()mgmt.printEdgeLabels()。还有一个名为printSchema()的方法,可以显示所有这些方法的组合输出。

mgmt = graph.openManagement()
mgmt.printSchema()

定义边标签

连接两个顶点的每条边都有一个标签,用于定义关系语义。例如,顶点 A 和 B 之间标记为 friend 的边编码了两个人之间的友谊。

要定义边标签,请在开放的图或管理事务上调用 makeEdgeLabel(String),并将边标签的名称作为参数。边标签名称在图中必须是唯一的。此方法返回一个边标签构建器,允许定义其多重性。边标签的 多重性 定义了该标签所有边的多重性约束,即一对顶点之间边的最大数量。JanusGraph 识别以下多重性设置。

边标签多重性

  • MULTI:允许任何一对顶点之间存在多个相同标签的边。换句话说,图对于此类边标签而言是多重图。对边多重性没有约束。
  • SIMPLE:允许任何一对顶点之间最多只有一条此类标签的边。换句话说,图对于该标签而言是简单图。确保给定标签和顶点对的边是唯一的。
  • MANY2ONE:允许图中任何顶点上最多有一条此类标签的出边,但对入边没有限制。边标签mother是MANY2ONE多重性的一个例子,因为每个人最多只有一个母亲,但母亲可以有多个孩子。
  • ONE2MANY:允许图中任何顶点上最多有一条此类标签的入边,但对出边没有限制。边标签winnerOf是ONE2MANY多重性的一个例子,因为每个比赛最多只有一个人获胜,但一个人可以赢得多个比赛。
  • ONE2ONE:允许图中任何顶点上最多有一条此类标签的入边和一条出边。边标签marriedTo是ONE2ONE多重性的一个例子,因为一个人只与另一个人结婚。

默认多重性为 MULTI。边标签的定义通过调用构建器上的 make() 方法完成,该方法返回定义的边标签,如以下示例所示。

mgmt = graph.openManagement()
follow = mgmt.makeEdgeLabel('follow').multiplicity(MULTI).make()
mother = mgmt.makeEdgeLabel('mother').multiplicity(MANY2ONE).make()
mgmt.commit()

定义属性键

顶点和边上的属性是键值对。例如,属性 name='Daniel' 具有键 name 和值 'Daniel'。属性键是 JanusGraph Schema 的一部分,可以约束允许的数据类型和值的基数。

要定义属性键,请在开放的图或管理事务上调用 makePropertyKey(String),并将属性键的名称作为参数。属性键名称在图中必须是唯一的,建议在属性名称中避免空格或特殊字符。此方法返回一个属性键的构建器。

注意

在创建属性键时,考虑同时创建图索引以获得更好的性能,请参阅索引性能

属性键数据类型

使用 dataType(Class) 定义属性键的数据类型。JanusGraph 将强制所有与该键关联的值都具有配置的数据类型,从而确保添加到图中的数据有效。例如,可以定义 name 键具有 String 数据类型。请注意,不支持原始类型。请使用对应的包装类,例如 Integer 而不是 int

将数据类型定义为 Object.class 以允许任何(可序列化的)值与键关联。但是,鼓励尽可能使用具体数据类型。配置的数据类型必须是具体类,而不是接口或抽象类。JanusGraph 强制执行类相等性,因此不允许添加配置数据类型的子类。

JanusGraph 原生支持以下数据类型。

JanusGraph 原生数据类型

名称 描述
字符串 字符序列
字符 单个字符
布尔值 真或假
字节 字节值
短整型 短整型值
整数 整型值
长整数 长整型值
浮点型 4字节浮点数
双精度浮点数 8字节浮点数
日期 特定时间点(java.util.Date
地理形状 地理形状,如点、圆或盒
UUID 通用唯一标识符(java.util.UUID

属性键基数

使用 cardinality(Cardinality) 来定义在任何给定顶点上与键关联的值的允许基数。

  • SINGLE:对于此类键,每个元素最多允许一个值。换句话说,图中的所有元素的键→值映射都是唯一的。属性键birthDate就是具有SINGLE基数的一个例子,因为每个人都只有一个出生日期。
  • LIST:允许此类键的每个元素具有任意数量的值。换句话说,该键与一个值列表相关联,允许重复值。假设我们将传感器建模为图中的顶点,属性键 sensorReading 是一个具有 LIST 基数的例子,以允许记录大量(可能重复的)传感器读数。
  • SET:允许每个元素有多个值但没有重复值。换句话说,该键与一组值相关联。如果我们想捕捉一个人的所有姓名(包括昵称、娘家姓等),则属性键 name 具有 SET 基数。

默认基数设置为 SINGLE。请注意,边和属性上使用的属性键具有基数 SINGLE。不支持为边或属性上的单个键附加多个值。

mgmt = graph.openManagement()
birthDate = mgmt.makePropertyKey('birthDate').dataType(Long.class).cardinality(Cardinality.SINGLE).make()
name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make()
sensorReading = mgmt.makePropertyKey('sensorReading').dataType(Double.class).cardinality(Cardinality.LIST).make()
mgmt.commit()

关系类型

边标签和属性键统称为关系类型。关系类型名称在图中必须是唯一的,这意味着属性键和边标签不能同名。JanusGraph API 中有方法可以查询关系类型是否存在或检索关系类型,包括属性键和边标签。

mgmt = graph.openManagement()
if (mgmt.containsRelationType('name'))
    name = mgmt.getPropertyKey('name')
mgmt.getRelationTypes(EdgeLabel.class)
mgmt.commit()

定义顶点标签

与边一样,顶点也有标签。与边标签不同,顶点标签是可选的。顶点标签用于区分不同类型的顶点,例如用户顶点和产品顶点。

尽管在概念和数据模型层面,标签是可选的,但 JanusGraph 会将所有顶点分配一个标签,作为内部实现细节。由 addVertex 方法创建的顶点使用 JanusGraph 的默认标签。

要创建标签,请在开放的图或管理事务上调用 makeVertexLabel(String).make(),并将顶点标签的名称作为参数。顶点标签名称在图中必须是唯一的。

mgmt = graph.openManagement()
person = mgmt.makeVertexLabel('person').make()
mgmt.commit()
// Create a labeled vertex
person = graph.addVertex(label, 'person')
// Create an unlabeled vertex
v = graph.addVertex()
graph.tx().commit()

自动Schema创建器

如果一个边标签、属性键或顶点标签没有被显式定义,那么在它首次被用于添加边、顶点或设置属性时,它将被隐式定义。为 JanusGraph 图配置的 DefaultSchemaMaker 定义了这些类型。

默认情况下,隐式创建的边标签具有 MULTI 多重性,隐式创建的属性键具有 SINGLE 基数。隐式创建的属性键的数据类型在 JanusGraph 原生支持的情况下会被推断。当且仅当给定值不是任何 Native JanusGraph Data Types 时,才使用 Object.class。用户可以通过实现和注册自己的 DefaultSchemaMaker 来控制自动 Schema 元素创建。

当为顶点属性定义一个不同于 SINGLE 的基数时,该基数应该用于第一次查询中顶点属性的所有值(即定义新顶点属性键的查询)。

强烈建议显式定义所有Schema元素,并通过在JanusGraph图配置中设置schema.default=none来禁用自动Schema创建。

更改Schema元素

一旦边标签、属性键或顶点标签的定义被提交到图中,就不能再更改。但是,Schema元素的名称可以通过 JanusGraphManagement.changeName(JanusGraphSchemaElement, String) 进行更改,如下例所示,其中属性键 place 被重命名为 location

mgmt = graph.openManagement()
place = mgmt.getPropertyKey('place')
mgmt.changeName(place, 'location')
mgmt.commit()

请注意,Schema名称更改可能不会立即在当前运行的事务和集群中的其他 JanusGraph 图实例中可见。虽然 Schema 名称更改通过存储后端通知所有 JanusGraph 实例,但 Schema 更改可能需要一段时间才能生效,并且在某些故障情况下(例如网络分区)如果与重命名同时发生,可能需要实例重启。因此,用户必须确保以下任一条件成立:

  • 重命名的标签或键当前未被主动使用(即未被写入或读取),并且在所有 JanusGraph 实例都知道名称更改之前不会被使用。
  • 运行中的事务积极适应短暂的中间期,在此期间旧名称或新名称根据特定的 JanusGraph 实例和名称更改通知状态而有效。例如,这意味着事务可以同时查询两个名称。

如果需要重新定义现有的Schema类型,建议将此类型的名称更改为当前未(且永远不会)使用的名称。之后,可以使用原始名称定义新的标签或键,从而有效地替换旧的。但是,请注意,这不会影响先前使用现有类型写入的顶点、边或属性。不支持在线重新定义现有图元素,必须通过批量图转换来完成。

请注意,如果您更改了作为混合索引一部分的属性名称,则不应在同一索引中重复使用该属性名称。例如,以下代码将失败。

name = mgmt.makePropertyKey("name").dataType(String.class).make()
mgmt.buildIndex("nameIndex", Vertex.class).addKey(name).buildMixedIndex("search")
mgmt.commit()

mgmt = graph.openManagement()
mgmt.changeName(mgmt.getPropertyKey("name"), "oldName");
name = mgmt.makePropertyKey("name").dataType(String.class).make()
mgmt.addIndexKey(mgmt.getGraphIndex("nameIndex"), name)
mgmt.commit()

// the following query will throw an exception
graph.traversal().V().has("name", textContains("value")).hasNext()

原因是,当您调用 mgmt.changeName 时,JanusGraph 不会尝试更改存储在混合索引后端中的字段名称。相反,JanusGraph 维护属性名称和索引字段名称之间的双重映射。如果您使用旧名称创建新索引并将其添加到同一索引,则会发生冲突,因为 JanusGraph 无法确定索引字段应该映射到哪个属性。

Schema约束

Schema 的定义允许用户配置显式属性和连接约束。属性可以绑定到特定的顶点标签和/或边标签。此外,连接约束允许用户显式定义哪些两个顶点标签可以通过边标签连接。这些约束可用于确保图与给定的域模型匹配。例如,对于众神之图,一个 god 可以是另一个 god 的兄弟,但不能是 monster 的兄弟,一个 god 可以有一个属性 age,但 location 不能有一个属性 age。这些约束默认是禁用的。

通过设置 schema.constraints=true 来启用这些 Schema 约束。此设置依赖于 schema.default 设置。如果配置 schema.default 设置为 none,则在违反 Schema 约束时会抛出 IllegalArgumentException。如果 schema.default 未设置为 none,则会自动创建 Schema 约束,但不会抛出异常。激活 Schema 约束对现有数据没有影响,因为这些 Schema 约束仅在插入过程中应用。因此,这些约束完全不影响数据读取。

可以使用 JanusGraphManagement.addProperties(VertexLabel, PropertyKey...) 将多个属性绑定到顶点,例如

mgmt = graph.openManagement()
person = mgmt.makeVertexLabel('person').make()
name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make()
birthDate = mgmt.makePropertyKey('birthDate').dataType(Long.class).cardinality(Cardinality.SINGLE).make()
mgmt.addProperties(person, name, birthDate)
mgmt.commit()

可以使用 JanusGraphManagement.addProperties(EdgeLabel, PropertyKey...) 将多个属性绑定到边,例如

mgmt = graph.openManagement()
follow = mgmt.makeEdgeLabel('follow').multiplicity(MULTI).make()
name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make()
mgmt.addProperties(follow, name)
mgmt.commit()

可以使用 JanusGraphManagement.addConnection(EdgeLabel, VertexLabel out, VertexLabel in) 在出、入和边之间定义连接,例如

mgmt = graph.openManagement()
person = mgmt.makeVertexLabel('person').make()
company = mgmt.makeVertexLabel('company').make()
works = mgmt.makeEdgeLabel('works').multiplicity(MULTI).make()
mgmt.addConnection(works, person, company)
mgmt.commit()