Qu’est-ce que JPA Hibernate?
Hibernate est l’une des bibliothèques Object Relational Mapper (ORM) les plus populaires pour les applications Java et Spring. Elle aide les développeurs à se connecter et à travailler avec des bases de données relationnelles à partir d’applications Java sans avoir à écrire de requêtes SQL. La bibliothèque implémente la spécification JPA (Java Persistence API) et fournit plusieurs fonctionnalités supplémentaires qui facilitent le développement de la persistance dans les applications.
Mise en cache dans JPA Hibernate
L’une des fonctionnalités intéressantes prises en charge par Hibernate est la mise en cache. Hibernate prend en charge deux niveaux de cache — L1 et L2. Le cache L1 est activé par défaut et fonctionne dans le cadre d’une application, il ne peut donc pas être partagé entre plusieurs threads. Par exemple, si vous avez une application de microservices échelonnée qui lit et écrit dans une table d’un système de base de données relationnelle, ce cache L1 est maintenu individuellement dans chacun de ces conteneurs où le microservice est en cours d’exécution.
Le cache L2 est une interface externe pluggable, à l’aide de laquelle nous pouvons mettre en cache les données fréquemment consultées dans un fournisseur de cache externe via Hibernate. Dans ce cas, le cache est maintenu hors session et peut être partagé à travers la pile de microservices (dans l’exemple ci-dessus).
Hibernate prend en charge le cache L2 avec la plupart des fournisseurs de cache populaires comme Redis, Ignite, NCache, etc.
Qu’est-ce que NCache?
NCache est l’un des fournisseurs de mise en cache distribué les plus populaires disponibles sur le marché. Il offre de nombreuses fonctionnalités et prend en charge l’intégration avec des piles de programmation populaires comme .NET, Java, etc.
NCache propose plusieurs versions — open source, professionnel et entreprise, et vous pouvez choisir parmi celles-ci en fonction des fonctionnalités qu’elles offrent.
Intégration de NCache avec Hibernate
NCache prend en charge l’intégration avec Hibernate en tant que cache L2 et également pour le cache de requêtes. En utilisant un cluster de cache distribué externe, nous pouvons nous assurer que les entités fréquemment consultées sont mises en cache et utilisées à travers les microservices dans un environnement échelonné sans surcharger inutilement la couche de base de données. De cette manière, les appels à la base de données sont maintenus au minimum et la performance de l’application est également optimisée.
Pour commencer, ajoutons les paquets nécessaires à notre projet Spring Boot. Pour illustrer, je vais choisir un JPA Repository qui utilise Hibernate ORM pour interagir avec la base de données relationnelle — la configuration MySQL.
Les dépendances dans mon fichier pom.xml ressemblent à ceci:
<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>
Mon JPARepository lit et écrit dans une table appelée books dans ma base de données MySQL. Le dépôt et l’entité ressemblent à ce qui suit:
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);
// enregistrer l'entité
repository.save(book);
// valider les modifications
repository.flush();
// retourner l'identifiant généré
var bookId = book.getBookId();
return bookId;
}
public Book findBook(int id) {
var entity = repository.findById(id);
if (entity.isPresent()) {
return entity.get();
}
return null;
}
}
Bien que cette configuration fonctionne parfaitement, nous n’avons pas ajouté de mécanisme de mise en cache. Voyons comment intégrer le cache à Hibernate avec NCache en tant que fournisseur.
Mise en cache de niveau 2 avec NCache
Pour intégrer NCache avec Hibernate, nous ajouterons deux dépendances supplémentaires à notre projet. Elles sont présentées ci-dessous:
<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>
Nous ajouterons également un fichier Hibernate.cfg.xml où nous configurerons le cache de second niveau et les détails ci-dessous:
<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>
Au-dessus de l’entité Book, nous ajouterons une annotation qui définira le statut de cache pour l’entité:
@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.
Le fichier client.nconf ressemble à ceci:
<?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>
Lorsque j’exécute mon application avec cette configuration et que je fais une opération GET pour un livre unique, Hibernate recherche l’entité dans le cluster NCache et renvoie l’entité mise en cache ; si elle n’est pas présente, cela indique un raté de cache.
Mise en cache des requêtes avec NCache
Une autre fonctionnalité de Hibernate que NCache prend entièrement en charge est le cache des requêtes. Dans cette approche, le jeu de résultats d’une requête peut être mis en cache pour les données très consultées. Cela garantit que la base de données n’est pas interrogée fréquemment pour les données très consultées. Ceci est spécifique aux requêtes HQL (Hibernate Query Language).
Pour activer le cache de requêtes, je vais simplement ajouter une autre ligne dans le fichier Hibernate.cfg.xml ci-dessous :
<property name="hibernate.cache.use_query_cache">true</property>
Dans le dépôt, je créerais une autre méthode qui exécutera une requête spécifique, et le résultat sera mis en cache.
@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();
}
Dans cette méthode, je recherche tous les livres qui commencent par la lettre T, et le jeu de résultats doit être mis en cache. Pour cela, je vais ajouter un indice de requête qui définira le cache sur vrai.
Lorsque nous atteignons l’API qui appelle cette méthode, nous pouvons voir que l’ensemble du jeu de données est maintenant mis en cache.
Conclusion
Le cache est l’une des stratégies les plus utilisées dans la construction d’applications distribuées. Dans une architecture de microservices, où une application est mise à l’échelle X fois en fonction de la charge, la fréquente accès à une base de données pour récupérer des données peut être coûteux.
Les fournisseurs de cache comme NCache offrent une solution facile et pluggable aux microservices Java qui utilisent Hibernate pour interroger les bases de données. Dans cet article, nous avons vu comment utiliser NCache comme cache L2 pour Hibernate et l’utiliser pour mettre en cache des entités individuelles et le cache de requêtes.
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac