ما هو JPA Hibernate؟
هابرنيت هي إحدى أكثر مكتبات خرائط الكائن إلى العلاقة (ORM) شعبية للتطبيقات اليافا وسبرينغ. إنها تساعد المطورين على التواصل مع قواعد البيانات العلائقية من التطبيقات اليافا دون الحاجة لكتابة عبارات SQL. تنفذ المكتبة ميضوع JPA (Java Persistence API) وتوفر العديد من الميزات الإضافية التي تساعد على تطوير البِيِّنات في التطبيقات بشكل أسرع وأسهل.
التخزين المؤقت في JPA Hibernate
إحدى الميزات الرائعة التي يدعمها هابرنيت هو التخزين المؤقت. يدعم هابرنيت مستويين من التخزين – L1 و L2. يتم تمكين ذاكرة التخزين المؤقت L1 افتراضيًا وتعمل داخل نطاق تطبيق معين، لذا لا يمكن مشاركته عبر متغيرات متعددة. على سبيل المثال، إذا كان لديك تطبيق ميكروسرفيس موزع يقرأ ويكتب في جدول في مجموعة بيانات علائقية، يتم الحفاظ على هذا التخزين المؤقت L1 بشكل فردي داخل كل من هذه الحاويات حيث يعمل الميكروسرفيس.
يُعتبر التخزين المؤقت L2 واجهة إضافية قابلة للإعداد، حيث يمكننا تخزين البيانات المُستهلكة بشكل متكرر في مزود تخزين مؤقت خارجي عبر هابرنيت. في هذه الحالة، يتم الحفاظ على التخزين المؤقت خارج الجلسة ويمكن مشاركته عبر مكونات الميكروسرفيس (في المثال أعلاه).
يدعم هابرنيت التخزين المؤقت L2 مع معظم مزودي التخزين المؤقت الشهير مثل ريديس، إنكايت، NCache، إلخ.
ما هو NCache؟
NCache هو من أكثر مزودي التخزين الموزع شهرة في السوق. يقدم العديد من الميزات ويدعم التكامل مع أنظمة البرمجة الشهيرة مثل .NET، Java، إلخ.
يأتي NCache بعدة أشكال — مفتوح المصدر، احترافي، وإنتربرايز ويمكنك الاختيار من بين هذه بناءً على الميزات التي يقدمونها.
دمج NCache مع Hibernate
يدعم NCache التكامل مع Hibernate كذاكرة ثانية وأيضاً لتخزين الاستعلامات. باستخدام خوادم ذاكرة موزعة خارجية، يمكننا التأكد من تخزين الكيانات التي يتم الوصول إليها بشكل متكرر واستخدامها عبر الخدمات الميكروسين في بيئة موزعة دون وضع أية حمل غير مرغوب فيه على طبقة قاعدة البيانات. بهذه الطريقة، يتم حفظ مكالمات قاعدة البيانات إلى حد ممكن، ويتم تحسين أداء التطبيق أيضاً.
للبدء، دعونا نضيف الحزم الضرورية إلى مشروع Spring Boot الخاص بنا. للتوضيح، سأستخدم مستودع JPA يستخدم 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>
مستودع JPA الخاص بي يقرأ ويكتب إلى جدول يسمى 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 كمزود.
التخزين من المستوى 2 باستخدام 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>
على رأس كيان Book، سنضيف تعليمة توجيه تحدد حالة التخزين للكيان:
@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
ميزة أخرى تدعمها NCache بالكامل في Hibernate هيتخزين الاستعلام. في هذا الأسلوب، يمكن تخزين مجموع نتائج الاستعلام للبيانات التي يتم الوصول إليها بشكل متكرر. وهذا يضمن عدم استعلام قاعدة البيانات بشكل متكرر للبيانات التي يتم الوصول إليها بشكل متكرر. وهذا محدد لاستعلامات HQL (لغة استعلام Hibernate).
لتمكين تخزين الاستعلام، سأضيف ببساطة سطرًا آخر في 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 كـ ذاكرة ثانية ل Hibernate واستخدامها لتخزين الكيانات الفردية وتخزين الاستعلامات.
Source:
https://dzone.com/articles/how-to-integrate-ncache-with-jpa-hibernate-for-cac