Javaにおけるバージョン番号パターン:Javaアプリケーションにおける堅牢なバージョン管理の実装
別名
- エンティティのバージョン管理
- バージョン管理
バージョン番号デザインパターンの目的
並行データ管理の重要な要素であるバージョン番号を使用して変更を追跡することにより、Javaアプリケーションのデータの整合性と一貫性を確保します。
実際の例を用いたバージョン番号パターンの詳細な説明
実世界の例
複数の司書が同時に書籍の詳細を更新できる図書館システムを考えてみましょう。図書館のデータベースにある各書籍エントリには、バージョン番号が付いています。司書が書籍の詳細を更新する場合、システムはエントリのバージョン番号を確認します。バージョン番号がデータベースの現在のバージョンと一致する場合、更新は続行され、バージョン番号が増加します。バージョン番号が変更されている場合、別の司書が既に書籍の詳細を更新していることを意味し、システムは司書に競合を通知し、最新の変更を確認することを提案します。これにより、更新が誤って上書きされることがなく、データの整合性と一貫性が維持されます。
分かりやすく言うと
Javaのバージョン番号パターンは、同時更新に対する堅牢な保護を提供し、分散システムにおける信頼性の高いデータバージョン管理を保証します。
Wikipediaによると
バージョン番号パターンは、データベースやその他のデータストア内のデータへの同時アクセスを管理するために使用される手法です。レコードが更新されるたびに増加するバージョン番号を各レコードに関連付けます。このパターンは、複数のユーザーまたはプロセスが同時に同じデータを更新しようとするときに、競合を検出して解決するのに役立ちます。
Javaにおけるバージョン番号パターンのプログラム例
アリスとボブは、データベースに保存されている書籍に取り組んでいます。私たちのヒーローは同時に変更を加えており、互いに上書きしないようにするためのメカニズムが必要です。
バージョン管理され、コピーコンストラクタを持つ`Book`エンティティがあります。
@Getter
@Setter
public class Book {
private long id;
private String title = "";
private String author = "";
private long version = 0; // version number
public Book(Book book) {
this.id = book.id;
this.title = book.title;
this.author = book.author;
this.version = book.version;
}
}
また、並行性制御を実装する`BookRepository`もあります。
public class BookRepository {
private final Map<Long, Book> collection = new HashMap<>();
public void update(Book book) throws BookNotFoundException, VersionMismatchException {
if (!collection.containsKey(book.getId())) {
throw new BookNotFoundException("Not found book with id: " + book.getId());
}
var latestBook = collection.get(book.getId());
if (book.getVersion() != latestBook.getVersion()) {
throw new VersionMismatchException(
"Tried to update stale version " + book.getVersion()
+ " while actual version is " + latestBook.getVersion()
);
}
// update version, including client representation - modify by reference here
book.setVersion(book.getVersion() + 1);
// save book copy to repository
collection.put(book.getId(), new Book(book));
}
public Book get(long bookId) throws BookNotFoundException {
if (!collection.containsKey(bookId)) {
throw new BookNotFoundException("Not found book with id: " + bookId);
}
// return copy of the book
return new Book(collection.get(bookId));
}
}
バージョン番号パターンの動作例を次に示します。
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) throws
BookDuplicateException,
BookNotFoundException,
VersionMismatchException {
var bookId = 1;
var bookRepository = new BookRepository();
var book = new Book();
book.setId(bookId);
bookRepository.add(book); // adding a book with empty title and author
LOGGER.info("An empty book with version {} was added to repository", book.getVersion());
// Alice and Bob took the book concurrently
final var aliceBook = bookRepository.get(bookId);
final var bobBook = bookRepository.get(bookId);
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
bookRepository.update(aliceBook); // and successfully saved book in database
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
// now Bob has the stale version of the book with empty title and version = 0
// while actual book in database has filled title and version = 1
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
try {
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
bookRepository.update(bobBook); // Bob tries to save his book to database
} catch (VersionMismatchException e) {
// Bob update fails, and book in repository remained untouchable
LOGGER.info("Exception: {}", e.getMessage());
// Now Bob should reread actual book from repository, do his changes again and save again
}
}
}
プログラム出力
14:51:04.119 [main] INFO com.iluwatar.versionnumber.App -- An empty book with version 0 was added to repository
14:51:04.122 [main] INFO com.iluwatar.versionnumber.App -- Alice updates the book with new version 1
14:51:04.122 [main] INFO com.iluwatar.versionnumber.App -- Bob tries to update the book with his version 0
14:51:04.123 [main] INFO com.iluwatar.versionnumber.App -- Exception: Tried to update stale version 0 while actual version is 1
Javaでバージョン番号パターンを使用する場合
- 分散システムで同時データ変更を処理する必要がある場合に使用します。
- データの整合性と一貫性が重要なシステムに適しています。
- バージョン管理または行バージョン管理機能をサポートするデータベースを使用するアプリケーションに最適です。
バージョン番号パターン Javaチュートリアル
Javaにおけるバージョン番号パターンの実際のアプリケーション
- Hibernate(Java Persistence API)は、バージョン番号を使用して楽観的ロックを実装します。
- Microsoft SQL ServerおよびOracleデータベースは、バージョンベースの並行性制御をサポートしています。
- Apache CouchDBおよびその他のNoSQLデータベースは、競合解決のためにバージョン管理を実装しています。
- Elasticsearch
- Apache Solr
バージョン番号パターンの利点とトレードオフ
利点
- データの整合性と一貫性を向上させます。
- 同時実行環境での更新の損失の可能性を低減します。
- 競合の検出と解決のためのメカニズムを提供します。
トレードオフ
- バージョンチェックと競合処理のための追加ロジックが必要です。
- データベーススキーマとアプリケーションロジックの複雑さが増す可能性があります。
- バージョンチェックと競合解決によるパフォーマンスのオーバーヘッドが発生する可能性があります。
関連するJavaデザインパターン
- 楽観的オフラインロック:競合の発生を防ぐのではなく、バージョン番号を使用して競合を検出します。
- 悲観的オフラインロック:競合を防ぐためにデータが更新のためにロックされる、並行性制御の代替アプローチです。