Overview
このガイドでは、Java Reactive Streams ドライバーで Java オブジェクトと BSON データのエンコードとデコードを処理する Codec とサポート クラスについて学習できます。Codec 抽象化を使用して、対応する BSON 型に任意の Java 型をマッピングできます。また、Document や BsonDocument のような中間マップベースのオブジェクトを使用せずに、ドメイン オブジェクトを BSON との間で直接マッピングするためにも使用できます。
次のセクションでは、Codec 抽象化を使用してカスタム エンコードおよびデコード ロジックを指定する方法を説明します。
POJO(Plain Old Java オブジェクト)のエンコードおよびデコードロジックのカスタマイズについて詳しくは、「POJO を使用したデータのモデル化」ガイドを参照してください。
コーデックを実装する
Codec インターフェースには、Java オブジェクトを BSON データにシリアル化および逆シリアル化するための抽象メソッドが含まれています。このインターフェースの**実装**では、BSON と Java オブジェクト間の変換ロジックを定義できます。
Codec インターフェースを実装するには、encode()、decode()、および getEncoderClass() 抽象メソッドをオーバーライドします。
encode() メソッドには次のパラメーターが必要です。
Parameter Type | 説明 |
|---|---|
| BSON ドキュメントを書き込むためのメソッドを公開するインターフェース型である |
| 実装によってエンコードされるデータ。型は、実装に割り当てられた型変数と一致する必要があります。 |
| 現在の値を MongoDB コレクションに保存するかどうかなど、BSON にエンコードされる Java オブジェクト データに関するメタデータが含まれます。 |
このメソッドは、BsonWriter インスタンスを使用してエンコードされた値を MongoDB に送信し、値を返しません。
decode() メソッドは、BSON データから取得した値が設定された Java オブジェクト インスタンスを返します。このメソッドには次のパラメータが必要です。
Parameter Type | 説明 |
|---|---|
| BSON ドキュメントを読み取るためのメソッドを公開するインターフェース型である |
| Java オブジェクトにデコードされる BSON データに関する情報が含まれます。 |
Java は型消去により型を推論できないため、getEncoderClass() メソッドは Java クラスのクラス インスタンスを返します。
例
次のコード例は、カスタム Codec を実装する方法を示しています。
ReadStatus列挙型には、本が読まれたかどうかを表す値 READ と UNREAD が含まれています。
public enum ReadStatus { READ, UNREAD }
ReadStatusCodec クラスは、Java enum 値を対応する BSON ブール値に変換するために Codec を実装します。encode() メソッドは ReadStatus を BSON ブール値に変換し、decode() メソッドは逆方向の変換を実行します。
public class ReadStatusCodec implements Codec<ReadStatus> { public void encode(BsonWriter writer, ReadStatus value, EncoderContext encoderContext) { writer.writeBoolean(value.equals(ReadStatus.READ) ? Boolean.TRUE : Boolean.FALSE); } public ReadStatus decode(BsonReader reader, DecoderContext decoderContext) { return reader.readBoolean() ? ReadStatus.READ : ReadStatus.UNREAD; } public Class<ReadStatus> getEncoderClass() { return ReadStatus.class; } }
Codec とそれが適用される Java Realm オブジェクトタイプとの間のマッピングを含む ReadStatusCodec のインスタンスを CodecRegistry に追加できます。このページの CodecRegistry セクションに進み、Codec を含める方法を確認します。
このセクションのクラスとインターフェースの詳細については、次の API ドキュメントを参照してください。
CodecRegistry を使用する
CodecRegistry は、指定された Java クラスをエンコードおよびデコードする Codec インスタンスの不変のコレクションです。次の CodecRegistries クラスの静的ファクトリー メソッドのいずれかを使用して、関連付けられた型に含まれる Codec インスタンスから CodecRegistry を構築できます。
fromCodecs()fromProviders()fromRegistries()
次のコード スニペットは、fromCodecs() メソッドを使用して CodecRegistry を構築する方法を示しています。
CodecRegistry codecRegistry = CodecRegistries.fromCodecs(new IntegerCodec(), new ReadStatusCodec());
前の例では、 CodecRegistryに次の Codec実装が含まれています。
IntegerCodec、Integersを変換し、BSON パッケージの一部となるCodecです。ReadStatusCodec:Java 列挙値を BSON ブール値に変換するサンプル
Codec
次のコードを使用して、前の例の CodecRegistry インスタンスから Codec インスタンスを検索できます。
Codec<ReadStatus> readStatusCodec = codecRegistry.get(ReadStatus.class); Codec<Integer> integerCodec = codecRegistry.get(Integer.class);
登録されていないクラスの Codec インスタンスを検索しようとすると、get() メソッドは CodecConfigurationException をスローします。
このセクションのクラスとインターフェースの詳細については、次の API ドキュメントを参照してください。
CodecProvider を使用する
CodecProvider は、Codec インスタンスを作成し、それを CodecRegistry インスタンスに割り当てる抽象メソッドを含むインターフェースです。CodecRegistry と同様に、BSON ライブラリは get() メソッドによって検索された Codec インスタンスを使用して、Java と BSON データ型間の変換を行います。
ただし、対応する Codec オブジェクトを必要とするフィールドを含むクラスを追加する場合は、クラスの Codec をインスタンス化する前に、クラスフィールドの Codec オブジェクトをインスタンス化する必要があります。get() メソッドの CodecRegistry パラメータを使用して、Codec が依存する Codec インスタンスを渡すことができます。
次のコード例は、CodecProvider を実装して、前の例の ReadStatusCodec などの CodecRegistry インスタンスで必要な Codec インスタンスを BookCodec に渡す方法を示しています。
public class BookCodecProvider implements CodecProvider { public BookCodecProvider() {} public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { if (clazz == Book.class) { return (Codec<T>) new BookCodec(registry); } // return null when not a provider for the requested class return null; } }
これらの Codec クラスを使用した読み取りおよび書き込み (write) 操作を示す実行可能な例については、このガイドの「カスタム コーデックの例」セクションを参照してください。
POJO を操作する場合は、よく使用されるデータ型を変換し、その動作をカスタマイズする際に重複コードを最小限に抑えるために PojoCodecProvider の使用を検討してください。詳しくは、弊社の「POJO を使用したデータのモデル化」ガイドを参照してください。
デフォルトのコーデック レジストリの使用
デフォルトのコーデック レジストリは、一般的に使用される Java と MongoDB の型間の変換を指定する CodecProvider クラスのセットです。別のコーデック レジストリを指定しない限り、ドライバーは自動的にデフォルトのコーデック レジストリを使用します。
1 つ以上の Codec クラスの動作をオーバーライドする必要があり、他のクラスのデフォルトのコーデック レジストリの動作を維持する場合は、優先順位に従ってすべてのレジストリを指定できます。たとえば、列挙型の Codec のデフォルトのプロバイダー動作をカスタム MyEnumCodec でオーバーライドするには、次の例に示すように、デフォルトのコーデック レジストリの前にレジストリ リストに追加する必要があります。
CodecRegistry newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry());
このセクションのクラスとインターフェースの詳細については、次の API ドキュメントの各セクションを参照してください。
タイプ マッピングのカスタマイズ
BsonTypeClassMap クラスには、BSON typesと Java 型間の推奨マッピングが含まれています。このクラスをカスタム Codec または CodecProvider で使用すると、Document クラスなどの Iterable または Map を実装するコンテナクラスに BSON types をデコードする Java 型を管理するのに役立ちます。
新しいエントリまたは置換エントリを含む Map を渡すことにより、BsonTypeClassMap のデフォルト マッピングを追加または変更できます。
次のコード スニペットは、デフォルトの BsonTypeClassMap インスタンス内の BSON 型に対応する Java クラスの種類を検索する方法を示しています。
BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(); Class<?> clazz = bsonTypeClassMap.get(BsonType.ARRAY); System.out.println("Java type: " + clazz.getName());
このコードは、次のように出力します。
Java type: java.util.List
BsonTypeClassMap コンストラクターで置換を指定することにより、インスタンス内のこれらのマッピングを変更できます。次の例は、BsonTypeClassMap インスタンス内の ARRAY のマッピングを Set クラスに置き換える方法を示しています。
Map<BsonType, Class<?>> replacements = new HashMap<BsonType, Class<?>>(); replacements.put(BsonType.ARRAY, Set.class); BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(replacements); Class<?> clazz = bsonTypeClassMap.get(BsonType.ARRAY); System.out.println("Java type: " + clazz.getName());
このコードは、次のように出力します。
Java type: java.util.Set
デフォルトのマッピングの完全なリストについては、BsonTypeClassMap API ドキュメントを参照してください。
Tip
Document クラスが BsonTypeClassMap を使用する方法の例については、DocumentCodecProvider クラスと DocumentCodec クラスのドライバー ソースコードを参照してください。
カスタム Codec の例
このセクションでは、Codec と CodecProvider を実装してカスタム Java クラスのエンコードおよびデコード ロジックを定義する方法を示します。また、カスタム実装を指定して使用し、挿入操作と取得操作を実行する方法も示します。
次の例では、MongoDB コレクションに保存して取り出すための Book というカスタム クラスとそのフィールドを定義しています。
public class Book { private String title; private ReadStatus readStatus = ReadStatus.UNREAD; private Integer pageCount; public Book() {} // ...
このクラスには次のフィールドが含まれており、それぞれに Codec を割り当てる必要があります。
titleBSON ライブラリに含まれるStringCodecを例で使用するString値が含まれます。readStatusには、本が読まれたかどうかが記述されています。例では、列挙値を BSON ブール値に変換するカスタム ReadStatusCodec を使用しています。pageCount例では BSON ライブラリに含まれるIntegerCodecを使用するInteger値が含まれます。
次のコード例は、Book クラスに Codec を実装する方法を示しています。コンストラクターは、フィールドをエンコードおよびデコードするのに必要な Codec インスタンスを検索する CodecRegistry のインスタンスを期待していることに注意してください。
public class BookCodec implements Codec<Book> { private Codec<String> stringCodec; private Codec<ReadStatus> readStatusCodec; private Codec<Integer> integerCodec; public BookCodec(CodecRegistry registry) { this.stringCodec = registry.get(String.class); this.readStatusCodec = registry.get(ReadStatus.class); this.integerCodec = registry.get(Integer.class); } // Defines an encode() method to convert Book field values to BSON values public void encode(BsonWriter writer, Book value, EncoderContext encoderContext) { writer.writeStartDocument(); writer.writeName("title"); stringCodec.encode(writer, value.getTitle(), encoderContext); writer.writeName("readStatus"); readStatusCodec.encode(writer, value.getReadStatus(), encoderContext); writer.writeName("pageCount"); integerCodec.encode(writer, value.getPageCount(), encoderContext); writer.writeEndDocument(); } // Defines a decode() method to convert BSON values to Book field values public Book decode(BsonReader reader, DecoderContext decoderContext) { Book book = new Book(); reader.readStartDocument(); while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { String fieldName = reader.readName(); if (fieldName.equals("title")) { book.setTitle(stringCodec.decode(reader, decoderContext)); } else if (fieldName.equals("readStatus")) { book.setReadStatus(readStatusCodec.decode(reader, decoderContext)); } else if (fieldName.equals("pageCount")) { book.setPageCount(integerCodec.decode(reader, decoderContext)); } else if (fieldName.equals("_id")) { reader.readObjectId(); } else { reader.skipValue(); } } reader.readEndDocument(); return book; } // Returns an instance of the Book class, since Java cannot infer the class type public Class<Book> getEncoderClass() { return Book.class; } }
フィールドの Codec インスタンスを Book で使用できるようにするには、次のコード例に示すカスタム CodecProvider を実装します。
public class BookCodecProvider implements CodecProvider { public BookCodecProvider() {} public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { if (clazz == Book.class) { return (Codec<T>) new BookCodec(registry); } // return null when not a provider for the requested class return null; } }
変換ロジックを定義した後、次の操作を実行できます。
Bookのインスタンスのデータを MongoDB に保存するMongoDB から次のインスタンスにデータを検索する
Book
次の**例**クラスには、BookCodecProvider を withCodecRegistry() メソッドに渡して MongoCollection インスタンスに割り当てるコードが含まれています。このサンプル クラスでは、Book クラスと関連するコーデックを使用してデータの挿入と検索も行います。
public class BookCodecExample { public static void main(String[] args) { String uri = "<MongoDB connection URI>"; try (MongoClient mongoClient = MongoClients.create(uri)) { CodecRegistry codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new ReadStatusCodec()), CodecRegistries.fromProviders(new BookCodecProvider()), MongoClientSettings.getDefaultCodecRegistry()); MongoDatabase database = mongoClient.getDatabase("codecs_example_db"); MongoCollection<Book> collection = database.getCollection("books", Book.class) .withCodecRegistry(codecRegistry); // construct and insert an instance of Book Book myBook = new Book(); myBook.setTitle("The Hobbit"); myBook.setReadStatus(ReadStatus.READ); myBook.setPageCount(310); Mono.from(collection.insertOne(myBook)).block(); // retrieve one or more instances of Book Flux.from(collection.find()) .doOnNext(System.out::println) .blockLast(); } } }
前の例を実行すると、出力は次のようになります。
Book [title=The Hobbit, readStatus=READ, pageCount=310]
API ドキュメント
このガイドで言及されているメソッドとクラスの詳細については、次のAPIドキュメントを参照してください。