JSON 模式
从 3.6 版起,MongoDB 支持根据提供的 JSON 模式 来校验文档的集合。如以下示例所示,模式本身以及校验动作和级别都可以在创建集合时定义:
示例 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 解析或构建文档对象),或使用 org.springframework.data.mongodb.core.schema 中 Spring Data 的 JSON 模式工具来构建它。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 提供了集合模式支持的入口,如下例所示:
示例 3. 使用 $jsonSchema 创建集合
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();
template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
生成模式
设置模式可能是一项耗时的工作,我们鼓励所有决定这样做的人真正投入所需的时间。这很重要,因为模式变更可能很困难。不过,有时您可能不想为此烦恼,这时 JsonSchemaCreator 就派上用场了。
示例 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;
}
// getter / 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
}
}
简单对象属性被视为常规属性。
原始类型被视为必需属性。
枚举被限制为可能的取值。
对象类型属性会被检查并表示为嵌套文档。
由转换器转换为
Code的String类型属性。生成模式时会忽略
@Transient属性。
使用可转换为 ObjectId 的类型(如 String)的 _id 属性会被映射为 { type : 'object' },除非通过 @MongoId 注解提供了更具体的信息。
表 1. 特殊模式生成规则
| Java | 模式类型 | 说明 |
|---|---|---|
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. 将多个模式合并为单个模式定义
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 模式时,可以将属性包裹在加密属性中,如下例所示。
示例 7. 通过 JSON 模式实现客户端字段级加密
MongoJsonSchema schema = MongoJsonSchema.builder()
.properties(
encrypted(string("ssn"))
.algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
.keyId("*key0_id")
).build();
除了手动定义加密字段外,还可以利用 @Encrypted 注解,如下例代码片段所示。
示例 8. 通过 JSON 模式实现客户端字段级加密
@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) {
// ... lookup via target element name
}
}
JSON 模式类型
下表列出了支持的 JSON 模式类型:
表 2. 支持的 JSON 模式类型
| 模式类型 | Java 类型 | 模式属性 |
|---|---|---|
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、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。