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 }
}
}
}
}
JSON 模式文档总是从根节点描述整个文档。模式本身是一个模式对象,可以包含描述属性和子文档的嵌入式模式对象。
required
是一个属性,用于描述文档中哪些属性是必需的。它可以与其他模式约束一起可选地指定。参见 MongoDB 文档中的 可用关键字。properties
与描述object
类型的模式对象相关。它包含特定属性的模式约束。firstname
指定了文档中firstname
字段的约束。在这里,它是一个基于字符串的properties
元素,声明了可能的字段值。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
获取一个模式构建器,使用流畅的 API 来配置模式。
直接配置所需的属性,如这里所示,或者如第 3 点所示进行更详细的配置。
配置所需的 String 类型
firstname
字段,仅允许luke
和han
值。属性可以是类型化的或非类型化的。使用JsonSchemaProperty
的静态导入可以使语法稍微更紧凑,并获得诸如string(…)
等入口点。构建模式对象。
通过网关接口上的静态方法,已经有一些预定义且强类型的模式对象(JsonSchemaObject
和 JsonSchemaProperty
)可供使用。然而,您可能需要构建自定义的属性验证规则,这些规则可以通过构建器 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
提供了集合的 schema 支持的入口点,如下例所示:
示例 3:使用 $jsonSchema
创建集合
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();
template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
生成模式
设置 schema 可能是一项耗时的任务,我们鼓励所有决定这样做的人,真正花时间去做。这很重要,因为 schema 的更改可能会很困难。然而,有时候人们可能不想被它困扰,这时 JsonSchemaCreator
就派上了用场。
示例 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
}
}
简单对象属性被视为常规属性。
原始类型被视为必需属性。
枚举被限制为可能的值。
对象类型属性被检查并表示为嵌套文档。
String
类型属性通过转换器转换为Code
。@Transient
属性在生成模式时被省略。
使用可以转换为 ObjectId
的类型(如 String
)的 _id
属性会被映射为 { type : 'object' }
,除非通过 @MongoId
注解提供了更具体的信息。
表 1. 特殊模式生成规则
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' }
}
}
}
}
给定类型的属性将合并到一个元素中。
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' }
}
}
}
给定类型的属性(及其继承的属性)被合并到一个模式中。
具有相同名称的属性需要引用相同的 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;
}
将设置为
encryptMetadata
的默认加密设置。使用默认加密设置的加密字段。
覆盖默认加密算法的加密字段。
@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) {
// ... 通过目标元素名称查找
}
}
JSON Schema 类型
下表展示了支持的 JSON schema 类型:
表 2. 支持的 JSON schema 类型
Schema 类型 | Java 类型 | Schema 属性 |
---|---|---|
untyped | - | description , 自动生成的 description , enum , allOf , anyOf , oneOf , not |
object | Object | required , additionalProperties , properties , minProperties , maxProperties , patternProperties |
array | 除 byte[] 外的任何数组 | uniqueItems , additionalItems , items , minItems , maxItems |
string | String | minLength , maxLength , 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
是一种通用类型,所有具有类型的 schema 类型都继承自它。它为具有类型的 schema 类型提供了所有 untyped
的 schema 属性。
更多信息,请参阅 $jsonSchema。