Wat is JPA Hibernate?
Hibernate is een van de meest populaire Object Relational Mapper (ORM) bibliotheken voor Java en Spring-toepassingen. Het helpt ontwikkelaars verbindingen tot stand brengen met en werken met relationele databases vanuit Java-toepassingen zonder SQL-query’s te hoeven schrijven. De bibliotheek implementeert de JPA (Java Persistence API) specificatie en biedt verschillende aanvullende functies die helpen persistentie in toepassingen sneller en gemakkelijker te ontwikkelen.
Caching in JPA Hibernate
Een van de coole functies die Hibernate ondersteunt, is caching. Hibernate ondersteunt twee niveaus van caching — L1 en L2. De L1 cache is standaard ingeschakeld en werkt binnen het bereik van een toepassing, dus kan deze niet worden gedeeld over meerdere threads. Bijvoorbeeld, als je een geschaalde microservicetoepassing hebt die leest en schrijft naar een tabel in een relationele database-setup, wordt deze L1 cache individueel onderhouden binnen elk van deze containers waar de microservicetoepassing draait.
De L2 cache is een externe pluggable interface, waarmee we vaak geaccesseerde gegevens in een externe caching provider via Hibernate kunnen cachen. In dit geval wordt de cache buiten de sessie onderhouden en kan deze worden gedeeld over de microservicestack (in bovenstaand voorbeeld).
Hibernate ondersteunt de L2 cache met de meeste populaire caching providers zoals Redis, Ignite, NCache, enz.
Wat is NCache?
NCache is een van de meest populaire aanbieders van gedistribueerde caching die op de markt beschikbaar zijn. Het biedt verschillende functies en ondersteunt integratie met populaire programmeerstacks zoals .NET, Java, enz.
NCache is beschikbaar in verschillende varianten — open source, professioneel en enterprise, en u kunt uit deze selecteren op basis van de functies die ze bieden.
Integratie van NCache met Hibernate
NCache ondersteunt integratie met Hibernate als L2 Cache en ook voor query caching. Door het gebruik van een extern gedistribueerd cachecluster kunnen we ervoor zorgen dat vaak gebruikte entiteiten worden gecached en gebruikt worden door microservices in een uitgebreide omgeving zonder onnodige belasting op de database-laag te zetten. Op deze manier worden de databaseoproepen zo minimaal mogelijk gehouden en wordt de applicatieprestaties ook geoptimaliseerd.
Om te beginnen, laten we de benodigde pakketten toevoegen aan ons spring boot project. Om te demonstreren ga ik uit van een JPA Repository die Hibernate ORM gebruikt om met de relationele database — MySQL setup te werken.
Afhankelijkheden in mijn pom.xml bestand ziet er zo uit:
<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>
Mijn JPARepository leest en schrijft naar een tabel genaamd boeken in mijn MySQL database. Het repository en de entiteit ziet er als volgt uit:
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);
// sla de entiteit op
repository.save(book);
// commit de wijzigingen
repository.flush();
// retourneer de gegenereerde 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;
}
}
Hoewel deze setup perfect werkt, hebben we hier geen caching toegevoegd. Laten we eens kijken hoe we caching kunnen integreren met Hibernate met NCache als provider.
L2 Caching met NCache
Om NCache met Hibernate te integreren, voegen we twee extra afhankelijkheden toe aan ons project. Deze zijn hieronder weergegeven:
<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>
We voegen ook een Hibernate.cfg.xml-bestand toe waarin we de tweede-laag cache configureren en details hieronder:
<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>
Bovendien voegen we boven de Entity Book een annotatie toe die de cachestatus voor de entiteit instelt:
@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.
De client.nconf ziet er als volgt uit:
<?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>
Wanneer ik mijn applicatie met deze setup uitvoer en een GET-operatie uitvoer voor een enkele boek, zoekt Hibernate de entiteit op in het NCache cluster en retourneert de gecacheerde entiteit; als deze niet aanwezig is, geeft het een cachemiss.
Query Caching met NCache
Een ander kenmerk van Hibernate dat NCache volledig ondersteunt, is query caching. Met deze aanpak kan de resultaatverzameling van een query worden gecacheerd voor vaak gebruikte gegevens. Dit zorgt ervoor dat de database niet vaak wordt geraadpleegd voor vaak gebruikte gegevens. Dit is specifiek voor HQL (Hibernate Query Language) queries.
Om Query Caching in te schakelen, voeg ik gewoon een extra regel toe aan het Hibernate.cfg.xml hieronder:
<property name="hibernate.cache.use_query_cache">true</property>
In het repository zou ik een andere methode aanmaken die een specifieke query uitvoert en het resultaat wordt gecachet.
@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();
}
In deze methode vraag ik naar alle boeken die beginnen met de letter T, en de resultatenreeks wordt gecachet. Hiervoor voeg ik een query-hint toe die de caching op true zet.
Wanneer we de API aanroepen die deze methode aanroept, kunnen we zien dat de hele gegevensset nu gecachet is.
Conclusie
Caching is een van de meest gebruikte strategieën bij het bouwen van gedistribueerde toepassingen. In een microservice-architectuur, waarbij een toepassing X keer wordt geschaald op basis van de belasting, kan het aanvallen van een database voor gegevens duur zijn.
Caching-providers zoals NCache bieden een eenvoudige en pluggable oplossing voor Java-microservices die Hibernate gebruiken voor het opvragen van databases. In dit artikel hebben we gezien hoe NCache kan worden gebruikt als een L2 Cache voor Hibernate en het gebruik ervan voor het cachen van individuele entiteiten en query-caching.
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac