如何在Spring Boot應用程式中整合NCache與JPA Hibernate進行快取

JPA Hibernate 是什麼?

Hibernate 是 Java 和 Spring 應用程式中最受歡迎的物件關係對映 (ORM) 庫之一。它幫助開發者從 Java 應用程式連接到並操作關聯式資料庫,而無需編寫 SQL 查詢。該庫實現了 JPA(Java Persistence API)規範,並提供了多種附加功能,幫助更快更輕鬆地開發應用程式的持久化部分。

JPA Hibernate 中的快取功能

Hibernate 支援的一項很酷的功能是快取。Hibernate 支援兩級快取 — L1 和 L2。L1 快取預設啟用,並在應用程式範圍內工作,因此無法跨多個執行緒共享。例如,如果您有一個擴展的微服務應用程式,該應用程式在關聯式資料庫設定中讀取和寫入表格,則此 L1 快取在每個容器中單獨維護,其中微服務正在運行。

L2 快取是一個外部可插拔接口,通過它我們可以使用 Hibernate 在外部快取提供者中快取經常訪問的數據。在這種情況下,快取是在會話外維護的,並且可以在微服務堆疊中共享(如上例所示)。

Hibernate 支援 L2 快取,並與大多數流行的快取提供者如 Redis、Ignite、NCache 等兼容。

什麼是 NCache?

NCache 是市場上最受歡迎的分佈式緩存供應商之一。它提供了多種功能,並支持與.NET、Java等流行編程堆棧的集成。

NCache 有多種版本 — 開源版、專業版和企業版,您可以根據它們提供的功能進行選擇。

將NCache與Hibernate集成

NCache支持與Hibernate集成作為L2緩存,也用於查詢緩存。通過使用外部分佈式緩存集群,我們可以確保經常訪問的實體被緩存並在擴展環境中的微服務之間使用,而不會對數據庫層施加不必要的負載。這樣,數據庫調用保持在最低限度,應用程序性能也得到了優化。

要開始,讓我們將必要的包添加到我們的Spring Boot項目中。為了演示,我選擇了一個使用Hibernate ORM與關係數據庫——MySQL設置工作的JPA Repository。

我的pom.xml文件中的依賴項如下所示:

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的表進行讀寫。倉庫和實體看起來如下:

Java

 

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> {}
Java

 

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.

Java

 

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;
  }
}

儘管此設置運作良好,但我們尚未加入任何快取機制。接下來,讓我們探討如何將快取整合到Hibernate中,並以NCache作為提供者。

NCache的二級快取

為了將NCache與Hibernate整合,我們將在我們的專案中新增兩個額外的依賴項。以下是這些依賴項的展示:

XML

 

<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文件,在其中配置二級快取及其詳細資訊如下:

XML

 

<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實體頂部,我們將添加一個註解,用於設定實體的快取狀態:

Java

 

@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

 

<?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查詢語言)查詢。

要啟用查詢緩存,我只需在Hibernate.cfg.xml中添加一行:

XML

 

<property name="hibernate.cache.use_query_cache">true</property>

在倉庫中,我會創建另一個方法,該方法將運行特定查詢,並將結果緩存起來。

Java

 

@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時,可以看到整個數據集現已緩存。

結論

緩存是構建分佈式應用程序中最常用的策略之一。在微服務架構中,根據負載將一個應用程序擴展到X倍時,頻繁地從數據庫獲取數據可能成本高昂。

NCache這樣的緩存提供商為使用Hibernate查詢數據庫的Java微服務提供了一個簡單且可插拔的解決方案。在本文中,我們已經看到如何將NCache用作Hibernate的二級緩存,並使用它來緩存個別實體和查詢結果。

Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac