Что такое JPA Hibernate?
Hibernate является одним из самых популярных объектно-реляционных мапперов (ORM) для приложений на Java и Spring. Он помогает разработчикам подключаться к и работать с реляционными базами данных из приложений на 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 в качестве кэша уровня 2 и также для кэширования запросов. Используя внешний распределенный кэш-кластер, мы можем обеспечить кэширование и использование часто запрашиваемых сущностей в масштабируемой среде микросервисов без нагрузки на уровень базы данных. Таким образом, количество вызовов базы данных сводится к минимуму, а производительность приложения оптимизируется.
Для начала давайте добавим необходимые пакеты в наш проект spring boot. Для демонстрации я выберу JPA Repository, который использует Hibernate ORM для работы с реляционной базой данных — установкой MySQL.
Зависимости в моем файле 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 считывает и записывает в таблицу под названием books в моей базе данных MySQL. Репозиторий и сущность выглядят следующим образом:
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();
// вернуть сгенерированный идентификатор
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 мы добавим еще две зависимости в наш проект. Они показаны ниже:
<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, где настроим кэш второго уровня и детали ниже:
<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>
К вершине сущности Книга мы добавим аннотацию, которая установит статус кэша для сущности:
@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
Еще одна функция Hibernate, которую NCache полностью поддерживает, это кэширование запросов. В этом подходе результирующий набор запроса может быть кэширован для часто используемых данных. Это обеспечивает то, что база данных не запрашивается слишком часто для часто используемых данных. Это специфично для запросов 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, и набор результатов должен быть кэширован. Для этого я добавлю подсказку запроса, которая установит кэширование в истинное состояние.
Когда мы запустим API, который вызывает этот метод, мы увидим, что вся набор данных теперь кэширован.
Заключение
Кэширование является одной из наиболее используемых стратегий при создании распределенных приложений. В архитектуре микросервисов, где одно приложение масштабируется в X раз в зависимости от нагрузки, частое обращение к базе данных за данными может быть затратным.
Поставщики кэширования, такие как NCache, предоставляют простой и встраиваемый решение для Java микросервисов, использующих Hibernate для запросов к базам данных. В этой статье мы рассмотрели, как использовать NCache в качестве L2 кэша для Hibernate и использовать его для кэширования отдельных сущностей и кэширования запросов.
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac