JSON Schema

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

示例 1. 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 schema 文档始终从其根节点描述整个文档。模式本身是一个模式对象,可以包含描述属性和子文档的嵌入式模式对象。
2 required 是一个属性,描述了文档中哪些属性是必需的。它可以是可选的,与其它模式约束一起指定。请参阅 MongoDB 文档中关于可用关键字的说明。
3 properties 与描述 object 类型的模式对象相关。它包含属性特定的模式约束。
4 firstname 指定了文档中 firstname 字段的约束。这里,它是一个基于字符串的 properties 元素,声明了可能的字段值。
5 address 是一个子文档,为其 postCode 字段中的值定义了模式。

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

示例 2. 创建 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 直接配置必需属性,如此处所示,或在示例 3 中提供更多详细信息。
3 配置必需的 String 类型字段 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));

生成 Schema

设置模式可能是一项耗时的工作,我们鼓励所有决定这样做的人真正花时间去完成。这很重要,模式更改可能很困难。然而,有时人们可能不想被这些细节困扰,这时就可以利用 JsonSchemaCreator

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

示例 4. 从域类型生成 Json Schema
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 由转换器转换为 CodeString 类型属性。
6 生成模式时会忽略 @Transient 属性。
使用可转换为 ObjectId 的类型的 _id 属性(例如 String)会被映射为 { type : 'object' },除非通过 @MongoId 注解提供了更具体的信息。
表 1. 特殊 Schema 生成规则
Java Schema 类型 备注

Object

type : object

如果元数据可用,带 properties

Collection

type : array

-

Map

type : object

-

Enum

type : string

带包含可能的枚举值的 enum 属性。

array

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. 将多个 Schema 合并到一个 Schema 定义中
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 schema 才能合并。以下示例显示了一个由于数据类型不匹配而无法自动合并的定义。在这种情况下,必须向 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 表达式解析 keyIds。为此,需要提供额外的环境元数据(通过 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 定义,如下面的代码片段所示。提供自定义扩展是计算 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 类型

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

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

untyped

-

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

object

Object

required, additionalProperties, properties, minProperties, maxProperties, patternProperties

array

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[]

(无)

boolean

boolean, Boolean

(无)

null

null

(无)

objectId

ObjectId

(无)

date

java.util.Date

(无)

timestamp

BsonTimestamp

(无)

regex

java.util.regex.Pattern

(无)

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

更多信息请参阅 $jsonSchema