¿Qué es JPA Hibernate?
Hibernate es una de las bibliotecas de Mapeo Objeto-Relacional (ORM) más populares para aplicaciones Java y Spring. Ayuda a los desarrolladores a conectarse y trabajar con bases de datos relacionales desde aplicaciones Java sin tener que escribir consultas SQL. La biblioteca implementa la especificación JPA (Java Persistence API) y proporciona varias características adicionales que ayudan a desarrollar la persistencia en aplicaciones de manera más rápida y fácil.
Caché en JPA Hibernate
Una de las características interesantes que Hibernate soporta es la caché. Hibernate soporta dos niveles de caché: L1 y L2. La caché L1 está habilitada por defecto y funciona dentro del ámbito de una aplicación, por lo que no puede ser compartida entre múltiples hilos. Por ejemplo, si tienes una aplicación de microservicio escalada que lee y escribe en una tabla en una configuración de base de datos relacional, esta caché L1 se mantiene individualmente dentro de cada uno de estos contenedores donde se ejecuta el microservicio.
La caché L2 es una interfaz externa pluggable, mediante la cual podemos almacenar en caché datos accedidos con frecuencia en un proveedor de caché externo a través de Hibernate. En este caso, la caché se mantiene fuera de la sesión y puede ser compartida a través de la pila de microservicios (en el ejemplo anterior).
Hibernate soporta la caché L2 con la mayoría de los proveedores de caché populares como Redis, Ignite, NCache, etc.
¿Qué es NCache?
NCache es uno de los proveedores de caché distribuida más populares disponibles en el mercado. Ofrece varias características y soporte para la integración con pilas de programación populares como .NET, Java, etc.
NCache viene en varias versiones — de código abierto, profesional y empresarial, y puedes elegir entre ellas según las características que ofrecen.
Integrando NCache con Hibernate
NCache soporta integración con Hibernate como caché L2 y también para el almacenamiento en caché de consultas. Al utilizar un clúster de caché distribuida externo, podemos garantizar que las entidades accedidas con frecuencia se almacenen en caché y se utilicen entre los microservicios en un entorno escalado sin poner una carga no deseada en la capa de base de datos. De esta manera, las llamadas a la base de datos se mantienen lo más mínimas posible, y el rendimiento de la aplicación se optimiza también.
Para comenzar, agreguemos los paquetes necesarios a nuestro proyecto de Spring Boot. Para demostrar, voy a utilizar un JPA Repository que usa Hibernate ORM para trabajar con la base de datos relacional — configuración de MySQL.
Las dependencias en mi archivo pom.xml se ven así:
<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>
Mi JPARepository lee y escribe en una tabla llamada libros en mi base de datos MySQL. El repositorio y la entidad se ven de la siguiente manera:
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);
// guardar la entidad
repository.save(book);
// confirmar los cambios
repository.flush();
// devolver el id generado
var bookId = book.getBookId();
return bookId;
}
public Book findBook(int id) {
var entity = repository.findById(id);
if (entity.isPresent()) {
return entity.get();
}
return null;
}
}
Mientras que este arreglo funciona perfectamente bien, aún no hemos agregado ningún tipo de caché. Veamos cómo podemos integrar la caché a Hibernate con NCache como proveedor.
Caché de nivel 2 con NCache
Para Integrar NCache con Hibernate, añadiremos dos dependencias más a nuestro proyecto. Estas se muestran a continuación:
<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>
También añadiremos un archivo Hibernate.cfg.xml donde configuraremos la caché de segundo nivel y los detalles a continuación:
<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>
Además, en la entidad Book, añadiremos una anotación que establecerá el estado de la caché para la entidad:
@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.
El archivo client.nconf se ve de la siguiente manera:
<?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>
Cuando ejecuto mi aplicación con esta configuración y ejecuto una operación GET para un solo libro, Hibernate busca la entidad en el clúster de NCache y devuelve la entidad en caché; si no está presente, se produce un fallo de caché.
Caché de consultas con NCache
Otra característica de Hibernate que NCache admite completamente es la caché de consultas. Con este enfoque, el conjunto de resultados de una consulta puede almacenarse en caché para datos accedidos con frecuencia. Esto garantiza que la base de datos no sea consultada con frecuencia para datos accedidos con frecuencia. Esto es específico para consultas de HQL (Hibernate Query Language).
Para habilitar el almacenamiento en caché de consultas, simplemente agregaré otra línea al archivo Hibernate.cfg.xml a continuación:
<property name="hibernate.cache.use_query_cache">true</property>
En el repositorio, crearía otro método que ejecutará una consulta específica, y el resultado se almacenará en caché.
@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();
}
En este método, estoy consultando todos los libros que comienzan con la letra T, y el conjunto de resultados debe almacenarse en caché. Para ello, agregaré una sugerencia de consulta que establecerá el almacenamiento en caché en verdadero.
Cuando accedamos a la API que llama a este método, podemos ver que todo el conjunto de datos ahora está almacenado en caché.
Conclusión
El almacenamiento en caché es una de las estrategias más utilizadas en la construcción de aplicaciones distribuidas. En una arquitectura de microservicios, donde una aplicación se escala X veces según la carga, golpear frecuentemente una base de datos para obtener datos puede ser costoso.
Los proveedores de caché como NCache ofrecen una solución fácil y pluggable para los microservicios de Java que utilizan Hibernate para consultar bases de datos. En este artículo, hemos visto cómo usar NCache como Caché L2 para Hibernate y cómo utilizarlo para almacenar en caché entidades individuales y el almacenamiento en caché de consultas.
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac