如何在Spring Boot应用中集成NCache与JPA Hibernate进行缓存

JPA Hibernate是什么?

Hibernate是Java和Spring应用中最流行的对象关系映射(ORM)库之一。它帮助开发者无需编写SQL查询即可从Java应用连接并操作关系数据库。该库实现了JPA(Java Persistence API)规范,并提供了多种额外功能,以加快并简化应用中的持久化开发。

JPA Hibernate中的缓存

Hibernate支持的一个亮点功能是缓存。Hibernate提供两级缓存——L1和L2。L1缓存默认启用,作用于应用范围内,因此无法跨多个线程共享。例如,在读写关系数据库表的微服务应用中,每个运行微服务的容器内都会独立维护这个L1缓存。

L2缓存是一个外部可插拔接口,通过它我们可以利用Hibernate在第三方缓存提供商中缓存频繁访问的数据。在这种情况下,缓存不在会话内维护,可以跨微服务栈共享(如上例所示)。

Hibernate与Redis、Ignite、NCache等大多数流行缓存提供商兼容,支持L2缓存。

什么是NCache?

NCache 是市场上最受欢迎的分布式缓存提供商之一。它提供多种功能,并支持与.NET、Java等流行编程栈的集成。

NCache 有多种版本——开源版、专业版和企业版,您可以根据它们提供的功能进行选择。

将NCache与Hibernate集成

NCache 支持与Hibernate集成作为二级缓存和查询缓存。通过使用外部分布式缓存集群,我们可以确保频繁访问的实体被缓存并在扩展环境中的微服务之间使用,而不会给数据库层带来不必要的负载。这样,数据库调用保持最小化,应用程序性能也得到优化。

要开始使用,让我们向我们的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