JSON Schema

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

As of version 3.6, MongoDB supports collections that validate documents against a provided JSON Schema. The schema itself and both validation action and level can be defined when creating the collection, as the following example shows: .Sample JSON schema

{
  "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 JSON schema documents always describe a whole document from its root. A schema is a schema object itself that can contain embedded schema objects that describe properties and subdocuments.
3 required 是一个属性,描述了文档中需要哪些属性。它可以与其他架构约束一起选择性地指定。请参阅 MongoDB 关于 available keywords的文档。
4 required is a property that describes which properties are required in a document. It can be specified optionally, along with other schema constraints. See MongoDB’s documentation on available keywords.
5 properties 与一个描述 object 类型的模式对象相关。它包含特定于属性的模式约束。
6 properties is related to a schema object that describes an object type. It contains property-specific schema constraints.
7 firstname 指定文档中 firstname 字段的约束。在此,它是一个基于字符串的 properties 元素,声明可能的字段值。
8 firstname specifies constraints for the firstname field inside the document. Here, it is a string-based properties element declaring possible field values.
9 address 是一个子文档,它为其 postCode 字段中的值定义一个模式。
10 address is a subdocument defining a schema for values in its postCode field.

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

You can provide a schema either by specifying a schema document (that is, by using the Document API to parse or build a document object) or by building it with Spring Data’s JSON schema utilities in org.springframework.data.mongodb.core.schema. MongoJsonSchema is the entry point for all JSON schema-related operations. The following example shows how use MongoJsonSchema.builder() to create a JSON schema: .Creating a JSON schema

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 Obtain a schema builder to configure the schema with a fluent API.
3 直接配置必需的属性,如此处所示,或像在 3 中那样提供更多详细信息。
4 Configure required properties either directly as shown here or with more details as in 3.
5 配置必需的 String 类型 firstname 字段,仅允许 lukehan 值。属性可以是类型化的或非类型化的。使用 JsonSchemaProperty 的静态导入来使语法变得更简洁,并获取像 string(…) 这样的入口点。
6 Configure the required String-typed firstname field, allowing only luke and han values. Properties can be typed or untyped. Use a static import of JsonSchemaProperty to make the syntax slightly more compact and to get entry points such as string(…).
7 Build the schema object.

网关接口上的静态方法中已经提供了一些预定义且强类型的模式对象(JsonSchemaObjectJsonSchemaProperty)。但是,您可能需要构建自定义属性验证规则,可以通过构建器 API 创建这些规则,如下例所示:

There are already some predefined and strongly typed schema objects (JsonSchemaObject and JsonSchemaProperty) available through static methods on the gateway interfaces. However, you may need to build custom property validation rules, which can be created through the builder API, as the following example shows:

// "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 为集合的模式支持提供了入口点,如下例所示:

CollectionOptions provides the entry point to schema support for collections, as the following example shows: .Create collection with $jsonSchema

MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

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

Generating a Schema

设置模式可能是一项耗时的任务,我们鼓励每个人在决定这样做时真正花时间去完成。请务必注意,模式更改可能很困难。不过,有时人们可能不想因此而退缩,这就是 JsonSchemaCreator 发挥作用的地方。

Setting up a schema can be a time consuming task and we encourage everyone who decides to do so, to really take the time it takes. It’s important, schema changes can be hard. However, there might be times when one does not want to balked with it, and that is where JsonSchemaCreator comes into play.

JsonSchemaCreator`及其默认实现是从映射基础结构提供的域类型元数据中生成 `MongoJsonSchema。这意味着,会考虑 annotated properties以及潜在的 custom conversions

JsonSchemaCreator and its default implementation generates a MongoJsonSchema out of domain types metadata provided by the mapping infrastructure. This means, that annotated properties as well as potential custom conversions are considered.

Example 1. Generate Json Schema from domain type
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 Simple object properties are consideres regular properties.
3 基本类型被视为必需的属性。
4 Primitive types are considered required properties
5 枚举被限制到可能的值。
6 Enums are restricted to possible values.
7 对象类型属性将被检查并表示为嵌套文档。
8 Object type properties are inspected and represented as nested documents.
9 被转换器转换为 CodeString 类型属性。
10 String type property that is converted to Code by the converter.
11 生成架构时,将忽略 @Transient 属性。
12 @Transient properties are omitted when generating the schema.

_id 使用可转换为 ObjectId(例如 String)的类型的属性会映射到 { type : 'object' },除非通过 @MongoId 注释提供更具体的信息。

_id properties using types that can be converted into ObjectId like String are mapped to { type : 'object' } unless there is more specific information available via the @MongoId annotation.

Table 1. Sepcial Schema Generation rules
Java Schema Type Notes

Object

type : object

with properties if metadata available.

Collection

type : array

-

Map

type : object

-

Enum

type : string

with enum property holding the possible enumeration values.

array

type : array

simple type array unless it’s a byte[]

byte[]

bsonType : binData

-

上面的示例演示了如何从非常精确的类型化来源派生模式。在域模型中使用多态元素可能会导致 Object 和泛型 <T> 类型的模式表示不准确,这些类型可能表示为 { type : 'object' },而没有进一步的说明。MongoJsonSchemaCreator.property(…) 允许定义其他详细信息,例如应在呈现模式时考虑的嵌套文档类型。

The above example demonstrated how to derive the schema from a very precise typed source. Using polymorphic elements within the domain model can lead to inaccurate schema representation for Object and generic <T> types, which are likely to represented as { type : 'object' } without further specification. MongoJsonSchemaCreator.property(…) allows defining additional details such as nested document types that should be considered when rendering the schema.

Example 2. Specify additional types for properties
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 给定类型的属性将合并到一个元素中。
2 Properties of the given types are merged into one element.

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

MongoDBs schema-free approach allows storing documents of different structure in one collection. Those may be modeled having a common base class. Regardless of the chosen approach, MongoJsonSchemaCreator.merge(…) can help circumvent the need of merging multiple schema into one.

Example 3. Merging multiple Schemas into a single Schema definition
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 给定类型的属性(及其继承的属性)将合并到一个架构中。
2 Properties (and their inherited ones) of the given types are combined into one schema.

对于要合并的同名属性,需要引用同一 JSON 架构。以下示例显示一个定义,该定义无法自动合并,因为数据类型不匹配。在这种情况下,必须向 MongoJsonSchemaCreator 提供 ConflictResolutionFunction

Properties with the same name need to refer to the same JSON schema in order to be combined. The following example shows a definition that cannot be merged automatically because of a data type mismatch. In this case a ConflictResolutionFunction must be provided to MongoJsonSchemaCreator.

class A extends Root {
	String value;
}

class B extends Root {
	Integer value;
}

Encrypted Fields

MongoDB 4.2 Field Level Encryption允许直接加密各个属性。

MongoDB 4.2 Field Level Encryption allows to directly encrypt individual properties.

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

Properties can be wrapped within an encrypted property when setting up the JSON Schema as shown in the example below.

Example 4. Client-Side Field Level Encryption via Json Schema
MongoJsonSchema schema = MongoJsonSchema.builder()
    .properties(
        encrypted(string("ssn"))
            .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
            .keyId("*key0_id")
	).build();

您可以利用 @Encrypted 注解(如下面的代码段所示)来替换手动定义已加密字段。

Instead of defining encrypted fields manually it is possible leverage the @Encrypted annotation as shown in the snippet below.

Example 5. Client-Side Field Level Encryption via 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 Default encryption settings that will be set for encryptMetadata.
3 使用默认加密设置的加密字段。
4 Encrypted field using default encryption settings.
5 使用默认加密算法重写加密字段。
6 Encrypted field overriding the default encryption algorithm.

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

The @Encrypted Annotation supports resolving keyIds via SpEL Expressions. To do so additional environment metadata (via the MappingContext) is required and must be provided.

@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 的最灵活方式。

The mongocrypt.keyId function is defined via an EvaluationContextExtension as shown in the snippet below. Providing a custom extension provides the most flexible way of computing keyIds.

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 Types

下表显示了受支持的 JSON 架构类型:

The following table shows the supported JSON schema types:

Table 2. Supported JSON schema types
Schema Type Java Type Schema Properties

untyped

-

description, generated description, enum, allOf, anyOf, oneOf, not

object

Object

required, additionalProperties, properties, minProperties, maxProperties, patternProperties

array

any array except byte[]

uniqueItems, additionalItems, items, minItems, maxItems

string

String

minLength, maxLentgth, pattern

int

int, Integer

multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum

long

long, Long

multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum

double

float, Float, double, Double

multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum

decimal

BigDecimal

multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum

number

Number

multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum

binData

byte[]

(none)

boolean

boolean, Boolean

(none)

null

null

(none)

objectId

ObjectId

(none)

date

java.util.Date

(none)

timestamp

BsonTimestamp

(none)

regex

java.util.regex.Pattern

(none)

untyped 是由所有类型化模式类型继承的泛型类型。它为所有 untyped 模式属性提供类型化模式类型。

untyped is a generic type that is inherited by all typed schema types. It provides all untyped schema properties to typed schema types.

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

For more information, see $jsonSchema.