Skip to content

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]، را در نظر بگیریم که هدف آن ذخیره برخی اطلاعات درباره افراد است:

 
ID
کلید اصلی جدول
VERSION
نسخهٔ سطر در جدول. هر بار که جزئیات شخص اصلاح می‌شود، شمارهٔ نسخهٔ او افزایش می‌یابد.
NOM
نام شخص
PRENOM
نام
DATENAISSANCE
تاریخ تولد
MARIE
یک عدد صحیح ۰ (مجرد) یا ۱ (متأهل)
NBENFANTS
تعداد فرزندان شخص

4.2.1.2. واحد [Personne]

ما در محیط زمان اجرای زیر هستیم:

لایه JPA [5] باید به‌عنوان یک پل بین دنیای رابطه‌ای پایگاه داده [7] و دنیای شیءگرا [4] که توسط برنامه‌های جاوا [3] مدیریت می‌شود، عمل کند.371ZQX. این پل از طریق پیکربندی ایجاد می‌شود و دو روش برای انجام این کار وجود دارد:

  1. با استفاده از فایل‌های XML. این عملاً تنها راه انجام آن بود تا پیش از ظهور JDK 1.5
  2. استفاده از انوتیشن‌های جاوا از 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 تمایز قائل شد:

  1. جدول T از قبل وجود دارد: در این صورت، حاشیه‌نویسی‌های JPA باید ساختار موجود (نام‌ها و تعریف ستون‌ها، محدودیت‌های یکپارچگی، کلیدهای خارجی، کلیدهای اصلی و غیره) را بازتولید کنند.
  2. جدول 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ها از یک روش یکسان برای تولید مقادیر کلید اصلی استفاده نمی‌کنند. برای مثال:
Firebird
از یک تولیدکننده مقدار استفاده می‌کند که قبل از هر درج فراخوانی می‌شود
SQL server
میدان کلید اصلی با نوع Identity تعریف شده است. نتیجه مشابه تولیدکننده مقدار Firebird است، با این تفاوت که مقدار کلید تنها پس از درج ردیف مشخص می‌شود.
Oracle
از ابجکتی به نام 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

    alter table jpa06_article 
        drop 
        foreign key FKFFBDD9D8ECCE8750;

    drop table if exists jpa06_article;

    drop table if exists jpa06_categorie;

    create table jpa06_article (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        categorie_id bigint not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa06_categorie (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        primary key (id)
    ) ENGINE=InnoDB;

    alter table jpa06_article 
        add index FKFFBDD9D8ECCE8750 (categorie_id), 
        add constraint FKFFBDD9D8ECCE8750 
        foreign key (categorie_id) 
references jpa06_categorie (id);
  • در [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] اقدام کند:

  1. می‌تواند یک شیء ایجاد کند و از لایه JPA بخواهد آن را پایدار سازد. سپس این شیء بخشی از زمینه پایداری می‌شود.
  2. می‌تواند از لایه [JPA] یک مرجع به یک شی پایدار موجود درخواست کند.
  3. می‌تواند یک شیء پایدار را که از لایه JPA به دست آورده است، تغییر دهد.
  4. می‌تواند از لایه JPA بخواهد یک شیء را از زمینه پایداری حذف کند.

لایه JPA رابطی به نام [EntityManager] در اختیار کلاینت قرار می‌دهد که همان‌طور که از نامش پیداست، برای مدیریت اشیاء @Entity در زمینه پایداری استفاده می‌شود. متدهای اصلی این رابط در زیر آورده شده‌اند:

void persist(Object entity)
افزودن entity به زمینه پایداری
void remove(Object entity)
حذف entity از زمینه پایداری
<T> T merge(T entity)
یک شیء entity از سمت کلاینت را که توسط زمینه پایداری مدیریت نمی‌شود، ادغام می‌کند،
با شیء entity از زمینه پایداری که دارای کلید اولیه یکسان است.
نتیجهٔ بازگردانده‌شده، شیء entity از زمینهٔ پایداری است.
<T> T find(Class<T> entityClass,
Object primaryKey)
یک شیء بازیابی‌شده از پایگاه داده را قرار می‌دهد
از طریق کلید اصلی آن. نوع شیء T لایه JPA را فعال می‌کند
تا بداند کدام جدول را استعلام کند. شیء پایدار ایجادشده به این ترتیب به کلاینت بازگردانده می‌شود.
Query createQuery(String queryText)
یک شیء پرس‌وجو را از یک پرس‌وجوی JPQL ایجاد می‌کند (پایداری جاوا
زبان پرس‌وجو). یک پرس‌وجو JPQL مشابه یک پرس‌وجو SQL است اگر
با این تفاوت که به جای جداول، اشیاء را پرس‌وجو می‌کند.
Query createNativeQuery(String queryText)
یک روش مشابه مورد قبلی، با این تفاوت که queryText یک
SQL به جای JPQL.
Query createNamedQuery(String name)
این روش با 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 em = emf.createEntityManager();

از متدهای زیر از رابط [EntityManager] برای مدیریت چرخه عمر زمینه پایداری استفاده می‌شود:

void close()
زمینه پایداری بسته می‌شود. باعث همگام‌سازی اجباری زمینه پایداری با پایگاه داده می‌شود:
  • اگر یک شیء در زمینه در پایگاه داده موجود نباشد، با استفاده از یک عملیات (SQL, INSERT) درج می‌شود
  • اگر یک شیء در زمینه در پایگاه داده موجود باشد و از زمان خوانده شدن تغییر کرده باشد، عملیاتی (SQL یا UPDATE) برای پایدارسازی تغییر انجام می‌شود
  • اگر یک شیء زمینه پس از یک عملیات remove روی آن به عنوان «حذف شده» علامت‌گذاری شده باشد، یک عملیات SQL DELETE برای حذف آن از پایگاه داده انجام می‌شود.
void clear()
شاهدباقی‌ماندن (persistence context) از تمام اشیاء خود تخلیه می‌شود اما بسته نمی‌شود.
void flush()
زمینه پایداری همانطور که برای close() توصیف شده است، با پایگاه داده همگام‌سازی می‌شود.

کلاینت JPA می‌تواند با استفاده از متد [EntityManager] همگام‌سازی زمینه پایداری با پایگاه داده را اجباری کند. flush (قبلی). همگام‌سازی می‌تواند صریح یا ضمنی باشد. در حالت اول، مشتری موظف است عملیات flush را هر زمان که بخواهد همگام‌سازی انجام دهد، فراخوانی کند؛ در غیر این صورت، این عملیات در زمان‌های مشخصی که بعداً تعیین خواهیم کرد، انجام می‌شوند. حالت همگام‌سازی توسط متدهای زیر از رابط [EntityManager] مدیریت می‌شود:

void setFlushMode(FlushModeType
flushMode)
دو مقدار ممکن برای flushmode وجود دارد:
FlushModeType.AUTO (پیش‌فرض): همگام‌سازی قبل از انجام می‌شود
هر پرس‌وجوی SELECT که به پایگاه داده ارسال می‌شود.
FlushModeType.COMMIT: همگام‌سازی تنها در زمان
پایان تراکنش‌ها در پایگاه داده.
FlushModeType getFlushMode()
حالت همگام‌سازی فعلی را برمی‌گرداند

برای خلاصه کردن. در حالت FlushModeType.AUTO که حالت پیش‌فرض است، زمینه پایداری در زمان‌های زیر با پایگاه داده همگام‌سازی می‌شود:

  1. قبل از هر عملیات SELECT روی پایگاه داده
  2. در پایان یک تراکنش در پایگاه داده
  3. پس از یک عملیات 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) است:

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (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] پیروی خواهیم کرد. در اینجا مثالی از خروجی آمده است:

Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :
select c from Client c
Hibernate: 
    select
        client0_.ID as ID2_,
        client0_.NOM as NOM2_,
        client0_.PRENOM as PRENOM2_,
        client0_.TITRE as TITRE2_,
        client0_.version as version2_ 
    from
        clients client0_
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
  • خط ۲: پرس‌وجوی JPQL،
  • خطوط ۳–۱۱: پرس‌وجوی متناظر SQL،
  • خطوط ۱۲–۱۵: نتیجه پرس‌وجوی JPQL.

4.5. ارتباطات بین زمینه پایداری و SGBD

4.5.1. کلاس Person

package entites;

...

@Entity
@Table(name = "jpa01_personne")
public class Personne {

  @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() {
    return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(), getNom(), getPrenom(), 
                         new SimpleDateFormat("dd/MM/yyyy").format(getDatenaissance()), isMarie(), getNbenfants());
  }

   // گیرنده و تنظیم‌کننده
...
}

4.5.2. برنامهٔ آزمون

package tests;

....
import entites.Personne;

@SuppressWarnings("unchecked")
public class Test1 {

   // ثوابت
  private final static String TABLE_NAME = "jpa01_personne";  // زمینه پایداری
  private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
  private static Personne p1;

  public static void main(String[] args) throws Exception {
     //پاکسازی پایگاه داده
    log("clean");
    clean();

     // خروجی
    log("dump");
    dump();

     // test1
    log("test1");
    test1();

     // test2
    log("test2");
    test2();

     // بستن EntityManagerFactory
    emf.close();
  }

   // نمایش محتویات جدول
  private static void dump() {
     // زمینه پایداری
    EntityManager em = emf.createEntityManager();
     // شروع تراکنش
    EntityTransaction tx = em.getTransaction();
    tx.begin();
     // نمایش افراد
    for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
      System.out.println(p);
    }
     // پایان تراکنش
    tx.commit();
     // پایان زمینه
    em.close();
  }

   //پاک کردن BD
  private static void clean() {
     // زمینه پایداری
    EntityManager em = emf.createEntityManager();
     // شروع تراکنش
    EntityTransaction tx = em.getTransaction();
    tx.begin();
     // حذف عناصر از جدول PERSONNES
    em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
     //پایان تراکنش
    tx.commit();
     //پایان زمینه
    em.close();
  }

   // گزارش‌ها
  private static void log(String message) {
    System.out.println("main : ----------- " + message);
  }

   //مدیریت شیء پایدار
  public static void test1() throws ParseException {
     // زمینه پایداری
    EntityManager em = emf.createEntityManager();
     // ایجاد شخص
    p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
    Personne p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
     // شروع تراکنش
    EntityTransaction tx = em.getTransaction();
    System.out.println("début transaction");
    tx.begin();
     // پایداری اشخاص
     // لاگ‌ها نشان می‌دهند که عملیات SQL INSERT بلافاصله پس از عملیات پایداری تولید می‌شود
     //احتمالاً برای به‌دست‌آوردن کلید اصلی
    System.out.println(String.format("Personne p1 %s non persistée", p1));
    System.out.println("em.persist(p1)");
    em.persist(p1);
    System.out.println(String.format("Personne p1 %s persistée", p1));
     // شخص p2
     //INSERT به محض انجام عملیات persist تولید می‌شود
    System.out.println(String.format("Personne p2 %s non persistée", p2));
    System.out.println("em.persist(p2)");
    em.persist(p2);
    System.out.println(String.format("Personne p2 %s persistée", p2));
    p2.setMarie(true);
    System.out.println(String.format("Personne p2 %s modifiée", p2));
     //عملیات DELETE مرتبط با عملیات حذف تنها در پایان تراکنش انجام می‌شود
    System.out.println("em.remove(p2)");
    em.remove(p2);
    System.out.println(String.format("Personne p2 %s supprimée", p2));
     //اصلاح p1
    p1.setNom("P1");
     //پایان تراکنش
    System.out.println("fin transaction");
    tx.commit();
     // پایان زمینه
    em.close();
     //جدول نمایش داده می‌شود
    dump();
  }

   // مدیریت اشیاء پایدار
  public static void test2() throws ParseException {
     // زمینه پایداری
    EntityManager em = emf.createEntityManager();
     // شروع تراکنش
    EntityTransaction tx = em.getTransaction();
    System.out.println("début transaction");
    tx.begin();
     // تغییر شخص جداشده فعلی p1
    System.out.println(String.format("Personne p1 %s actuelle non persistée", p1));
    p1.setMarie(false);
    System.out.println(String.format("Personne p1 %s nouvelle non persistée", p1));
     // اتصال مجدد شخص P1
    System.out.println("em.merge(p1)");
    Personne p1b = em.merge(p1);
    System.out.println(String.format("Personne p1b %s attachée", p1b));
     //پایان تراکنش
    System.out.println("fin transaction");
    tx.commit();
       //پایان زمینه
    em.close();
   // نمایش جدول
    dump();
  }
}

4.5.3. پیکربندی Hibernate

<?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" />
      <property name="hibernate.show_sql" value="true"/>
....
             <! -- ایجاد خودکار طرحواره -->
      <property name="hibernate.hbm2ddl.auto" value="create" />
....
    </properties>
  </persistence-unit>
</persistence>

4.5.4. پیکربندی log4j.properties

# ارسال مستقیم پیام‌های لاگ به stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

# گزینهٔ لاگر ریشه
log4j.rootLogger=ERROR, stdout

# گزینه‌های گزارش‌گیری Hibernate (INFO فقط پیام‌های راه‌اندازی را نمایش می‌دهد)
#log4j.logger.org.hibernate=DEBUG

#ثبت JDBC پارامترهای پیوندی استدلال‌های زمان اجرا
log4j.logger.org.hibernate.type=DEBUG

4.5.5. نتایج

init:
deps-jar:
Compiling 1 source file to C:\data\travail\2008-2009\netbeans\jpa\hibernate-personnes-entites\build\classes
compile-single:
run-single:
main : ----------- پاکسازی
Hibernate: delete from jpa01_personne
main : ----------- dump
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
main : ----------- test1
début transaction
Personne p1 [null,0,Martin,Paul,31/01/2000,true,2] non persistée
em.persist(p1)
Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,312 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,312 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,312 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,312 DEBUG StringType:133 - binding 'Martin' to parameter: 4
17:57:26,312 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,312 DEBUG IntegerType:133 - binding '0' to parameter: 6
Personne p1 [1,0,Martin,Paul,31/01/2000,true,2] persistée
Personne p2 [null,0,Durant,Sylvie,05/07/2001,false,0] non persistée
em.persist(p2)
Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,328 DEBUG DateType:133 - binding '05 juillet 2001' to parameter: 1
17:57:26,328 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 3
17:57:26,328 DEBUG StringType:133 - binding 'Durant' to parameter: 4
17:57:26,328 DEBUG StringType:133 - binding 'Sylvie' to parameter: 5
17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 6
Personne p2 [2,0,Durant,Sylvie,05/07/2001,false,0] persistée
Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] modifiée
em.remove(p2)
Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] supprimée
fin transaction
Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?, PRENOM=?, VERSION=? where ID=? and VERSION=?
17:57:26,343 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,343 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,343 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,343 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26,359 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 6
17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 8
Hibernate: delete from jpa01_personne where ID=? and VERSION=?
17:57:26,359 DEBUG IntegerType:133 - binding '2' to parameter: 1
17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 2
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
17:57:26,375 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26,390 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
17:57:26,390 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_
17:57:26,390 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26,390 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,390 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26,390 DEBUG IntegerType:172 - returning '1' as column: VERSION0_
[1,1,P1,Paul,31/01/2000,true,2]
main : ----------- test2
début transaction
Personne p1 [1,1,P1,Paul,31/01/2000,true,2] actuelle non persistée
Personne p1 [1,1,P1,Paul,31/01/2000,false,2] nouvelle non persistée
em.merge(p1)
Hibernate: select personne0_.ID as ID0_0_, personne0_.DATENAISSANCE as DATENAIS2_0_0_, personne0_.MARIE as MARIE0_0_, personne0_.NBENFANTS as NBENFANTS0_0_, personne0_.NOM as NOM0_0_, personne0_.PRENOM as PRENOM0_0_, personne0_.VERSION as VERSION0_0_ from jpa01_personne personne0_ where personne0_.ID=?
17:57:26,406 DEBUG IntegerType:133 - binding '1' to parameter: 1
17:57:26,406 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_0_
17:57:26,406 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_0_
17:57:26,406 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_0_
17:57:26,406 DEBUG StringType:172 - returning 'P1' as column: NOM0_0_
17:57:26,406 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_0_
17:57:26,406 DEBUG IntegerType:172 - returning '1' as column: VERSION0_0_
Personne p1b [1,1,P1,Paul,31/01/2000,false,2] attachée
fin transaction
Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?, PRENOM=?, VERSION=? where ID=? and VERSION=?
17:57:26,406 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,406 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26,406 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,421 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26,421 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,421 DEBUG IntegerType:133 - binding '2' to parameter: 6
17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 8
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
17:57:26,453 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26,453 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
17:57:26,453 DEBUG BooleanType:172 - returning 'false' as column: MARIE0_
17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26,453 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,453 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: VERSION0_
[1,2,P1,Paul,31/01/2000,false,2]
BUILD SUCCESSFUL (total time: 3 seconds)

سؤال: رابطه بین کد جاوا و نتایج نمایش‌داده‌شده را توضیح دهید.