【Spring连载】使用Spring Data访问 MongoDB----对象映射之JSON Schema

【Spring连载】使用Spring Data访问 MongoDB----对象映射之JSON Schema

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

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 schema documents总是从根描述整个document。schema是一个schema对象本身,它可以包含描述属性和子文档的嵌入schema对象。
2. required是一个属性,用于描述文档中需要哪些属性。可以选择性地指定它以及其他schema约束。请参阅MongoDB关于[可用关键字](https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#available-keywords)的文档。
3. properties与描述对象类型的schema对象相关。它包含特定于属性的schema约束。
4. firstname为document中的firstname字段指定约束。这里,它是一个基于字符串的属性元素,声明可能的字段值。
5. address是一个子文档,在其postCode字段中定义值的schema。

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

java 复制代码
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. 获取一个schema生成器,以使用fluent API配置schema。
2. 如图所示直接配置所需属性,或如第3步所示提供更多详细信息。
3. 配置所需的String类型的firstname字段,只允许使用luke和han值。属性可以是类型化的,也可以是非类型化的。使用JsonSchemaProperty的静态导入使语法稍微紧凑一点,并获取string(...)等入口点。
4. 生成schema对象。

通过gateway接口上的静态方法,已经有一些预定义的强类型schema对象(JsonSchemaObject和JsonSchemaProperty)可用。但是,你可能需要构建自定义属性验证规则,这些规则可以通过builder API创建,如下面的示例所示:

java 复制代码
// "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支持的入口点,如下面的示例所示:
使用$jsonSchema创建集合

java 复制代码
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

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

一、生成Schema

建立一个schema可能是一项耗时的任务,如果想快速构建schema,可以使用JsonSchemaCreator。

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

java 复制代码
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));
json 复制代码
{
    '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. 由转换器转换为代码的字符串类型属性。
6. 在生成schema时忽略@Transient属性。

_id属性使用可以转换为ObjectId的类型,如String,映射到{ type : 'object' },除非有更具体的信息可以通过@MongoId注解获得。
表1:特殊Schema生成规则

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 -

上面的示例演示了如何从非常精确的类型源派生schema。在域模型中使用多态元素可能导致Object和泛型<T>类型的模式表示不准确,它们很可能表示为{ type : 'object' },而没有进一步的说明。MongoJsonSchemaCreator.property(...)允许定义额外的细节,比如在呈现schema时应该考虑的嵌套文档类型。
例5:为属性指定其他类型

java 复制代码
class Root {
	Object value;
}

class A {
	String aValue;
}

class B {
	String bValue;
}
MongoJsonSchemaCreator.create()
    .property("value").withTypes(A.class, B.class) --------1
json 复制代码
{
    'type' : 'object',
    'properties' : {
        'value' : {
            'type' : 'object',
            'properties' : {                       --------1 
                'aValue' : { 'type' : 'string' },
                'bValue' : { 'type' : 'string' }
            }
        }
    }
}

1. 给定类型的属性被合并到一个元素中。

MongoDB的schema-free方法允许在一个集合中存储不同结构的文档。它们可以被建模为具有公共基类。无论选择哪种方法,MongoJsonSchemaCreator.merge(...)都可以帮助满足将多个schema合并为一个schema的需要。
例6:将多个Schemas合并到单个Schema定义中

java 复制代码
abstract class Root {
	String rootValue;
}

class A extends Root {
	String aValue;
}

class B extends Root {
	String bValue;
}

MongoJsonSchemaCreator.mergedSchemaFor(A.class, B.class) --------1
json 复制代码
{
    'type' : 'object',
       'properties' : {                                  --------1
           'rootValue' : { 'type' : 'string' },
           'aValue' : { 'type' : 'string' },
           'bValue' : { 'type' : 'string' }
       }
    }
}

1. 给定类型的属性(及其继承的属性)被组合到一个schema中。

具有相同名称的属性需要引用相同的JSON schema才能进行组合。下面的示例展示了由于数据类型不匹配而无法自动合并的定义。在这种情况下,一个ConflictResolutionFunction必须提供给MongoJsonSchemaCreator。

java 复制代码
class A extends Root {
	String value;
}

class B extends Root {
	Integer value;
}

二、加密字段

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

设置JSON Schema时,可以将属性封装在加密的属性中,如下例所示。
例7:通过Json Schema进行客户端字段级加密

java 复制代码
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进行客户端字段级加密

java 复制代码
@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字段覆盖默认加密算法。

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

java 复制代码
@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的最灵活的方法。

java 复制代码
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类型:
支持的JSON schema类型

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是由所有类型化schema类型继承的泛型类型。它将所有untyped schema属性提供给类型化schema类型。

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

相关推荐
憧憬成为java架构高手的小白5 小时前
LangGraph4j 实战(下)
java
天诰5 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
这个昵称不容易9 小时前
SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在
java·springboot·毕设
计算机毕设定制辅导-无忧学长9 小时前
《基于微信小程序的居家医疗陪护预约系统设计与实现》
java·spring boot·微信小程序·uniapp·居家医疗陪护预约系统
文丑颜不良啊10 小时前
LeetCode2221~2240题目解析
java·leetcode
秋饼10 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
文丑颜不良啊12 小时前
LeetCode2161~2180题目解析
java·leetcode
文丑颜不良啊14 小时前
LeetCode2141~2160题目解析
java·leetcode
LabVIEW开发14 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
天天被压力15 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python