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 }
      }
    }
  }
}
1JSON スキーマドキュメントは常にルートからドキュメント全体を記述します。スキーマは、プロパティとサブドキュメントを記述する埋め込みスキーマオブジェクトを含めることができるスキーマオブジェクト自体です。
2required は、ドキュメント内でどのプロパティが必要であるかを説明するプロパティです。他のスキーマ制約とともにオプションで指定できます。使用可能なキーワード (英語) については、MongoDB のドキュメントを参照してください。
3properties は、object 型を記述するスキーマオブジェクトに関連しています。これには、プロパティ固有のスキーマ制約が含まれます。
4firstname は、ドキュメント内の firstname フィールドの制約を指定します。ここでは、可能なフィールド値を宣言する文字列ベースの properties 要素です。
5address は、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)
1 スキーマビルダーを取得して、流れるような API を使用してスキーマを構成します。
2 ここに示すように必要なプロパティを直接構成するか、3 のように詳細を指定して構成します。
3 必須の文字列型の firstname フィールドを構成し、luke 値と han 値のみを許可します。プロパティは型指定することも、型指定しないこともできます。JsonSchemaProperty の静的インポートを使用して、構文をもう少しコンパクトにし、string(…) などのエントリポイントを取得します。
4 スキーマオブジェクトを構築します。

ゲートウェイインターフェース上の静的メソッドを通じて使用できる、事前定義された厳密に型指定されたスキーマオブジェクト (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 が役に立ちます。

JsonSchemaCreator とそのデフォルト実装は、マッピングインフラストラクチャによって提供されるドメイン型のメタデータから MongoJsonSchema を生成します。これは、アノテーション付きプロパティと潜在的なカスタム変換が考慮されることを意味します。

例 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;
    }

    // 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 コンバーターによって Code に変換される String 型のプロパティ。
6@Transient プロパティは、スキーマの生成時に省略されます。
 String のような ObjectId に変換できる型を使用する _id プロパティは、@MongoId アノテーションを介して利用可能なより具体的な情報がない限り、{ type : 'object' } にマップされます。
表 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' }
            }
        }
    }
}
1 指定された型のプロパティは 1 つの要素にマージされます。

MongoDB のスキーマフリーのアプローチにより、異なる構造のドキュメントを 1 つのコレクションに保存できます。これらは、共通の基本クラスを持ってモデル化できます。選択したアプローチに関係なく、MongoJsonSchemaCreator.merge(…) は複数のスキーマを 1 つにマージする必要性を回避できます。

例 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' }
       }
    }
}
1 指定された型のプロパティ (およびその継承されたプロパティ) は 1 つのスキーマに結合されます。

同じ名前のプロパティを結合するには、同じ JSON スキーマを参照する必要があります。次の例は、データ型の不一致のために自動的にマージできない定義を示しています。この場合、ConflictResolutionFunction を MongoJsonSchemaCreator に提供する必要があります。

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;
}
1encryptMetadata に設定されるデフォルトの暗号化設定。
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 スキーマの種類

次の表は、サポートされている JSON スキーマ型を示しています。

表 2: サポートされている JSON スキーマの種類
スキーマの種類 Java 型 スキーマのプロパティ

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 (英語) を参照してください。