🎉

Spring Data JPAでIDとしてUUIDv7を利用する

2024/12/13に公開

この記事は jig.jp Advent Calender 2024 の13日目の記事です。


初めまして、じんたんと申します。
最近、家の電気毛布が2枚連続で壊れてしまいとても悲しい気持ちです。どうぞよろしくお願いします。
Spring Data JPA のデフォルト実装である HibernateとMySQLを利用した記事となっております。

UUIDv7を使いたい

  • 連番IDと違いIDを推測されにくい
  • 時系列ソートが出来るのでUUIDv4よりもインデックス効率がいい
  • 時系列部分と乱数部分があるので衝突しにくい

などの理由でUUIDv7を利用することになりました。

既存のUUID生成アノテーション

Hibernateがデフォルトで提供するUUID生成用アノテーションには以下のものがあります。

JPA の@GeneratedValue

@GeneratedValue(strategy = GenerationType.UUID) // UUIDv4を生成

Hibernate の独自アノテーション@UuidGenerator

@UuidGenerator(style = Style.TIME) // UUIDv1 を生成
@UuidGenerator(style = Style.RANDOM) // UUIDv4 を生成

これらのアノテーションではUUIDv7 を生成することはできませんでした。

UUIDv7を生成するカスタムアノテーションを作成する

UUIDv7を生成するカスタムアノテーションを作成していきましょう。

依存関係の追加

まずはUUIDv7を生成するためにjava-uuid-generatorライブラリを使用します。
Mavenを使用している場合、pom.xmlに以下を追加してください。

<dependency>
    <groupId>com.fasterxml.uuid</groupId>
    <artifactId>java-uuid-generator</artifactId>
    <version>5.1.0</version>
</dependency>

UUIDv7生成クラスを作成

以下のようにHibernateのIdentifierGeneratorを実装し、UUIDv7を生成するクラスを作成します。

import java.io.Serializable;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;

public class UuidV7Generator implements IdentifierGenerator {

    private static final TimeBasedEpochGenerator GENERATOR;

    static {
        GENERATOR = Generators.timeBasedEpochGenerator();
    }

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object obj) {
        return GENERATOR.generate();
    }
}

カスタムアノテーションを作成

エンティティで簡潔に利用できるようにするため、カスタムアノテーションを作成します。

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.annotations.IdGeneratorType;

@IdGeneratorType(UuidV7Generator.class)
@Retention(RUNTIME)
@Target({FIELD, METHOD})
public @interface GenerateUuidV7 {
}

エンティティでの利用

エンティティのIDフィールドにアノテーションを適用します。

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

import java.util.UUID;

@Entity
public class MyEntity {

    @Id
    @Column(columnDefinition = "BINARY(16)")
    @GenerateUuidV7
    private UUID id;

    // 他のフィールドやメソッド
}

MySQLはネイティブでUUID型をサポートしていないので今回はBINARY(16) でUUIDを保存しました。Hibernateで発行されるクエリで自動的にUUIDとBINARYの相互変換をしてくれたのでよかったです。

動作確認

エンティティを保存する際にUUIDv7が自動生成されます。

MyEntity entity = new MyEntity();
entityRepository.save(entity);
System.out.println("Generated ID: " + entity.getId());

生成されたUUIDはUUIDv7の形式に準拠し、順序付きの一意な値となります。

感想

UUIDv7を生成するライブラリはjava-uuid-generator以外にも沢山あるので別のものを利用しても大丈夫だと思います。
UUIDv7でもjava.util.UUIDはそのまま使えたので優秀だなと思いました。

jig.jp Engineers' Blog

Discussion