JPA Hibernateとは何か?
Hibernateは、JavaおよびSpringアプリケーション向けの最も人気のあるオブジェクトリレーショナルマッパー(ORM)ライブラリの一つです。開発者がJavaアプリケーションからリレーショナルデータベースに接続して操作するためにSQLクエリを書く必要がなくなります。このライブラリはJPA(Java Persistence API)仕様を実装し、アプリケーションの永続性をより迅速かつ容易に開発するための追加機能を提供します。
JPA Hibernateにおけるキャッシング
Hibernateがサポートするクールな機能の一つがキャッシングです。HibernateはL1およびL2の2つのレベルのキャッシングをサポートしています。L1キャッシュはデフォルトで有効になっており、アプリケーションスコープ内で機能するため、複数のスレッド間で共有することはできません。例えば、リレーショナルデータベースのテーブルに対して読み書きを行うスケールアウトされたマイクロサービスアプリケーションがある場合、このL1キャッシュはマイクロサービスが実行されている各コンテナ内で個別に維持されます。
L2キャッシュは外部のプラグインインターフェースであり、Hibernateを使用して頻繁にアクセスされるデータを外部キャッシングプロバイダにキャッシュできます。この場合、キャッシュはセッション外で管理され、マイクロサービススタック全体で共有できます(上記の例の場合)。
HibernateはRedis、Ignite、NCacheなどのほとんどの人気のあるキャッシングプロバイダでL2キャッシュをサポートしています。
NCacheとは何か?
NCacheは、市場で最も人気のある分散キャッシングプロバイダーの1つです。それはいくつかの機能を提供し、.NET、Javaなどの人気のプログラミングスタックとの統合をサポートしています。
NCacheにはオープンソース、プロフェッショナル、エンタープライズのいくつかのバージョンがあり、提供される機能に基づいて選択できます。
NCacheとHibernateの統合
NCacheは、L2キャッシュとクエリキャッシングのためにHibernateとの統合をサポートしています。外部の分散キャッシュクラスタを使用することで、頻繁にアクセスされるエンティティがキャッシュされ、スケールアウトされた環境でのマイクロサービス全体で使用されることを保証できます。この方法で、データベースへの呼び出しが最小限に抑えられ、アプリケーションのパフォーマンスも最適化されます。
始めるために、必要なパッケージをSpring Bootプロジェクトに追加しましょう。デモンストレーションのために、リレーショナルデータベースであるMySQLの設定を使用するJPAリポジトリを選択します。
pom.xmlファイル内の依存関係は次のようになります。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
私のJPARepositoryは、MySQLデータベース内のbooksというテーブルに読み書きします。リポジトリとエンティティは次のようになります。
package com.myjpa.helloapp.repositories;
import com.myjpa.helloapp.models.entities.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {}
package com.myjpa.helloapp.models.entities;
import jakarta.persistence.*;
import java.util.Date;
import org.hibernate.annotations.CreationTimestamp;
@Entity(name = "Book")
@Table(name = "Book")
public class Book {
@Id @GeneratedValue(strategy = GenerationType.AUTO) private int bookId;
@Column(name = "book_name") private String bookName;
@Column(name = "isbn") private String isbn;
@CreationTimestamp @Column(name = "created_date") private Date createdDate;
public Book() {}
public Book(String bookName, String isbn) {
this.bookName = bookName;
this.isbn = isbn;
}
public int getBookId() {
return bookId;
}
public String getBookName() {
return bookName;
}
public String getIsbn() {
return isbn;
}
public Date getCreatedDate() {
return createdDate;
}
}
A BookService
interacts with this repository and exposes GET
and INSERT
functionalities.
package com.myjpa.helloapp.services;
import com.myjpa.helloapp.models.entities.Book;
import com.myjpa.helloapp.repositories.BookRepository;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired
private BookRepository repository;
public int createNew(String bookName, String isbn) {
var book = new Book(bookName, isbn);
// エンティティを保存
repository.save(book);
// 変更をコミット
repository.flush();
// 生成されたIDを返す
var bookId = book.getBookId();
return bookId;
}
public Book findBook(int id) {
var entity = repository.findById(id);
if (entity.isPresent()) {
return entity.get();
}
return null;
}
}
この設定は完全に機能していますが、キャッシュを追加していません。NCacheをプロバイダーとしてHibernateにキャッシュを統合する方法を見てみましょう。
NCacheを使用したL2キャッシング
NCacheをHibernateと統合するために、プロジェクトにさらに2つの依存関係を追加します。以下に示します。
<dependency>
<groupId>com.alachisoft.ncache</groupId>
<artifactId>ncache-hibernate</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>6.4.2.Final</version>
</dependency>
また、Hibernate.cfg.xmlファイルを追加し、2次キャッシュと詳細を設定します。以下に示します。
<hibernate-configuration>
<session-factory>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">JCacheRegionFactory</property>
<property name="hibernate.javax.cache.provider" >com.alachisoft.ncache.hibernate.jcache.HibernateNCacheCachingProvider</property>
<property name="ncache.application_id">booksapi</property>
</session-factory>
</hibernate-configuration>
Bookエンティティの上部に、エンティティのキャッシュ状態を設定するアノテーションを追加します。
@Entity(name = "Book")
@Table(name = "Book")
@Cache(region = "demoCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Book {}
I’m indicating that my entities will be cached under the region demoCache
, which is basically my cache cluster name.
I’d also place my client.nconf and config.nconf files, which contain information about the cache cluster and its network details in the root directory of my project.
client.nconfは以下のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!-- Client configuration file is used by client to connect to out-proc caches.
Light weight client also uses this configuration file to connect to the remote caches.
This file is automatically generated each time a new cache/cluster is created or
cache/cluster configuration settings are applied.
-->
<configuration>
<ncache-server connection-retries="5" retry-connection-delay="0" retry-interval="1"
command-retries="3" command-retry-interval="0.1" client-request-timeout="90"
connection-timeout="5" port="9800" local-server-ip="192.168.0.108" enable-keep-alive="False"
keep-alive-interval="0" />
<cache id="demoCache" client-cache-id="" client-cache-syncmode="optimistic"
skip-client-cache-if-unavailable="False" reconnect-client-cache-interval="10"
default-readthru-provider="" default-writethru-provider="" load-balance="True"
enable-client-logs="True" log-level="info">
<server name="192.168.0.108" />
</cache>
</configuration>
この設定でアプリケーションを実行し、単一の書籍に対するGET操作を行うと、HibernateはNCacheクラスタでエンティティを検索し、キャッシュされたエンティティを返します。存在しない場合は、キャッシュミスが表示されます。
NCacheを使用したクエリキャッシング
NCacheが完全にサポートするHibernateのもう一つの機能はクエリキャッシングです。このアプローチでは、クエリの結果セットをキャッシュできます。これにより、頻繁にアクセスされるデータに対してデータベースが頻繁にクエリされることはありません。これはHQL(Hibernate Query Language)クエリに固有です。
クエリキャッシングを有効にするために、Hibernate.cfg.xmlに次の行を追加します:
<property name="hibernate.cache.use_query_cache">true</property>
リポジトリでは、特定のクエリを実行する別のメソッドを作成し、結果をキャッシュします。
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
@Query(value = "SELECT p FROM Book p WHERE bookName like 'T%'")
@Cacheable(value = "demoCache")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region = "demoCache")
@QueryHints(value = { @QueryHint(name = "org.hibernate.cacheable", value = "true") })
public List<Book> findAllBooks();
}
このメソッドでは、Tで始まるすべての書籍を照会し、結果セットをキャッシュするように設定します。これを行うために、キャッシングをtrueに設定するクエリヒントを追加します。
このメソッドを呼び出すAPIにアクセスすると、データセット全体がキャッシュされていることがわかります。
結論
キャッシングは、分散アプリケーションを構築する際に最も使用される戦略の1つです。マイクロサービスアーキテクチャでは、アプリケーションが負荷に基づいてX回スケールアウトされる場合、データのためにデータベースに頻繁にアクセスすることはコストがかかる可能性があります。
NCacheのようなキャッシングプロバイダは、Hibernateを使用してデータベースを照会するJavaマイクロサービスに簡単でプラグ可能なソリューションを提供します。この記事では、NCacheをHibernateのL2キャッシュとして使用し、個々のエンティティとクエリキャッシングをキャッシュする方法を見てきました。
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac