MongoDB java driver ObjectId の生成時は、文字列パラメータは12バイトの16進数にしておく


MongoDB の データ登録時に、
ObjectId を生成して、insert 実行したところ、以下のエラーが発生しました。
対処した内容を記載します。


Mongo Driver の Version

3.2.2 です。

    <!-- MongoDB -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongo-java-driver</artifactId>
        <version>3.2.2</version>
    </dependency>


エラー内容

ERROR [2017-06-11 17:41:41,495] org.apache.wicket.util.listener.ListenerCollection: Error invoking listener: org.apache.wicket.Application$3@480131f8
! java.lang.IllegalArgumentException: invalid hexadecimal representation of an ObjectId: [node11qt8o6gp4dtag1uu3a6ksq80g4|||17|||Wicket-MongoDB]
! at org.bson.types.ObjectId.parseHexString(ObjectId.java:523)
! at org.bson.types.ObjectId.<init>(ObjectId.java:237)

原因

ObjectId に文字列を設定する場合は、12バイトの16進数でないとエラーになります。
spring - java.lang.IllegalArgumentException: invalid hexadecimal representation of an ObjectId - Stack Overflow

以下、コンストラクタ内で、 parseHexString が呼ばれ、

    /**
     * Constructs a new instance from a 24-byte hexadecimal string representation.
     *
     * @param hexString the string to convert
     * @throws IllegalArgumentException if the string is not a valid hex string representation of an ObjectId
     */
    public ObjectId(final String hexString) {
        this(parseHexString(hexString));
    }

parseHexString から、 isValid メソッドが呼ばれて、そこで、文字列に対するチェックを行っています。

    /**
     * Checks if a string could be an {@code ObjectId}.
     *
     * @param hexString a potential ObjectId as a String.
     * @return whether the string could be an object id
     * @throws IllegalArgumentException if hexString is null
     */
    public static boolean isValid(final String hexString) {
        if (hexString == null) {
            throw new IllegalArgumentException();
        }

        int len = hexString.length();
        if (len != 24) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            char c = hexString.charAt(i);
            if (c >= '0' && c <= '9') {
                continue;
            }
            if (c >= 'a' && c <= 'f') {
                continue;
            }
            if (c >= 'A' && c <= 'F') {
                continue;
            }

            return false;
        }

        return true;
    }


対処方法

キー値に設定しようとした値は、12バイトの16進数 ではないので、
ユニークインデックスは張ったカラムに設定を行いました。
無理矢理16進数に変換する処理を作って適用してもよかったかもしれませんが、
そこまでしなくていいと判断しました。


補足

ObjectId には、Date を引数に取るコンストラクタもあります。

    /**
     * Constructs a new instance using the given date.
     *
     * @param date the date
     */
    public ObjectId(final Date date) {
        this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false);
    }

デフォルトコンストラクタでは、実装は以下のようになっていて、内部的に上記コンストラクタを呼び出しています。

    /**
     * Create a new object id.
     */
    public ObjectId() {
        this(new Date());
    }

この実装だと、複数のサーバからアクセスあった場合、IDが被りそうですが、
MACHINE_IDENTIFIERPROCESS_IDENTIFIERNEXT_COUNTER
が肝になっていそうで、筐体のMacアドレスや、AtomicInteger などを使用していて、
IDを一意に採番する実装になっています。

以上です。

コメント