JSON Schema

从 3.6 版本开始,MongoDB 支持根据提供的 JSON Schema 验证文档的集合。在创建集合时,可以定义模式本身以及验证操作和级别,如下例所示

示例 1. 示例 JSON 架构
{
  "type": "object",                                                        (1)

  "required": [ "firstname", "lastname" ],                                 (2)

  "properties": {                                                          (3)

    "firstname": {                                                         (4)
      "type": "string",
      "enum": [ "luke", "han" ]
    },
    "address": {                                                           (5)
      "type": "object",
      "properties": {
        "postCode": { "type": "string", "minLength": 4, "maxLength": 5 }
      }
    }
  }
}
1 JSON 架构文档始终描述从根开始的整个文档。架构本身就是一个架构对象,它可以包含描述属性和子文档的嵌入式架构对象。
2 required 是一个属性,它描述了文档中哪些属性是必需的。它可以与其他架构约束一起可选地指定。有关详细信息,请参阅 MongoDB 的文档,了解可用关键字
3 properties 与描述 object 类型的架构对象相关。它包含特定于属性的架构约束。
4 firstname 指定文档中 firstname 字段的约束。在这里,它是一个基于字符串的 properties 元素,声明了可能的字段值。
5 address 是一个子文档,定义了其 postCode 字段中值的架构。

您可以通过指定架构文档(即使用 Document API 解析或构建文档对象)或使用 Spring Data 的 JSON 架构实用程序(位于 org.springframework.data.mongodb.core.schema 中)来构建架构。MongoJsonSchema 是所有 JSON 架构相关操作的入口点。以下示例展示了如何使用 MongoJsonSchema.builder() 创建 JSON 架构

示例 2. 创建 JSON 架构
MongoJsonSchema.builder()                                                    (1)
    .required("lastname")                                                    (2)

    .properties(
                required(string("firstname").possibleValues("luke", "han")), (3)

                object("address")
                     .properties(string("postCode").minLength(4).maxLength(5)))

    .build();                                                                (4)
1 获取架构构建器,使用流畅的 API 配置架构。
2 直接配置必需属性,如这里所示,或使用更多详细信息,如 3 中所示。
3 配置必需的字符串类型 firstname 字段,仅允许 lukehan 值。属性可以是类型化的或非类型化的。使用 JsonSchemaProperty 的静态导入使语法更紧凑,并获取入口点,例如 string(…)
4 构建架构对象。

通过网关接口上的静态方法,已经有一些预定义的和强类型化的架构对象(JsonSchemaObjectJsonSchemaProperty)可用。但是,您可能需要构建自定义属性验证规则,这些规则可以通过构建器 API 创建,如下面的示例所示

// "birthdate" : { "bsonType": "date" }
JsonSchemaProperty.named("birthdate").ofType(Type.dateType());

// "birthdate" : { "bsonType": "date", "description", "Must be a date" }
JsonSchemaProperty.named("birthdate").with(JsonSchemaObject.of(Type.dateType()).description("Must be a date"));

CollectionOptions 提供了对集合架构支持的入口点,如下面的示例所示

示例 3. 使用 $jsonSchema 创建集合
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

template.createCollection(Person.class, CollectionOptions.empty().schema(schema));

生成架构

设置架构可能是一项耗时的任务,我们鼓励所有决定这样做的人,真正花时间去做。重要的是,架构更改可能很困难。但是,可能会有时候人们不想被它困扰,这就是 JsonSchemaCreator 发挥作用的地方。

JsonSchemaCreator 及其默认实现从映射基础设施提供的域类型元数据生成 MongoJsonSchema。这意味着,带注释的属性 以及潜在的 自定义转换 都将被考虑。

示例 4. 从域类型生成 Json 架构
public class Person {

    private final String firstname;                   (1)
    private final int age;                            (2)
    private Species species;                          (3)
    private Address address;                          (4)
    private @Field(fieldType=SCRIPT) String theForce; (5)
    private @Transient Boolean useTheForce;           (6)

    public Person(String firstname, int age) {        (1) (2)

        this.firstname = firstname;
        this.age = age;
    }

    // gettter / setter omitted
}

MongoJsonSchema schema = MongoJsonSchemaCreator.create(mongoOperations.getConverter())
    .createSchemaFor(Person.class);

template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
{
    'type' : 'object',
    'required' : ['age'],                     (2)
    'properties' : {
        'firstname' : { 'type' : 'string' },  (1)
        'age' : { 'bsonType' : 'int' }        (2)
        'species' : {                         (3)
            'type' : 'string',
            'enum' : ['HUMAN', 'WOOKIE', 'UNKNOWN']
        }
        'address' : {                         (4)
            'type' : 'object'
            'properties' : {
                'postCode' : { 'type': 'string' }
            }
        },
        'theForce' : { 'type' : 'javascript'} (5)
     }
}
1 简单对象属性被视为常规属性。
2 原始类型被视为必需属性
3 枚举被限制为可能的值。
4 对象类型属性被检查并表示为嵌套文档。
5 String 类型属性,由转换器转换为 Code
6 @Transient 属性在生成模式时被省略。
使用可以转换为 ObjectId 的类型的 _id 属性(如 String)被映射到 { type : 'object' },除非通过 @MongoId 注解提供更具体的的信息。
表 1. 特殊模式生成规则
Java 模式类型 备注

对象

type : object

如果元数据可用,则使用 properties

集合

type : array

-

映射

type : object

-

枚举

type : string

使用 enum 属性保存可能的枚举值。

数组

type : array

简单类型数组,除非它是 byte[]

byte[]

bsonType : binData

-

上面的例子展示了如何从非常精确的类型化源代码中推导出模式。在域模型中使用多态元素会导致 Object 和泛型 <T> 类型的模式表示不准确,这些类型很可能被表示为 { type : 'object' },而没有进一步的说明。MongoJsonSchemaCreator.property(…) 允许定义额外的细节,例如在渲染模式时应该考虑的嵌套文档类型。

示例 5. 为属性指定附加类型
class Root {
	Object value;
}

class A {
	String aValue;
}

class B {
	String bValue;
}
MongoJsonSchemaCreator.create()
    .property("value").withTypes(A.class, B.class) (1)
{
    'type' : 'object',
    'properties' : {
        'value' : {
            'type' : 'object',
            'properties' : {                       (1)
                'aValue' : { 'type' : 'string' },
                'bValue' : { 'type' : 'string' }
            }
        }
    }
}
1 给定类型的属性合并到一个元素中。

MongoDB 的无模式方法允许在同一个集合中存储不同结构的文档。这些文档可能被建模为具有一个共同的基类。无论选择哪种方法,MongoJsonSchemaCreator.merge(…) 都可以帮助避免将多个模式合并为一个的需要。

示例 6. 将多个模式合并为单个模式定义
abstract class Root {
	String rootValue;
}

class A extends Root {
	String aValue;
}

class B extends Root {
	String bValue;
}

MongoJsonSchemaCreator.mergedSchemaFor(A.class, B.class) (1)
{
    'type' : 'object',
       'properties' : { (1)
           'rootValue' : { 'type' : 'string' },
           'aValue' : { 'type' : 'string' },
           'bValue' : { 'type' : 'string' }
       }
    }
}
1 给定类型的属性(及其继承的属性)被组合成一个模式。

具有相同名称的属性需要引用相同的 JSON 模式才能被组合。以下示例展示了一个无法自动合并的定义,因为数据类型不匹配。在这种情况下,必须向 MongoJsonSchemaCreator 提供一个 ConflictResolutionFunction

class A extends Root {
	String value;
}

class B extends Root {
	Integer value;
}

加密字段

MongoDB 4.2 字段级加密 允许直接加密单个属性。

在设置 JSON Schema 时,可以将属性包装在加密属性中,如下面的示例所示。

示例 7. 通过 Json Schema 进行客户端字段级加密
MongoJsonSchema schema = MongoJsonSchema.builder()
    .properties(
        encrypted(string("ssn"))
            .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
            .keyId("*key0_id")
	).build();

无需手动定义加密字段,可以使用 @Encrypted 注解,如下面的代码片段所示。

示例 8. 通过 Json Schema 进行客户端字段级加密
@Document
@Encrypted(keyId = "xKVup8B1Q+CkHaVRx+qa+g==", algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Random") (1)
static class Patient {

    @Id String id;
    String name;

    @Encrypted (2)
    String bloodType;

    @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") (3)
    Integer ssn;
}
1 将为 encryptMetadata 设置的默认加密设置。
2 使用默认加密设置的加密字段。
3 覆盖默认加密算法的加密字段。

@Encrypted 注解支持通过 SpEL 表达式解析 keyId。为此,需要额外的环境元数据(通过 MappingContext)并必须提供。

@Document
@Encrypted(keyId = "#{mongocrypt.keyId(#target)}")
static class Patient {

    @Id String id;
    String name;

    @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Random")
    String bloodType;

    @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
    Integer ssn;
}

MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext);
MongoJsonSchema patientSchema = schemaCreator
    .filter(MongoJsonSchemaCreator.encryptedOnly())
    .createSchemaFor(Patient.class);

mongocrypt.keyId 函数通过 EvaluationContextExtension 定义,如下面的代码片段所示。提供自定义扩展提供了计算 keyId 的最灵活方式。

public class EncryptionExtension implements EvaluationContextExtension {

    @Override
    public String getExtensionId() {
        return "mongocrypt";
    }

    @Override
    public Map<String, Function> getFunctions() {
        return Collections.singletonMap("keyId", new Function(getMethod("computeKeyId", String.class), this));
    }

    public String computeKeyId(String target) {
        // ... lookup via target element name
    }
}

JSON Schema 类型

下表显示了支持的 JSON Schema 类型

表 2. 支持的 JSON Schema 类型
模式类型 Java 类型 Schema 属性

无类型

-

description、生成的 descriptionenumallOfanyOfoneOfnot

对象

对象

requiredadditionalPropertiespropertiesminPropertiesmaxPropertiespatternProperties

数组

byte[] 之外的任何数组

uniqueItemsadditionalItemsitemsminItemsmaxItems

字符串

String

minLengthmaxLentgthpattern

整数

intInteger

multipleOfminimumexclusiveMinimummaximumexclusiveMaximum

长整数

longLong

multipleOfminimumexclusiveMinimummaximumexclusiveMaximum

双精度浮点数

floatFloatdoubleDouble

multipleOfminimumexclusiveMinimummaximumexclusiveMaximum

十进制

BigDecimal

multipleOfminimumexclusiveMinimummaximumexclusiveMaximum

数字

Number

multipleOfminimumexclusiveMinimummaximumexclusiveMaximum

二进制数据

byte[]

(无)

布尔值

布尔型, Boolean

(无)

(无)

objectId

ObjectId

(无)

日期

java.util.Date

(无)

时间戳

BsonTimestamp

(无)

正则表达式

java.util.regex.Pattern

(无)

untyped 是一种通用类型,所有类型化的模式类型都继承自它。它为类型化的模式类型提供所有 untyped 模式属性。

有关更多信息,请参阅 $jsonSchema