4. JPA : خلاصهای
ما قصد داریم JPA (Java Persistence API) را با چند مثال معرفی کنیم. JPA در این دوره پوشش داده شده است:
- پایداری جاوا ۵ در عمل: [http://tahe.developpez.com/java/jpa] – ابزارهایی را برای ساخت لایه دسترسی به داده با JPA فراهم میکند
4.1. نقش JPA در یک معماری لایهای
از خوانندگان دعوت میشود تا به ابتدای این سند (بند ۲) بازگردند که نقش لایه JPA را در یک معماری لایهای توضیح میدهد. لایه JPA بخشی از لایههای دسترسی به دادهها را تشکیل میدهد:
![]() |
لایه [DAO] با مشخصه JPA رابطه برقرار میکند. صرفنظر از محصولی که آن را پیادهسازی میکند، رابط لایه JPA که به لایه [DAO] ارائه میشود، یکسان باقی میماند. در زیر چند مثال از [ref1] ارائه میشود که به ما امکان میدهد لایه JPA خود را بسازیم.
4.2. JPA – مثالها
4.2.1. مثال ۱ – نمایش شیء یک جدول واحد
4.2.1.1. جدول [personne]
بیایید یک پایگاه داده حاوی یک جدول واحد، [personne]، را در نظر بگیریم که هدف آن ذخیره برخی اطلاعات درباره افراد است:
![]() |
کلید اصلی جدول | |
نسخهٔ سطر در جدول. هر بار که جزئیات شخص اصلاح میشود، شمارهٔ نسخهٔ او افزایش مییابد. | |
نام شخص | |
نام | |
تاریخ تولد | |
یک عدد صحیح ۰ (مجرد) یا ۱ (متأهل) | |
تعداد فرزندان شخص |
4.2.1.2. واحد [Personne]
ما در محیط زمان اجرای زیر هستیم:
![]() |
لایه JPA [5] باید بهعنوان یک پل بین دنیای رابطهای پایگاه داده [7] و دنیای شیءگرا [4] که توسط برنامههای جاوا [3] مدیریت میشود، عمل کند.371ZQX. این پل از طریق پیکربندی ایجاد میشود و دو روش برای انجام این کار وجود دارد:
- با استفاده از فایلهای XML. این عملاً تنها راه انجام آن بود تا پیش از ظهور JDK 1.5
- استفاده از انوتیشنهای جاوا از JDK نسخه ۱.۵ به بعد
در این سند، ما فقط از روش دوم استفاده خواهیم کرد.
شیء [Personne] که نمایانگر جدول [personne] ارائهشده در بالا است، میتواند به شکل زیر باشد:
...
@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "DATENAISSANCE", nullable = false)
@Temporal(TemporalType.DATE)
private Date datenaissance;
@Column(name = "MARIE", nullable = false)
private boolean marie;
@Column(name = "NBENFANTS", nullable = false)
private int nbenfants;
//تولیدکنندگان
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
setNom(nom);
setPrenom(prenom);
setDatenaissance(datenaissance);
setMarie(marie);
setNbenfants(nbenfants);
}
//toString
public String toString() {
...
}
// گیرندهها و تنظیمکنندهها
...
}
پیکربندی با استفاده از @Annotationهای جاوا انجام میشود. آنونیشنهای جاوا یا توسط کامپایلر و یا توسط ابزارهای تخصصی در زمان اجرا پردازش میشوند. به جز آنونیشن روی خط ۳ که برای کامپایلر در نظر گرفته شده است، تمام آنونیشنهای دیگر در اینجا برای پیادهسازی JPA که در حال استفاده است، چه Hibernate و چه TopLink، در نظر گرفته شدهاند. بنابراین، آنها در زمان اجرا پردازش خواهند شد. در صورت عدم وجود ابزارهایی که قادر به تفسیر آنها باشند، این حاشیهنویسیها نادیده گرفته میشوند. بنابراین، کلاس [Personne] که در بالا نشان داده شده است، میتواند در زمینهای خارج از JPA استفاده شود.
باید بین دو سناریو برای استفاده از انوتیشنهای JPA در یک کلاس C مرتبط با جدول T تمایز قائل شد:
- جدول T از قبل وجود دارد: در این صورت، حاشیهنویسیهای JPA باید ساختار موجود (نامها و تعریف ستونها، محدودیتهای یکپارچگی، کلیدهای خارجی، کلیدهای اصلی و غیره) را بازتولید کنند.
- جدول T وجود ندارد و بر اساس حاشیهنویسیهای موجود در کلاس C ایجاد خواهد شد.
مورد دوم آسانترین مورد برای رسیدگی است. با استفاده از نشانهگذاریهای JPA، ساختار جدول T مورد نظر خود را مشخص میکنیم. مورد اول اغلب پیچیدهتر است. ممکن است جدول T مدتها پیش، خارج از هرگونه زمینه JPA ایجاد شده باشد. بنابراین ساختار آن ممکن است برای پل رابطهای/شیء JPA نامناسب باشد. برای سادهسازی موضوع، مورد دوم را در نظر میگیریم که در آن جدول T مرتبط با کلاس C بر اساس حاشیهنویسیهای JPA کلاس C ایجاد خواهد شد.
بیایید در مورد anotationهای JPA برای کلاس [Personne] توضیح دهیم:
- خط ۴: انوتیشن @Entity اولین انوتیشن ضروری است. این انوتیشن قبل از خط اعلام کلاس قرار میگیرد و نشان میدهد که کلاس مورد نظر باید توسط لایه پایداری JPA مدیریت شود. در صورت عدم وجود این انوتیشن، تمام انوتیشنهای دیگر JPA نادیده گرفته میشوند.
- خط ۵: آناوتیشن @Table جدول پایگاه دادهای را که کلاس نمایانگر آن است مشخص میکند. آرگومان اصلی آن `name` است که نام جدول را تعیین میکند. اگر این آرگومان حذف شود، نام جدول بر اساس نام کلاس انتخاب میشود، در این مورد [Personne]. بنابراین در مثال ما آناوتیشن @Table غیرضروری است.
- خط ۸: تگ @Id برای مشخص کردن فیلدی در کلاس که نمایانگر کلید اصلی جدول است، استفاده میشود. این تگ الزامی است. در اینجا، نشان میدهد که فیلد id در خط ۱۱ نمایانگر کلید اصلی جدول است.
- خط ۹: تگ @Column برای مرتبط کردن یک فیلد در کلاس با ستون جدول که آن فیلد نماینده آن است، استفاده میشود. ویژگی name نام ستون در جدول را مشخص میکند. اگر این ویژگی حذف شود، نام ستون با نام فیلد یکسان خواهد بود. در مثال ما، بنابراین آرگومان name الزامی نبود. آرگومان `nullable=false` مشخص میکند که ستون مرتبط با فیلد نمیتواند مقدار `NULL` را داشته باشد و بنابراین فیلد باید دارای مقدار باشد.
- خط ۱۰: تگ @GeneratedValue مشخص میکند که کلید اصلی چگونه تولید میشود، زمانی که توسط SGBD بهطور خودکار ایجاد میشود. این مورد در تمام مثالهای ما صادق است. این الزامی نیست. بنابراین، شخص ما میتواند شماره دانشجویی داشته باشد که به عنوان کلید اصلی عمل میکند و توسط SGBD تولید نمیشود، بلکه توسط برنامه تعیین میشود. در این صورت، حاشیهنویسی @GeneratedValue وجود نخواهد داشت. آرگومان `strategy` مشخص میکند که کلید اصلی چگونه توسط SGBD تولید میشود. همه SGBDها از یک روش یکسان برای تولید مقادیر کلید اصلی استفاده نمیکنند. برای مثال:
از یک تولیدکننده مقدار استفاده میکند که قبل از هر درج فراخوانی میشود | |
میدان کلید اصلی با نوع Identity تعریف شده است. نتیجه مشابه تولیدکننده مقدار Firebird است، با این تفاوت که مقدار کلید تنها پس از درج ردیف مشخص میشود. | |
از ابجکتی به نام SEQUENCE استفاده میکند که مجدداً به عنوان تولیدکننده مقدار عمل میکند |
لایه JPA باید بسته به SGBD، دستورات متفاوتی از SQL را برای ایجاد تولیدکننده مقدار ایجاد کند. از طریق پیکربندی به آن گفته میشود که چه نوع SGBD را باید مدیریت کند. در نتیجه، میتواند استراتژی استاندارد برای تولید مقادیر کلید اصلی برای آن SGBD را تعیین کند. آرگومان strategy = GenerationType.AUTO به لایه JPA دستور میدهد که از این استراتژی استاندارد استفاده کند. این تکنیک در تمام مثالهای این سند برای هفت نمونه SGBD مورد استفاده، کار کرده است.
- خط 14: تگ @Version فیلدی را که برای مدیریت دسترسی همزمان به همان سطر در جدول استفاده میشود، مشخص میکند.
برای درک این مسئله دسترسی همزمان به همان سطر در جدول [personne]، فرض کنیم یک برنامه وب اجازه میدهد جزئیات یک شخص بهروزرسانی شود و سناریوی زیر را در نظر بگیریم:
در زمان T1، کاربری به نام U1 وارد حالت ویرایش برای شخص P میشود. در این نقطه، تعداد فرزندان 0 است. آنها این عدد را به 1 تغییر میدهند، اما قبل از اینکه بتوانند تغییرات خود را ذخیره کنند، کاربری با شناسه U2 ویرایش همان شخص P را آغاز میکند. از آنجا که U1 هنوز تغییرات خود را ذخیره نکرده است، U2 میبیند که تعداد فرزندان روی صفحهاش به صورت 0 نمایش داده میشود. U2 نام شخص P را به حروف بزرگ تغییر میدهد. سپس U1 و U2 تغییرات خود را به ترتیب ذخیره میکنند. تغییر U2 غالب خواهد بود: در پایگاه داده، نام با حروف بزرگ نوشته میشود و تعداد فرزندان روی صفر باقی میماند، حتی اگر U1 فکر کند آن را به ۱ تغییر داده است.
مفهوم نسخهٔ یک شخص به ما کمک میکند این مشکل را حل کنیم. بیایید دوباره همان مورد استفاده را در نظر بگیریم:
در زمان T1، کاربری به نام U1 وارد حالت ویرایش برای شخص P میشود. در این نقطه، تعداد فرزندان 0 است و نسخه V1 است. آنها تعداد فرزندان را به 1 تغییر میدهند، اما قبل از اینکه تغییرات خود را ذخیره کنند، کاربری به نام U2 ویرایش همان شخص P را آغاز میکند. از آنجا که U1 هنوز تغییرات خود را ذخیره نکرده است، U2 تعداد فرزندان را 0 و نسخه را V1 میبیند. U2 نام شخص P را به حروف بزرگ تغییر میدهد. سپس U1 و U2 تغییرات خود را به ترتیب تأیید میکنند. قبل از تأیید یک تغییر، بررسی میشود تا اطمینان حاصل شود که کاربری که تغییر را برای شخص P اعمال میکند، نسخه یکسانی با نسخه فعلی ثبتشده برای شخص P دارد. این امر برای کاربر U1 صادق است. ویرایش آنها بنابراین پذیرفته میشود و نسخه شخص ویرایششده از V1 به V2 تغییر میکند تا نشان دهد که آن شخص دچار تغییر شده است. هنگام اعتبارسنجی ویرایش توسط U2، متوجه خواهیم شد که U2 دارای نسخه V1 از شخص P است، در حالی که نسخه فعلی شخص P، V2 است. سپس میتوانیم به کاربر U2 اطلاع دهیم که شخص دیگری قبلاً تغییراتی ایجاد کرده است و او باید از نسخه جدید شخص P دوباره شروع کند. او این کار را انجام خواهد داد، یک نسخه از شخص P (V2) را که اکنون یک فرزند دارد بازیابی میکند، نام را بزرگنویس میکند و تغییرات را تأیید میکند. تغییر آنها پذیرفته خواهد شد اگر شخص ثبتشده P هنوز نسخه V2 را داشته باشد. در نهایت، تغییرات انجامشده توسط U1 و U2 در نظر گرفته خواهند شد، در حالی که در مورد استفاده بدون نسخهها، یکی از تغییرات از دست رفته بود.
لایه [DAO] برنامهٔ مشتری میتواند نسخهٔ کلاس [Personne] را مدیریت کند. هرگاه یک شیء P تغییر کند، نسخهٔ آن شیء در جدول یک واحد افزایش مییابد. آنگشتنشان @Version اجازه میدهد این مدیریت به لایه JPA واگذار شود. فیلد مورد نظر لازم نیست مانند مثال، version نامیده شود. این فیلد میتواند هر نامی داشته باشد.
فیلدهای متناظر با آنوتیشنهای @Id و @Version برای اهداف پایداری (persistence) وجود دارند. اگر کلاس [Personne] نیازی به پایدارسازی نداشت، این فیلدها ضروری نبودند. بنابراین میتوانیم ببینیم که یک شیء بسته به اینکه نیاز به پایدارسازی داشته باشد یا نه، به شیوهای متفاوت نمایش داده میشود.
- خط ۱۷: بار دیگر، آناوتیشن @Column اطلاعاتی را در مورد ستون جدول [personne] که با فیلد nom از کلاس Personne مرتبط است، ارائه میدهد. در اینجا دو آرگومان جدید مییابیم:
- unique=true نشان میدهد که نام یک شخص باید منحصر به فرد باشد. این امر منجر به افزودن یک قید منحصر به فرد به ستون NOM در جدول [personne] میشود.
- length=30 تعداد کاراکترهای ستون NOM را روی 30 تنظیم میکند. این بدان معناست که نوع این ستون VARCHAR(30) خواهد بود.
- خط ۲۴: تگ @Temporal برای مشخص کردن نوع SQL برای یک ستون یا فیلد از نوع تاریخ/زمان استفاده میشود. نوع TemporalType.DATE تنها تاریخ را بدون زمان همراه نشان میدهد. سایر انواع ممکن عبارتند از TemporalType.TIME برای رمزگذاری زمان و TemporalType.TIMESTAMP برای رمزگذاری تاریخ و زمان.
اکنون بیایید درباره بقیه کد کلاس [Personne] توضیح دهیم:
- خط ۶: کلاس رابط Serializable را پیادهسازی میکند. sérialisation یک شیء شامل تبدیل آن به دنبالهای از بیتها است. désérialisation عملیات معکوس است. سریالسازی و دِسریالسازی بهویژه در برنامههای کلاینت–سرور که در آنها اشیاء از طریق شبکه مبادله میشوند، استفاده میشوند. برنامههای کاربردی کلاینت یا سرور از این عملیات بیخبر هستند، عملیاتی که بهصورت شفاف توسط JVM انجام میشود. با این حال، برای امکانپذیر شدن این کار، کلاسهای اشیاء مبادلهای باید با کلمه کلیدی Serializable «برچسبگذاری» شوند.
- خط ۳۷: یک سازنده برای کلاس. توجه داشته باشید که فیلدهای id و version در میان پارامترها گنجانده نشدهاند. این به آن دلیل است که این دو فیلد توسط لایه JPA مدیریت میشوند و نه توسط برنامه کاربردی.
- خطوط ۵۱ به بعد: متدهای get و set برای هر یک از فیلدهای کلاس. توجه داشته باشید که انوتیشنهای JPA ممکن است به جای خود فیلدها، روی متدهای get آنها قرار گیرند. قرارگیری آنوتیشنها، مدی را که JPA باید برای دسترسی به فیلدها استفاده کند، مشخص میکند:
- اگر انوتیشنها در سطح فیلد قرار گیرند، JPA مستقیماً به فیلدها دسترسی خواهد داشت تا آنها را بخواند یا بنویسد
- اگر حاشیهنویسیها در سطح get قرار گیرند، JPA برای خواندن یا نوشتن فیلدها از طریق متدهای get/set به آنها دسترسی خواهد داشت
موقعیت anotation @Id است که موقعیت anotationهای JPA را در یک کلاس تعیین میکند. وقتی در سطح فیلد قرار گیرد، نشاندهنده دسترسی مستقیم به فیلدها است؛ وقتی در سطح get قرار گیرد، نشاندهنده دسترسی به فیلدها از طریق متدهای get و set است. سایر آنوتیشنها باید به همان شیوه آنوتیشن @Id قرار گیرند.
4.2.2. پیکربندی لایه JPA
تستهای لایه JPA را میتوان با استفاده از معماری زیر انجام داد:
![]() |
- در [7]: پایگاهداده که از انوتیشنهای انتیت [Personne] و همچنین پیکربندیهای اضافی انجامشده در فایلی به نام [persistence.xml] ایجاد خواهد شد
- تا [5, 6]: لایه JPA که توسط Hibernate پیادهسازی شده است
- در [4]: انتیت [Personne]
- به [3]: یک برنامهٔ تست مبتنی بر کنسول
پیکربندی لایه JPA توسط فایل [META-INF/persistence.xml] انجام میشود:
![]() |
در زمان اجرا، فایل [META-INF/persistence.xml] در Classpath برنامه جستجو میشود.
بیایید پیکربندی لایه JPA را که در فایل [persistence.xml] پروژه ما تعریف شده است، بررسی کنیم:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<!-- ارائهدهنده -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<!-- کلاسهای پایدار -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- لاگها SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- اتصال JDBC -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="hibernate.connection.username" value="jpa" />
<property name="hibernate.connection.password" value="jpa" />
<!-- ایجاد خودکار طرحواره -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- گویش -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!-- properties DataSource c3p0 -->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
برای درک این پیکربندی، باید به معماری دسترسی به دادههای برنامهمان نگاه کنیم:
![]() |
- فایل [persistence.xml] لایههای [4, 5, 6] را پیکربندی خواهد کرد
- [4]: پیادهسازی Hibernate از JPA
- [5]: Hibernate از طریق یک استخر اتصال به پایگاه داده دسترسی پیدا میکند. استخر اتصال، مخزنی از اتصالات باز به SGBD است. به یک SGBD توسط چندین کاربر دسترسی پیدا میشود، هرچند به دلایل عملکردی، نمیتواند از حد N برای تعداد اتصالات همزمان فراتر رود. کد خوب نوشته شده، یک اتصال به SGBD را برای حداقل زمان ممکن باز میکند: دستورات SQL را صادر کرده و اتصال را میبندد. این کار را هر زمان که نیاز به کار با پایگاه داده داشته باشد، به طور مکرر انجام میدهد. هزینه باز و بسته کردن یک اتصال ناچیز نیست و اینجاست که استخر اتصال وارد عمل میشود. هنگامی که برنامه اجرا میشود، استخر اتصال، اتصالات N1 را به SGBD باز میکند. برنامه هر زمان که به یک اتصال نیاز داشته باشد، یک اتصال باز را از استخر درخواست میکند. این اتصال به محض اینکه برنامه دیگر به آن نیازی نداشته باشد، ترجیحاً در سریعترین زمان ممکن، به استخر بازگردانده میشود. اتصال بسته نمیشود و برای کاربر بعدی در دسترس باقی میماند. بنابراین، استخر اتصالات سیستمی برای اشتراکگذاری اتصالات باز است.
- [6]: درایور JDBC برای دستگاه SGBD
اکنون بیایید ببینیم که فایل [persistence.xml] چگونه لایههای [4, 5, 6] را در بالا پیکربندی میکند:
- خط ۲: تگ ریشه فایل XML، <persistence> است.
- خط ۳: <persistence-unit> برای تعریف یک واحد پایداری استفاده میشود. ممکن است چندین واحد پایداری وجود داشته باشد. هر یک از آنها یک نام (ویژگی 'name') و یک نوع تراکنش (ویژگی 'transaction-type') دارد. برنامه از طریق نام واحد پایداری به آن دسترسی پیدا میکند، که در این مورد 'jpa' است. نوع تراکنش RESOURCE_LOCAL نشان میدهد که برنامه خود با استفاده از SGBD تراکنشها را مدیریت میکند. در اینجا نیز همینطور خواهد بود. هنگامی که برنامه در یک کانتینر EJB3 اجرا میشود، میتواند از سرویس تراکنش آن کانتینر استفاده کند. در این مورد، ما transaction-type=JTA (Java Transaction API) را تنظیم خواهیم کرد. JTA مقدار پیشفرض زمانی است که ویژگی transaction-type حذف شود.
- خط ۵: تگ <provider> برای تعریف کلاسی استفاده میشود که رابط [javax.persistence.spi.PersistenceProvider] را پیادهسازی میکند و به برنامه امکان میدهد لایه پایداری را راهاندازی کند. از آنجا که ما از پیادهسازی JPA/Hibernate استفاده میکنیم، کلاسی که در اینجا به کار رفته یک کلاس Hibernate است.
- خط ۶: تگ <properties> ویژگیهای خاص provider انتخابشده را معرفی میکند. بنابراین، بسته به اینکه Hibernate، Toplink، Kodo و غیره انتخاب شده باشد، ویژگیهای متفاوتی اعمال خواهند شد. موارد زیر مربوط به Hibernate هستند.
- خط ۸: به Hibernate دستور میدهد تا classpath پروژه را اسکن کند تا کلاسهای دارای @Entity را پیدا کند تا بتوان آنها را مدیریت کرد. کلاسهای @Entity را میتوان با استفاده از تگهای <class>nom_de_la_classe</class>، مستقیماً در داخل تگ <persistence-unit> نیز تعریف کرد. این کاری است که ما با پروژه provider JPA / Toplink انجام خواهیم داد.
- خطوط ۱۰ تا ۱۲ که در اینجا با کامنت غیرفعال شدهاند، لاگهای کنسول Hibernate را پیکربندی میکنند:
- خط ۱۰: برای فعال یا غیرفعال کردن نمایش دستورات SQL که توسط Hibernate بر روی SGBD صادر میشوند. این امر در مرحله یادگیری بسیار مفید است. به دلیل پل رابطهای/شیءگرا، برنامه روی اشیاء پایدار کار میکند و عملیات از نوع [persist, merge, remove] را روی آنها اعمال میکند. دانستن اینکه کدام دستورات SQL در واقع برای این عملیات صادر میشوند، بسیار مفید است. با مطالعهٔ آنها، شما به تدریج شروع به استخراج دستورات SQL میکنید که Hibernate هنگام انجام یک عملیات خاص روی اشیاء پایدار تولید خواهد کرد، و پل رابطهای-به-شیء در ذهن شما شکل میگیرد.
- خط ۱۱: دستورات SQL نمایشدادهشده در کنسول را میتوان بهخوبی قالببندی کرد تا خواندن آنها آسانتر شود
- خط ۱۲: دستورات نمایشدادهشده SQL نیز حاشیهنویسی خواهند شد
- خطوط ۱۵–۱۹ لایه JDBC را تعریف میکنند (لایه [6] در معماری):
- خط ۱۵: کلاس درایور JDBC برای SGBD، در اینجا MySQL5
- خط ۱۶: URL پایگاه داده مورد استفاده
- خطوط 17 و 18: نام کاربری و رمز عبور اتصال
- خط ۲۲: Hibernate باید بداند که با کدام SGBD در حال کار است. این به این دلیل است که تمام SGBDها دارای افزونههای اختصاصی SQL هستند، یک روش خاص برای مدیریت تولید خودکار مقادیر کلید اصلی، ... که به این معنی است که Hibernate باید SGBD مورد نظر را بشناسد تا بتواند دستورات SQL را که برای آن قابل فهم است، ارسال کند. [MySQL5InnoDBDialect] به SGBD و MySQL5 با جداول از نوع InnoDB که از تراکنشها پشتیبانی میکنند، اشاره دارد.
- خطوط ۲۴ تا ۲۸ استخر اتصالات c3p0 (لایه [5] در معماری) را پیکربندی میکنند:
- خطوط ۲۴ و ۲۵: حداقل (پیشفرض ۳) و حداکثر تعداد اتصالات (پیشفرض ۱۵) در استخر. تعداد اولیه پیشفرض اتصالات ۳ است.
- خط ۲۶: حداکثر زمان انتظار، به میلیثانیه، برای درخواست اتصال از سمت کلاینت. پس از گذشت این زمان، c3p0 یک استثنا برمیگرداند.
- خط ۲۷: برای دسترسی به BD، Hibernate از دستورات آماده SQL (PreparedStatement) استفاده میکند که c3p0 میتواند آنها را در کش ذخیره کند. این بدان معناست که اگر برنامه برای بار دوم یک دستور SQL آماده شده را که از قبل در کش موجود است درخواست کند، نیازی به آمادهسازی مجدد آن نخواهد بود (آمادهسازی یک دستور SQL هزینه در بر دارد) و دستور موجود در کش استفاده خواهد شد. در اینجا، حداکثر تعداد سفارشهای آمادهشده SQL که کش میتواند در تمام اتصالات نگه دارد، مشخص میشود (یک سفارش آمادهشده SQL متعلق به یک اتصال واحد است).
- خط ۲۸: فرکانس، به میلیثانیه، که در آن اتصالات از نظر اعتبار بررسی میشوند. یک اتصال در استخر ممکن است به دلایل مختلف نامعتبر شود (درایور JDBC اتصال را به دلیل فعال بودن برای مدت طولانی نامعتبر میکند، درایور JDBC دارای «باگ» است، و غیره).
- خط ۲۰: این مشخص میکند که هنگام راهاندازی واحد پایداری، طرحواره پایگاه داده برای اشیاء @Entity باید تولید شود. Hibernate اکنون تمام ابزارهای لازم برای صدور دستورات تولید جداول پایگاه داده را در اختیار دارد:
- پیکربندی اشیاء @Entity به آن امکان میدهد تا جدولهای مورد نظر را شناسایی کند.
- خطوط ۱۵–۱۸ و ۲۴–۲۸ به آن امکان میدهند تا یک اتصال با SGBD برقرار کند
- خط ۲۲ به آن میگوید که از کدام گویش SQL برای تولید جداول استفاده کند
بنابراین، فایل [persistence.xml] مورد استفاده در اینجا، هر بار که برنامه اجرا میشود یک پایگاه داده جدید ایجاد میکند. جداول پس از حذف شدن (drop table)، در صورتی که قبلاً وجود داشته باشند، دوباره ایجاد میشوند (create table). توجه داشته باشید که بدیهی است این کار نباید روی یک پایگاه داده تولیدی انجام شود...
4.2.3. مثال ۲: رابطه یکبهچند
4.2.3.1. شمای پایگاه داده « »
1 ![]() | 2 |
- در [1]، پایگاه داده، و در [2]، DDL آن (MySQL5)
یک آیتم A(id, version, name) دقیقاً به یک دسته C(id, version, name) تعلق دارد. یک دسته C میتواند صفر، یک یا چند آیتم را در خود داشته باشد. یک رابطه یکبهچند (Category → Item) و رابطه معکوس چندبهیک (Item → Category) وجود دارد. این رابطه با کلید خارجی در جدول [article] که به جدول [categorie] ارجاع میدهد، نشان داده شده است (سطور 24–28 از DDL).
4.2.3.2. ابجکتهای @Entity که نمایندهٔ پایگاه داده هستند
یک مقاله با @Entity زیر [Article] نمایش داده میشود:
package entites;
...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {
// fields
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// رابطهٔ اصلی: مقاله (چند) → دستهبندی (یک)
// از طریق یک کلید خارجی (categorie_id) در مقاله پیادهسازی شده است
// ۱. یک مقاله باید یک دستهبندی داشته باشد (nullable=false)
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "categorie_id", nullable = false)
private Categorie categorie;
// سازندهها
public Article() {
}
// گیرنده و تنظیمکننده
...
// toString
public String toString() {
return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
}
}
- خطوط ۹–۱۱: کلید اصلی @Entity
- خطوط ۱۳–۱۵: شماره نسخه آن
- خطوط 17–18: نام آیتم
- خطوط ۲۰–۲۵: یک رابطهٔ چند به یک که @Entity Article را به @Entity Categorie مرتبط میکند:
- خط ۲۳: تفسیر ManyToOne. «Many» به @Entity Article که در آن قرار داریم اشاره دارد و «One» به @Entity Categorie (خط 25) اشاره میکند. یک دستهبندی (One) میتواند چندین آیتم (Many) داشته باشد.
- خط ۲۴: حاشیهنویسی ManyToOne ستون کلید خارجی را در جدول [article] تعریف میکند. این (name) categorie_id نامگذاری خواهد شد و هر ردیف باید در این ستون (nullable=false) دارای یک مقدار باشد.
- خط ۲۵: دستهای که آیتم به آن تعلق دارد. هنگامی که یک آیتم در زمینه پایداری قرار میگیرد، درخواستی ارسال میشود تا دستهبندی آن بلافاصله اضافه نشود (fetch=FetchType.LAZY, خط ۲۳). مشخص نیست که آیا این درخواست منطقی است یا خیر. خواهیم دید.
یک دستهبندی با @Entity زیر نمایش داده میشود: [Categorie]:
package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {
// fields
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// رابطه معکوس دستهبندی (یک) -> مقاله (بسیار) از رابطه مقاله (بسیار) -> دستهبندی (یک)
// درج آبشاری: دستهبندی -> درج مقالات
// بهروزرسانی آبشاری: دستهبندی -> بهروزرسانی مقالات
// حذف آبشاری: دستهبندی → مقالات
@OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
private Set<Article> articles = new HashSet<Article>();
// سازندهها
public Categorie() {
}
// گیرندهها و تنظیمکنندهها
...
// toString
public String toString() {
return String.format("Categorie[%d,%d,%s]", id, version, nom);
}
// ارتباط دوطرفه دستهبندی <--> مقاله
public void addArticle(Article article) {
// مورد به مجموعه موارد در دستهبندی اضافه میشود
articles.add(article);
// مقاله دستهبندی خود را تغییر میدهد
article.setCategorie(this);
}
}
- خطوط ۸–۱۱: کلید اصلی @Entity
- خطوط ۱۲–۱۴: نسخهٔ آن
- خطوط ۱۶–۱۷: نام دستهبندی
- خطوط ۱۹–۲۴: مجموعه مقالات در دستهبندی
- خط ۲۳: تگ @OneToMany نشاندهنده یک رابطه یکبهچند است. «One» به @Entity [Categorie] که در آن قرار داریم اشاره دارد، و «Many» به نوع [Article] در خط ۲۴ اشاره دارد: یک (One) دستهبندی دارای مقالات متعددی (Many) است.
- خط ۲۳: این حاشیهنویسی معکوس (mappedBy) حاشیهنویسی ManyToOne است که بر روی فیلد categorie از @Entity Article اعمال شده است: mappedBy=category. رابطه ManyToOne که بر روی فیلد categorie از @Entity Article تعریف شده است، رابطه اصلی است. این رابطه ضروری است. این رابطه، رابطه کلید خارجی را نشان میدهد که @Entity Article را به @Entity Categorie متصل میکند. رابطه OneToMany، که در فیلد articles از @Entity Categorie تعریف شده است، رابطه معکوس است. این رابطه ضروری نیست. این یک قابلیت تسهیلی برای بازیابی آیتمها از یک دستهبندی است. بدون این قابلیت، این آیتمها از طریق یک پرسوجو JPQL بازیابی میشدند.
- خط ۲۳: cascadeType.ALL مشخص میکند که عملیات (persist, merge, remove) که بر روی یک @Entity Categorie انجام میشود باید به مقالات آن نیز سرایت کند.
- خط ۲۴: آیتمهای یک دستهبندی در یک شی از نوع `Set<Article>` قرار داده میشوند. نوع `Set` اجازه تکرار را نمیدهد. بنابراین، یک آیتم یکسان نمیتواند دو بار در شی `Set<Article>` قرار گیرد. «آیتم یکسان» به چه معناست؟ برای نشان دادن اینکه آیتم a با آیتم b یکسان است، جاوا از عبارت a.equals(b) استفاده میکند. در کلاس Object، که والد همه کلاسها است، a.equals(b) اگر a==b باشد، true است، c.a.d. اگر اشیاء a و b در همان مکان حافظه قرار داشته باشند. ممکن است بخواهیم بگوییم که آیتمهای a و b اگر نام یکسانی داشته باشند، یکسان هستند. در این صورت، توسعهدهنده باید دو متد را در کلاس [Article] override کند:
- equals: که باید مقدار true را بازگرداند اگر دو آیتم نام یکسانی داشته باشند
- hashCode: باید برای دو شیء [Article] که متد equals آنها را برابر میداند، یک مقدار عددی یکسان بازگرداند. در اینجا، مقدار بنابراین از نام آیتم ساخته خواهد شد. مقدار بازگشتی hashCode میتواند هر عدد صحیحی باشد. این مقدار در مخازن شیء مختلف، بهویژه دیکشنریها (Hashtable)، استفاده میشود.
رابطه OneToMany ممکن است برای ذخیره «بسیاری» (Many)، از نوعهای دیگری غیر از Set، مانند اشیاء List استفاده کند. ما در این سند این موارد را پوشش نخواهیم داد. خواننده میتواند آنها را در [ref1] بیابد.
- خط ۳۸: متد [addArticle] به ما امکان میدهد یک مقاله را به یک دستهبندی اضافه کنیم. این متد تضمین میکند که هر دو انتهای رابطه OneToMany که [Categorie] را به [Article] متصل میکند، بهروزرسانی شوند.
4.3. API در لایه JPA
بیایید محیط زمان اجرای یک مشتری JPA را توضیح دهیم:
![]() |
ما میدانیم که لایه JPA یک پل شیء-رابطهای بین [3] و [4] ایجاد میکند. مجموعهٔ اشیایی که توسط لایهٔ JPA در چارچوب این پل شیء-رابطهای مدیریت میشوند، «زمینهٔ پایداری» نامیده میشود. برای دسترسی به دادهها در زمینه پایداری، یک کلاینت JPA [1] باید از طریق لایه JPA [2] اقدام کند:
- میتواند یک شیء ایجاد کند و از لایه JPA بخواهد آن را پایدار سازد. سپس این شیء بخشی از زمینه پایداری میشود.
- میتواند از لایه [JPA] یک مرجع به یک شی پایدار موجود درخواست کند.
- میتواند یک شیء پایدار را که از لایه JPA به دست آورده است، تغییر دهد.
- میتواند از لایه JPA بخواهد یک شیء را از زمینه پایداری حذف کند.
لایه JPA رابطی به نام [EntityManager] در اختیار کلاینت قرار میدهد که همانطور که از نامش پیداست، برای مدیریت اشیاء @Entity در زمینه پایداری استفاده میشود. متدهای اصلی این رابط در زیر آورده شدهاند:
افزودن entity به زمینه پایداری | |
حذف entity از زمینه پایداری | |
یک شیء entity از سمت کلاینت را که توسط زمینه پایداری مدیریت نمیشود، ادغام میکند، با شیء entity از زمینه پایداری که دارای کلید اولیه یکسان است. نتیجهٔ بازگرداندهشده، شیء entity از زمینهٔ پایداری است. | |
یک شیء بازیابیشده از پایگاه داده را قرار میدهد از طریق کلید اصلی آن. نوع شیء T لایه JPA را فعال میکند تا بداند کدام جدول را استعلام کند. شیء پایدار ایجادشده به این ترتیب به کلاینت بازگردانده میشود. | |
یک شیء پرسوجو را از یک پرسوجوی JPQL ایجاد میکند (پایداری جاوا زبان پرسوجو). یک پرسوجو JPQL مشابه یک پرسوجو SQL است اگر با این تفاوت که به جای جداول، اشیاء را پرسوجو میکند. | |
یک روش مشابه مورد قبلی، با این تفاوت که queryText یک SQL به جای JPQL. | |
این روش با createQuery یکسان است، با این تفاوت که ترتیب JPQL queryText دارد به یک فایل پیکربندی منتقل شده و نامی به آن اختصاص داده شده است. این نام است که بهعنوان پارامتر متد عمل میکند. |
یک شیء EntityManager چرخه عمر دارد که لزوماً با چرخه عمر برنامه یکسان نیست. این شیء یک شروع و یک پایان دارد. بنابراین، یک کلاینت JPA میتواند به طور متوالی با شیءهای EntityManager مختلف کار کند. زمینه پایداری مرتبط با یک EntityManager چرخه عمر یکسانی با خود شیء دارد. این دو از یکدیگر جداییناپذیرند. هنگامی که یک شیء EntityManager بسته میشود، زمینه پایداری آن در صورت لزوم با پایگاه داده همگامسازی شده و سپس از بین میرود. برای بهدست آوردن یک زمینه پایداری جدید، باید یک EntityManager جدید ایجاد شود.
کلاینت JPA میتواند با استفاده از عبارت زیر یک EntityManager – و در نتیجه یک زمینه پایداری – ایجاد کند:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance");
- javax.persistence.Persistence یک کلاس ایستا است که برای بهدستآوردن یک فابریک برای اشیاء EntityManager استفاده میشود. این فابریک به یک واحد پایداری خاص متصل است. همانطور که میدانیم، فایل پیکربندی [META-INF/persistence.xml] برای تعریف واحدهای پایداری استفاده میشود و این واحدها نامی دارند:
<persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">
در مثال بالا، واحد پایداری elections-dao-jpa-mysql-01PU نامیده میشود. این واحد دارای پیکربندی خاص خود است، بهویژه SGBD که با آن کار میکند. دستور [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] یک کارخانهٔ شیء از نوع EntityManagerFactory ایجاد میکند که قادر به ارائهٔ اشیاء EntityManager است و برای مدیریت زمینههای پایداری مرتبط با واحد پایداری با نام elections-dao-jpa-mysql در نظر گرفته شده است.-01PU. یک شیء EntityManager و در نتیجه یک زمینه پایداری، از شیء EntityManagerFactory به شرح زیر به دست میآید:
از متدهای زیر از رابط [EntityManager] برای مدیریت چرخه عمر زمینه پایداری استفاده میشود:
زمینه پایداری بسته میشود. باعث همگامسازی اجباری زمینه پایداری با پایگاه داده میشود:
| |
شاهدباقیماندن (persistence context) از تمام اشیاء خود تخلیه میشود اما بسته نمیشود. | |
زمینه پایداری همانطور که برای close() توصیف شده است، با پایگاه داده همگامسازی میشود. |
کلاینت JPA میتواند با استفاده از متد [EntityManager] همگامسازی زمینه پایداری با پایگاه داده را اجباری کند. flush (قبلی). همگامسازی میتواند صریح یا ضمنی باشد. در حالت اول، مشتری موظف است عملیات flush را هر زمان که بخواهد همگامسازی انجام دهد، فراخوانی کند؛ در غیر این صورت، این عملیات در زمانهای مشخصی که بعداً تعیین خواهیم کرد، انجام میشوند. حالت همگامسازی توسط متدهای زیر از رابط [EntityManager] مدیریت میشود:
دو مقدار ممکن برای flushmode وجود دارد: FlushModeType.AUTO (پیشفرض): همگامسازی قبل از انجام میشود هر پرسوجوی SELECT که به پایگاه داده ارسال میشود. FlushModeType.COMMIT: همگامسازی تنها در زمان پایان تراکنشها در پایگاه داده. | |
حالت همگامسازی فعلی را برمیگرداند |
برای خلاصه کردن. در حالت FlushModeType.AUTO که حالت پیشفرض است، زمینه پایداری در زمانهای زیر با پایگاه داده همگامسازی میشود:
- قبل از هر عملیات SELECT روی پایگاه داده
- در پایان یک تراکنش در پایگاه داده
- پس از یک عملیات flush یا close روی زمینه پایداری
در حالت FlushModeType.COMMIT، رفتار یکسان است، با این تفاوت که عملیات ۱ انجام نمیشود. حالت استاندارد تعامل با لایه JPA، حالت تراکنشی است. کلاینت عملیات مختلفی را در داخل یک تراکنش روی زمینه پایداری انجام میدهد. در این حالت، نقاطی که زمینه پایداری با پایگاه داده همگامسازی میشود، موارد ۱ و ۲ بالا در حالت AUTO و فقط مورد ۲ در حالت COMMIT هستند.
در پایان، با فرمان API از رابط پرسوجو (Query interface) خاتمه میدهیم که به شما امکان میدهد فرمانهای JPQL را به زمینه پایداری (persistence context) یا فرمانهای SQL را مستقیماً به پایگاه داده صادر کنید تا دادهها را بازیابی کنید. رابط پرسوجو به شرح زیر است:
![]() |
- ۱ – متد getResultList یک SELECT را اجرا میکند که چندین شیء را بازمیگرداند. این موارد در یک شیء List به دست میآیند. این شیء یک رابط است. این شیء یک شیء Iterator را فراهم میکند که به شما امکان میدهد تا عناصر لیست L را به شکل زیر پیمایش کنید:
Iterator iterator = L.iterator();
while (iterator.hasNext()) {
// شیء iterator.next() را فراخوانی کنید که نماینده آیتم فعلی در لیست است
...
}
لیست L را میتوان با استفاده از یک for نیز پردازش کرد:
for (Object o : L) {
//از شیء o استفاده کنید
}
- ۲ – متد getSingleResult یک فرمان JPQL / SQL SELECT را اجرا میکند که یک شیء واحد را بازمیگرداند.
- ۳ - متد executeUpdate یک دستور UPDATE یا DELETE را اجرا میکند و تعداد ردیفهای تحت تأثیر عملیات را برمیگرداند.
- ۴ - متد setParameter(String, Object) امکان اختصاص یک مقدار به یک پارامتر نامگذاریشده در یک دستور JPQL پیکربندیشده را فراهم میکند
- ۵ - متد setParameter(int, Object) به پارامتر بر اساس نام آن ارجاع نمیدهد، بلکه بر اساس موقعیت آن در دستور JPQL عمل میکند.
4.4. پرسوجوهای در JPQL
JPQL (زبان پرسوجوی پایداری جاوا) زبان پرسوجوی لایه JPA است. زبان JPQL مشابه زبان SQL است که در پایگاههای داده استفاده میشود. در حالی که SQL با جداول کار میکند، JPQL با اشیاء تصویری درون آن جداول کار میکند. ما یک مثال را در معماری زیر بررسی خواهیم کرد:
![]() |
پایگاه داده، که آن را [dbrdvmedecins2] مینامیم، یک پایگاه داده MySQL5 است که شامل چهار جدول میباشد:
![]() |
این پایگاه داده حاوی اطلاعاتی است که برای مدیریت قرارهای ملاقات گروهی از پزشکان استفاده میشود.
4.4.1. جدول [MEDECINS]
این شامل اطلاعات مربوط به پزشکان است.
![]() | ![]() |
- ID: شماره شناسایی پزشک – کلید اصلی جدول
- VERSION: شمارهای که نسخهٔ سطر در جدول را شناسایی میکند. این شماره هر بار که تغییری در سطر ایجاد میشود، یک واحد افزایش مییابد.
- NOM: نام خانوادگی پزشک
- PRENOM: نام کوچک آنها
- TITRE: عنوان آنها (خانم، بانو، آقای)
4.4.2. جدول [CLIENTS]
بیماران پزشکان مختلف در جدول [CLIENTS] ثبت میشوند:
![]() | ![]() |
- ID: شماره شناسه مشتری – کلید اصلی جدول
- VERSION: شمارهای که نسخهٔ سطر در جدول را شناسایی میکند. این شماره هر بار که تغییری در سطر اعمال میشود، یک واحد افزایش مییابد.
- NOM: نام خانوادگی مشتری
- PRENOM: نام کوچک آنها
- TITRE: عنوان آنها (خانم، بانو، آقا)
4.4.3. جدول [CRENEAUX]
این جدول بازههای زمانی را که ثبتنام در RV امکانپذیر است، فهرست میکند:
![]() |
![]() |
- ID: شمارهای که جایگاه زمانی را شناسایی میکند – کلید اصلی جدول (ردیف 8)
- VERSION: شمارهای که نسخهٔ سطر در جدول را شناسایی میکند. این شماره هر بار که تغییری در سطر اعمال میشود، ۱ واحد افزایش مییابد.
- ID_MEDECIN: شمارهای که پزشک مربوط به این شیفت را شناسایی میکند – کلید خارجی روی ستون MEDECINS (QZXX2HTMLP000733ZQX).
- HDEBUT: زمان شروع اسلات
- MDEBUT: دقیقه شروع اسلات
- HFIN: زمان پایان اسلات
- MFIN: دقایق پایان اسلات
ردهی دوم جدول [CRENEAUX] (به [1] بالا مراجعه کنید) نشان میدهد، برای مثال، که نوبت شمارهٔ ۲ از ساعت ۸:۲۰ شروع و در ساعت ۸:۴۰ پایان مییابد و به پزشک شمارهٔ ۱ اختصاص داده شده است. (خانم ماری PELISSIER).
4.4.4. جدول [RV]
ورودیهای RV را برای هر پزشک فهرست میکند:
![]() |
- ID: شماره یکتا که RV را شناسایی میکند – کلید اصلی
- JOUR: روز RV
- ID_CRENEAU: بازه زمانی برای RV – کلید خارجی در فیلد [ID] در جدول [CRENEAUX] – هم بازه زمانی و هم پزشک مربوطه را مشخص میکند.
- ID_CLIENT: شماره مشتری که رزرو برای او انجام میشود – کلید خارجی روی فیلد [ID] در جدول [CLIENTS]
این جدول دارای محدودیت یکتایی بر روی « » برای مقادیر در ستونهای الحاقی (JOUR, ID_CRENEAU) است:
اگر یک سطر در جدول [RV] دارای مقدار (JOUR1, ID_CRENEAU1) برای ستونها (JOUR, ID_CRENEAU)، این مقدار نباید در هیچ جای دیگری ظاهر شود. در غیر این صورت، این بدان معناست که دو رکورد RV همزمان برای یک پزشک ثبت شدهاند. از دیدگاه برنامهنویسی جاوا، درایور پایگاه داده JDBC هنگام وقوع این امر، یک SQLException را فعال میکند.
ورودی مربوط به id که برابر با ۳ است (رجوع شود به [1] بالا)، نشان میدهد که یک RV برای اسلات شماره ۲۰ و مشتری شماره ۴ در تاریخ ۲۳ اوت ۲۰۰۶ رزرو شده است. جدول [CRENEAUX] به ما میگوید که نوبت شمارهٔ ۲۰ معادل بازهٔ زمانی ۱۶:۲۰–۱۶:۴۰ است و متعلق به پزشک شمارهٔ ۱ (خانم ماری PELISSIER) میباشد. جدول [CLIENTS] نشان میدهد که مشتری شمارهٔ ۴ خانم بریژیت BISTROU است.
4.4.5. ایجاد پایگاه داده
برای ایجاد جداول و پر کردن آنها میتوانید از اسکریپت [dbrdvmedecins2.sql] استفاده کنید. با [WampServer] میتوانید به شرح زیر عمل کنید:
![]() |
- در [1]، روی آیکون [WampServer] کلیک کرده و گزینه [PhpMyAdmin] [2] را انتخاب کنید،
- در [3]، در پنجرهای که باز شده است، پیوند [Bases de données] را انتخاب کنید،
![]() |
- به [2]، یک پایگاه داده با نام [4] با کدگذاری [5] ایجاد کنید،
- در [7]، پایگاه داده ایجاد شده است. روی لینک آن کلیک کنید،
![]() |
- در [8]، ما یک فایل SQL را وارد میکنیم،
- که آن را با استفاده از دکمه [9] از سیستم فایل انتخاب میکنید،
![]() |
- در [11]، اسکریپت SQL را انتخاب کرده و در [12] آن را اجرا کنید،
- در [13]، چهار جدول پایگاه داده ایجاد شدهاند. یکی از پیوندها را دنبال کنید،
![]() |
- در [14]، محتویات جدول.
پس از این به این پایگاه داده بازنمیگردیم. با این حال، از خواننده دعوت میشود تا روند توسعه آن را با پیشرفت برنامهها دنبال کند، بهویژه زمانی که کارها به درستی پیش نمیروند.
4.4.6. لایه [JPA]
بیایید به معماری مثال بازگردیم:
![]() |
ما اکنون در حال ساخت پروژه Maven برای لایه [JPA] هستیم.
4.4.7. پروژه NetBeans
به شرح زیر است:
![]() |
- در [1]، ما یک پروژه Maven از نوع [Java Application] [2]،
- در [3]، به پروژه یک نام میدهیم،
![]() |
- در [4]، پروژهٔ تولیدشده.
4.4.8. ایجاد لایه [JPA]
بیایید به معماریای که باید بسازیم بازگردیم:
![]() |
با NetBeans میتوان بهطور خودکار لایه [JPA] را تولید کرد. آشنایی با این روشهای تولید خودکار مفید است، زیرا کد تولیدشده بینشهای ارزشمندی درباره نحوه نوشتن اجزای JPA ارائه میدهد.
4.4.9. ایجاد یک اتصال NetBeans به پایگاه داده
- SGBD MySQL 5 را اجرا کنید تا BD در دسترس باشد،
- یک اتصال NetBeans به پایگاه داده [dbrdvmedecins2] ایجاد کنید،
![]() |
- در برگه [Services] [1]، در شاخه [Databases] [2]، درایور JDBC MySQL [3] را انتخاب کنید،
- سپس گزینه «اتصال با استفاده از» [4] را برای ایجاد یک اتصال به پایگاه داده MySQL انتخاب کنید،
- در [5]، اطلاعات مورد درخواست را وارد کنید. در [6]، نام پایگاه داده را وارد کنید؛ در [7]، نام کاربری و رمز عبور پایگاه داده را وارد کنید؛
- در [8]، میتوانید جزئیاتی را که ارائه کردهاید، آزمایش کنید،
- در [9]، پیامی که در صورت صحیح بودن جزئیات انتظار دارید مشاهده کنید،
![]() |
- در [10]، اتصال برقرار میشود. چهار جدول در پایگاه داده متصل نمایش داده میشوند.
4.4.10. ایجاد یک واحد پایداری
بیایید به معماری که در حال حاضر در دست ساخت است بازگردیم:
![]() |
ما در حال حاضر در حال ساخت لایه [JPA] هستیم. پیکربندی آن در فایلی به نام [persistence.xml] تعریف شده است که در آن واحدهای پایداری تعریف میشوند. هر یک از این موارد به اطلاعات زیر نیاز دارد:
- اطلاعات دسترسی به پایگاه داده (JDBC, URL, نام کاربری، رمز عبور)،
- کلاسهایی که نمایانگر جداول پایگاه داده خواهند بود،
- پیادهسازی JPA مورد استفاده. JPA یک مشخصه است که توسط محصولات مختلف پیادهسازی میشود. در اینجا، از Hibernate استفاده خواهیم کرد.
NetBeans میتواند این فایل پایداری را با استفاده از یک جادوگر تولید کند.
![]() |
- روی پروژه کلیک راست کرده و «ایجاد یک واحد پایداری» [1] را انتخاب کنید،
- در [2]، یک واحد پایداری ایجاد کنید،
![]() |
- در [3]، برای واحد پایداری که ایجاد میکنید، نامی انتخاب کنید،
- در [4]، پیادهسازی Hibernate JPA (JPA 2.0) را انتخاب کنید،
- در [5]، مشخص کنید که جداول در BD قبلاً ایجاد شدهاند و بنابراین نباید دوباره ایجاد شوند. ویزارد را تأیید کنید،
- در [6]، پروژه جدید،
- در [7]، فایل [persistence.xml] در پوشه [META-INF] ایجاد شده است،
- در [8]، وابستگیهای جدید به پروژه Maven اضافه شدهاند.
فایل تولیدشده [META-INF/persistence.xml] به شرح زیر است:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
این فایل حاوی اطلاعاتی است که در ویزارد ارائه شده است:
- خط ۳: نام واحد پایداری،
- خط ۳: نوع تراکنشهای پایگاه داده. در اینجا، RESOURCE_LOCAL نشان میدهد که برنامه تراکنشهای خود را مدیریت خواهد کرد،
- خطوط ۶–۹: ویژگیهای JDBC منبع داده.
در برگه [Design] میتوانید نمایی کلی از فایل [persistence.xml] را مشاهده کنید:
![]() |
برای بهدستآوردن لاگهای Hibernate، فایل [persistence.xml] را به شرح زیر تکمیل میکنیم:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
- خط ۱۱: ما درخواست مشاهده دستورات SQL صادر شده توسط Hibernate را داریم،
- خط ۱۲: این خاصیت اجازه میدهد که این موارد بهصورت قالببندیشده نمایش داده شوند.
وابستگیها به پروژه اضافه شدهاند. فایل [pom.xml] به شرح زیر است:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-rdvmedecins-jpql-hibernate</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.1_spec</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
</project>
وابستگیهای اضافه شده همگی مربوط به Hibernate (ORM) هستند. ما وابستگی درایور JDBC را از MySQL اضافه خواهیم کرد:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
4.4.11. تولید اشیاء JPA
JPA میتوان واحدها را با استفاده از یک جادوگر NetBeans تولید کرد:
![]() |
- در [1]، اشیاء JPA از یک پایگاه داده ایجاد میشوند،
![]() |
- در [2]، اتصال ایجاد شده در مرحله قبل ([dbrdvmedecins2]) را انتخاب کنید،
- در [3]، تمام جداول پایگاه داده مرتبط را انتخاب کنید،
![]() |
- در [4]، برای کلاسهای جاوا که با چهار جدول مرتبط هستند، نامی تعیین کنید،
- و همچنین نام پکیج [5]،
- در [6]، JPA سطرهای جدول را از BD در قالب مجموعهها گروهبندی میکند. ما یک لیست را به عنوان مجموعه انتخاب میکنیم،
![]() |
- در [7]، کلاسهای جاوایی که توسط جادوگر ایجاد شدهاند.
4.4.12. اشیاء تولیدشده JPA
اینتیتی [Medecin] نمایانگر جدول [medecins] است. کلاس جاوا مملو از آنوتیشنها است که خواندن کد را در نگاه اول دشوار میکند. اگر تنها آنچه برای درک نقش اینتیتی ضروری است را حفظ کنیم، کد زیر به دست میآید:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
// سازندهها
....
// گیرندهها و تنظیمکنندهها
....
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- در خط ۴، anotation @Entity کلاس [Medecin] را به همراه JPA و c.a.d به عنوان یک انتیت (entity) تعیین میکند. یک کلاس مرتبط با جدولی برای BD از طریق API و JPA،
- خط ۵، نام جدول BD که با انتیتي JPA مرتبط است. هر فیلد در جدول با یک فیلد در کلاس جاوا مطابقت دارد؛
- خط ۶، کلاس رابط Serializable را پیادهسازی میکند. این امر در برنامههای کاربردی کلاینت/سرور ضروری است، جایی که اشیاء بین کلاینت و سرور سریالیزه میشوند.
- خطوط ۱۰–۱۱: فیلد id کلاس [Medecin] با فیلد [ID] (خط ۱۰) در جدول [medecins] مطابقت دارد،
- خطوط ۱۳–۱۴: فیلد «title» کلاس [Medecin] با فیلد [TITRE] (خط ۱۳) در جدول [medecins] مطابقت دارد،
- ردههای ۱۶–۱۷: فیلد «name» در کلاس [Medecin] با فیلد [NOM] (ردهی ۱۶) در جدول [medecins] مطابقت دارد،
- ردههای ۱۹–۲۰: فیلد «نسخه» در کلاس [Medecin] با فیلد [VERSION] (ردهی ۱۹) در جدول [medecins] مطابقت دارد. در اینجا، جادوگر تشخیص نمیدهد که این ستون در واقع یک ستون نسخه است که باید هر بار که ردیف مربوطه تغییر میکند، افزایش یابد. برای اختصاص این نقش به آن، باید تگ @Version اضافه شود. ما این کار را در مرحله بعدی انجام خواهیم داد،
- خطوط 22–23: فیلد 'first_name' از کلاس [Medecin] با فیلد [PRENOM] در جدول [medecins] مطابقت دارد،
- خطوط ۱۰–۱۱: فیلد id با کلید اصلی [ID] جدول مطابقت دارد. حاشیهنویسیهای روی خطوط ۸–۹ این نکته را روشن میکنند،
- ردیف ۸: حاشیه @Id نشان میدهد که فیلد حاشیهدار شده با کلید اصلی جدول مرتبط است،
- خط ۹: لایه [JPA] کلید اصلی را برای سطرهایی که در جدول [Medecins] درج میکند، تولید خواهد کرد. چندین استراتژی ممکن وجود دارد. در اینجا، استراتژی GenerationType.IDENTITY نشان میدهد که لایه JPA از حالت auto_increment جدول MySQL استفاده خواهد کرد،
- خطوط ۲۵–۲۶: جدول [creneaux] دارای یک کلید خارجی است که به جدول [medecins] ارجاع میدهد. یک اسلات متعلق به یک پزشک است. برعکس، یک پزشک چندین اسلات مرتبط با خود دارد. بنابراین ما یک رابطه یکبهچند (یک پزشک به چندین اسلات) داریم، رابطهای که توسط انوتیشن @OneToMany از طریق JPA (خط 25) تعریف شده است. میدان در خط 26 شامل تمام بازههای زمانی دکتر خواهد بود. این کار بدون هیچ برنامهنویسی انجام میشود. برای درک کامل خط 25، باید کلاس [Creneau] را معرفی کنیم.
این به شرح زیر است:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
private List<Rv> rvList;
// سازندهها
...
// گیرنده و تنظیمکننده
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
ما فقط در مورد حاشیهنویسیهای جدید نظر میدهیم:
- ما بیان کردهایم که جدول [creneaux] یک کلید خارجی به جدول [medecins] دارد: یک اسلات با یک پزشک مرتبط است. چندین اسلات ممکن است با یک پزشک مرتبط باشند. از جدول [creneaux] به جدول [medecins] رابطهای وجود دارد که به صورت چند (فاصله زمانی) به یک (پزشک) تعریف شده است. این حاشیهنویسی @ManyToOne در خط ۳۲ است که برای تعریف کلید خارجی استفاده میشود،
- خط ۳۱، با حاشیهنویسی @JoinColumn، رابطه کلید خارجی را مشخص میکند: ستون [ID_MEDECIN] در جدول [creneaux] یک کلید خارجی بر روی ستون [ID] در جدول [medecins] است،
- خط ۳۳: مرجعی به پزشکی که اسلات را در اختیار دارد. این نیز بدون هیچ برنامهنویسی بهدست میآید.
بنابراین رابطه کلید خارجی بین انتیت [Creneau] و انتیت [Medecin] توسط دو انوتیشن تعریف میشود:
- در موجودیت [Creneau]:
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
- در موجوده [Medecin]:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
هر دو نشانهگذاری، یک رابطه را منعکس میکنند: رابطه کلید خارجی از جدول [creneaux] به جدول [medecins]. گفته میشود که این دو معکف یکدیگر هستند. فقط رابطه @ManyToOne ضروری است. این رابطه به طور صریح رابطه کلید خارجی را تعریف میکند. رابطه @OneToMany اختیاری است. اگر موجود باشد، صرفاً به رابطه @ManyToOne که با آن مرتبط است ارجاع میدهد. این معنی ویژگی mappedBy در خط ۱ از موجودیت [Medecin] است. مقدار این ویژگی، نام فیلدی در موجودیت [Creneau] است که دارای نشانهگذاری @ManyToOne است، که کلید خارجی را مشخص میکند. همچنان در همین خط 1 از موجودیت [Medecin]، ویژگی cascade=CascadeType.ALL رفتار موجوده [Medecin] را در رابطه با موجوده [Creneau] تعیین میکند:
- اگر یک موجودیت جدید [Medecin] در پایگاه داده درج شود، آنگاه موجودیتهای [Creneau] در فیلد روی خط 2 نیز باید درج شوند،
- اگر یک موجودیت [Medecin] در پایگاه داده اصلاح شود، آنگاه موجودیتهای [Creneau] در فیلد روی خط 2 نیز باید اصلاح شوند،
- اگر یک موجودیت [Medecin] از پایگاه داده حذف شود، آنگاه موجودیتهای [Creneau] در فیلد روی خط ۲ نیز باید حذف شوند.
ما کد دو موجودیت دیگر را بدون هیچگونه توضیحات خاص ارائه میدهیم، زیرا آنها هیچ نشانه جدیدی معرفی نمیکنند.
وجود [Client]
package rdvmedecins.jpa;
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
private List<Rv> rvList;
// سازندهها
...
// گیرنده و تنظیمکننده
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- خطوط ۲۴–۲۵ رابطه کلید خارجی بین جدول [rv] و جدول [clients] را نشان میدهند.
واحد [Rv]:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client idClient;
// سازندگان
...
//گیرنده و تنظیمکننده
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- ردیف ۱۳ فیلد «day» با نوع Java را در Date مشخص میکند. این نشان میدهد که در جدول [rv]، ستون [JOUR] (ردیف ۱۲) از نوع تاریخ (بدون زمان) است،
- ردههای ۱۶–۱۸: رابطه کلید خارجی را از جدول [rv] به جدول [creneaux] تعریف میکنند،
- خطوط ۲۰–۲۲: رابطه کلید خارجی را از جدول [rv] به جدول [clients] تعریف میکند.
تولید خودکار اشیاء JPA یک پایهٔ کاری در اختیار ما قرار میدهد. گاهی این کافی است، گاهی کافی نیست. در اینجا چنین است:
- ما باید انوتیشن @Version را به فیلدهای نسخهٔ مختلف این اِنتِیتیها اضافه کنیم،
- ما باید متدهای toString را بنویسیم که صریحتر از متدهای تولیدشده باشند،
- اشیاء [Medecin] و [Client] مشابه هستند. ما آنها را از یک کلاس [Personne] مشتق خواهیم کرد،
- ما روابط معکوس @OneToMany روابط @ManyToOne را حذف خواهیم کرد. این روابط ضروری نیستند و باعث پیچیدگیهای برنامهنویسی میشوند،
- و ما اعتبارسنجی @NotNull را روی کلیدهای اصلی حذف میکنیم. هنگام پایدارسازی یک موجودیت JPA با MySQL، موجودیت اصلی دارای کلید اصلی null است. فقط پس از پایداری در پایگاه داده است که کلید اصلی عنصر پایدار شده، مقدار مییابد.
با این مشخصات، کلاسهای مختلف به شرح زیر هستند:
کلاس Person برای نمایش پزشکان و مشتریان استفاده میشود:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM")
private String prenom;
// سازندگان
...
// گیرنده و تنظیمکننده
...
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
- خط ۶: توجه کنید که کلاس [Personne] خود یک انتیت نیست (@Entity). این کلاس، کلاس والد انتیتها خواهد بود. انوتیشن @MappedSuperClass این وضعیت را نشان میدهد.
انتیتی [Client] سطرهای جدول [clients] را در بر میگیرد. این کلاس از کلاس قبلی [Personne] ارث میبرد:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// سازندگان
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
- خط ۶: کلاس [Client] یک انتیت JPA است،
- خط ۷: این کلاس با جدول [clients] مرتبط است،
- خط ۸: از کلاس [Personne] مشتق میشود.
اِنتیتی [Medecin] که سطرهای جدول [medecins] را در بر میگیرد، از همان الگو پیروی میکند:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// سازندهها
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
اِنتیتهی [Creneau] سطرهای جدول [creneaux] را در بر میگیرد:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// سازندهها
...
// گیرندهها و تنظیمکنندهها
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
//TODO: هشدار – این روش در صورتی که فیلدهای id تنظیم نشده باشند کار نخواهد کرد
...
}
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
- ردههای ۴۰ تا ۴۲ رابطه «چند به یک» بین جدول [creneaux] و جدول [medecins] در پایگاه داده را مدلسازی میکنند: یک پزشک چندین نوبت ملاقات دارد، و یک نوبت ملاقات به یک پزشک واحد تعلق دارد.
اِنتیتهی [Rv] سطرهای جدول [rv] را در بر میگیرد:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// سازندهها
...
// گیرندهها و تنظیمکنندهها
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
- ردههای ۲۷–۲۹ رابطه «چند به یک» بین جدول [rv] و جدول [clients] را مدلسازی میکنند (یک مشتری ممکن است در چندین Rv ظاهر شود) در پایگاه داده، در حالی که خطوط ۲۳–۲۵ رابطه «چند به یک» بین جدول [rv] و جدول [creneaux] را مدلسازی میکنند (یک بازه زمانی ممکن است در چندین Rv ظاهر شود).
4.4.13. کد دسترسی به دادهها
اکنون کد دسترسی به دادهها از طریق لایه JPA را به پروژه اضافه میکنیم:
![]() |
![]() |
کلاس [MainJpql] به شرح زیر است:
package rdvmedecins.console;
import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class MainJpql {
public static void main(String[] args) {
// EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
// entityManager
EntityManager em = emf.createEntityManager();
// اسکنر صفحهکلید
Scanner clavier = new Scanner(System.in);
//حلقه ورود پرسوجو JPQL
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
String requete = clavier.nextLine();
while (!requete.trim().equals("*")) {
try {
// نمایش نتیجه پرسوجو
for (Object o : em.createQuery(requete).getResultList()) {
System.out.println(o);
}
} catch (Exception e) {
System.out.println("L'exception suivante s'est produite : " + e);
}
// پاک کردن زمینه پایداری
em.clear();
// پرسش جدید
System.out.println("---------------------------------------------");
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
requete = clavier.nextLine();
}
// بستن منابع
em.close();
emf.close();
}
}
- خط ۱۲: ایجاد EntityManagerFactory مرتبط با واحد پایداری که قبلاً ایجاد کردیم. پارامتر متد createEntityManagerFactory نام این واحد پایداری است:
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- خط ۱۴: ایجاد EntityManager، که لایه پایداری را مدیریت میکند،
- خط ۱۹: وارد کردن یک پرسوجو JPQL select،
- خطوط ۲۳–۲۸: نمایش نتیجهٔ پرسوجو،
- خط ۲۰: ورودی زمانی متوقف میشود که کاربر * را تایپ کند.
سؤال: پرسوجوهای JPQL مورد نیاز برای بازیابی اطلاعات زیر را ارائه دهید:
- فهرست پزشکان به ترتیب نزولی نام خانوادگی
- فهرست پزشکانی که عنوانشان 'آقا' است
- فهرست شکافهای قرار ملاقات خانم پلیسیه
- فهرست قرارها به ترتیب صعودی تاریخ
- فهرست مشتریان (بر اساس نام خانوادگی) که در تاریخ 24/08/2006 با خانم PELISSIER قرار ملاقات داشتند
- تعداد مراجعینی که خانم PELISSIER در تاریخ 24/08/2006 ملاقات کرده است
- بیمارانی که نوبت رزرو نکردهاند
- پزشکان بدون نوبت
ما از مثال پاراگراف ۲.۷ در [ref1] پیروی خواهیم کرد. در اینجا مثالی از خروجی آمده است:
- خط ۲: پرسوجوی JPQL،
- خطوط ۳–۱۱: پرسوجوی متناظر SQL،
- خطوط ۱۲–۱۵: نتیجه پرسوجوی JPQL.
4.5. ارتباطات بین زمینه پایداری و SGBD
4.5.1. کلاس Person
4.5.2. برنامهٔ آزمون
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
4.5.3. پیکربندی Hibernate
4.5.4. پیکربندی log4j.properties
4.5.5. نتایج
سؤال: رابطه بین کد جاوا و نتایج نمایشدادهشده را توضیح دهید.





































