رابطه یک به یک : در دنیای ابجکت در نظر بگیرید که یک Person داریم که Employee است و هر Employee هم یک Person است و رابطه یک به یک دارند که به ازای هر Person تنها یک Employee داریم و بیشتر از یکی نمیخواهیم داشته باشیم حال در دنیای relational چطوری میشه این رابطه را ایجاد کرد ؟
وقتی ما در جدول Employee یک F.Key از نوع Person ها بگذاریم ، هر Employee میتواند چندین Person داشته باشد و برای داشتن رابطه یک به یک میبایست F.key را توسط UniqueConstraint@ از نوع Unique میگیریم و باعث میشه از یک F.key تنها یکبار میشود استفاده کرد و در نتیجه هر Person تنها میتواند یکبار Employee باشد
رابطه چند به چند : فرض کنید هر teacher یکسری Course را درس میدهد و هر Course توسط چند teacher ارائه میشود ، این یک رابطه چند به چند است و در دنیای Relational میتوان از جداول واسط استفاده کرد که از هر دو P.key بصورت F.key داخل آن جدول واسط استفاده کنیم و با join زدن روابط چند به چند را پیاده سازی کنیم
روابط ارث بری در relational : فرض کنید یک ابجکت پدر داریم بنام Person که داخلش یک نام و نام خانوادگی دارد که دو فرزند مختلف دارد مثلا یکی NationalPerson برای افراد با ملیت ملی و داخلی که فیلدی برای شماره ملی دریافت میکند و ForeignPerson برای اتباع خارجی که فیلدی برای دریافت شماره پاسپورت دارد ولی هر دوی این کلاس های فرزند نام و نام خانوادگی را دارند و از پدر به ارث برده اند و کلیه فیلد های آن یکسان است ولی فرزندان در یک فیلد متفاوت هستند
دو راه حل برای پیاده سازی این ارث بری در دنیای Relational داریم :
روش اول :
در این حالت میبایست علاوه بر فیلد های مشترک هر دو فیلد متفاوت را در جداول ایجاد کرد و یک ستون دیگر برای تعیین نوع فرزند ایجاد کرد و اینطوری هر دو نوع فرزند قابلیت ذخیره شدن در دیتابیس را دارند
یک جدول Person بگیریم که کلیه فیلد های فرزندان هم داشته باشد و یک ستون اضافه برای PersonType میگذاریم که مشخص کنیم Person ما اتباع خارجی است یا اتباع داخلی و بعد و چنانچه PersonType از نوع ForeignPerson بود حتما فیلد شماره پاسپورت را داشته باشد و اگر اتباع داخلی بود حتما مقدار برای ستون شماره ملی داشته باشد
(بهتر است در این حالت یک Trigger برای چک کردن فیلد ها با نوع Type آنها ایجاد کنیم تا از بروز مشکلات آتی جلوگیری کند)
و در شی گرایی برای مدل کردن باید بگوییم که جدول Person در واقع میتواند سه نوع شی داشته باشد و بر مبنای ستون PersonType میتواند از هم تفکیک شوند
بدی این روش اینه که Person ما از هر نوعی باشد یکی از ستون ها خالی میماند و مدیریت کردن دیتا ها هم مشکل تر خواهد بود
روش دوم :
در روش دوم بجای استفاده از یک جدول از چند جدول استفاده میکنیم جداول ما میشود Person , ForeignPerson , NationalPerson که primary key جداول فرزند را Foreign key میگیرند به جدول Person در ORM هم این رابطه قابل پیاده سازی است که بعدا پیاده سازی این روش را توضیح خواهیم داد
پس در دنیای شی گرایی ما یکسری کلاس های Entity@ داریم که نماینده جداول دیتابیس ما هستند و هر فیلد از اون کلاس entity نماینده هر ستون از جدول است و هر Object از اون کلاس entity نماینده یک سطر از جدول ما است
از وقتی استاندارد JPA ارائه شد در هایبرنیت هم از annotation های JPA استفاده میکند
کانفیگ های مربوط به Hibernate :
- برای نمایش کوئری هایی که روی دیتابیس میزند در کنسول میتوان hibernate.show.sql را برابر true قرار داد ولی باید حواسمان باشد که در مرحله تست از این کانفیگ استفاده کنیم چون مقدار بسیار زیادی لاگ تولید میکند
- بعضی کوئری ها در پاسخ مقدار بسیار زیادی ResultSet خواهیم داشت که روی پرفرمانس برنامه تاثیر میگذارد و کارایی را کاهش میدهد و نیاز داریم که اون حجم بزرگ از خروجی را بصورت دسته دسته از دیتابیس دریافت کنیم برای این منظور مقدار fetch size را باید تعیین کنیم که توسط hibernate.jdbc.fetch_size و یک عدد به عنوان مقدارش تعیین میشود و اگر این عدد روی 50 باشد و ما یک میلیون رکورد را بخواهیم دریافت کنیم 50 تا 50 تا آنرا از دیتابیس دریافت میکند ما در اجرای کد این موضوع را احساس نمیکنیم و ORM خودش این موضوع را هندل میکند البته نکته بسیار مهمی که وجود دارد این است که درایور دیتابیس ساپورت بکند یا نه
- برای عملیات Insert Update Delete که روی دیتابیس تغییر ایجاد میکند و اگر این حجم از کوئری ها زیاد باشد مثلا فرض کنید میخواهیم صد تا کوئری را برای یک عملیات بزنیم و همه این صد تا کوئری برای یک کار زده میشود مثلا فرض کنید محاسبه تعداد لایک پست های یک کاربر میتواند مثال خوبی باشد ، و با تعیین hibernate.jdbc.batch_size و یک عدد تعداد کوئری های همزمانی که روی دیتابیس میخورد را مشخص میکنیم
- برای ایجاد دیتا مدل ها و جداول از روی entity model ها میتوان از کانفیگ hibernate.hbm2ddl.auto استفاده کرد و هایبرنیت خودش با توجه به مقداری که تعیین شده مدیریت ساخت جداول را بر عهده میگیرد که بر چند نوع است :
none : غیر فعال میکند
validate : تنها چک میکند که جداول طبق مدل ها معتبر و قابل استفاده باشند و تغییری در دیتابیس ایجاد نمیکند و اگر تفاوتی در جداول و Entity Model ها وجود داشت هایبرنیت اجرا نمیشود
create : اولین بار که بالا میاد جداول را drop میکند و از اول create میکند
create-drop : اول کلیه جداول را create میکند و در انتهای اتمام اجرای برنامه هنگامی که Session Factory را close بکنیم کلیه جداول را drop میکند و برای تست کاربرد دارد
update : اگر تغییراتی در مدل ها اعمال شده بود با جداول چک میکند و اگر چیزی کم و زیاد بود آنرا تغییر میدهد
- تعیین connection pool size در هایبرنیت به طور پیش فرض از یک connection pool داخلی استفاده میکند که میتوان سایزش را توسط hibernate.connection.pool.size تعیین کنیم موقعی که از JNDI استفاده کنیم و DataSource را lookup کنیم از connection pool خودش استفاده نمیکند
نوع xml ای هم داریم :
کانفیگ های مربوط به fetch size و batch size و connection pool size :
<property name="hibernate.jdbc.fetch_size"> >=0 </property>
این امکان در یکسری از jdbc driver ها وجود دارد که وقتی میخواهیم یک ResultSet بگیریم بصورت دسته دسته آنها را دریافت کنیم و مثلا در تعداد بالای رکورد های دریافت آنرا بصورت خرد خرد دریافت کند و این باعث میشود بافر کمتری استفاده شود و سرعت اجرا بهینه تری داشته باشد ولی در اجرا ما تفاوتی را حس نخواهیم کرد و این نکته را هم باید در نظر داشته که همه jdbc driver ها ساپورت نمیکنند
<property name="hibernate.jdbc.batch_size"> >=1 </property>
برای عملیات insert , update , delete هنگامی که بخواهیم چندین دستور را یکباره و در یک connection اجرا کنیم ، میتوان تعیین کرد که تعداد دستورات چند تا چند تا به دیتابیس ارسال شود
<property name="hibernate.connection.pool_size"> >=1 </property>
دادن کانفیگ ها توسط کلاس Configuration : میتوان کانفیگ ها را از طریق xml فایل ارسال نکنیم و با استفاده از کلاس Configuration و متد setProperty کانفیگ های مورد نیاز را پاس کنیم که در Spring هم از همین روش میتوانیم استفاده کنیم
روش های تولید اعداد یکتا :
Identity : انجین پایگاه داده خودش برای هر رکورد به آن یکی اضافه میکند و نیازی نیست که ORM درگیر آن شود ولی یکسری از RDBMS ها از این قابلیت پشتیبانی نمیکنند و از روش Sequence استفاده میکنند
Sequence : انجین یک ابجکت جداگانه برای تولید اعداد یکتا استفاده میکند که توسط هایبرنیت هم ساپورت میشود
Assigned : که توسط کد ما به آن ارسال میشود و انجین پایگاه داده درگیر آن نمیشود که میتوانیم از java.util.UUID استفاده کنیم که همیشه یک String یکتا تولید میکند ، برخی از توسعه دهنده ها یک جدول برای نگهداری آخرین Id تولید شده ایجاد میکنند و موقع Insert کردن آخرین مقدار Id را از روی اون جدول میخوانند و یکی بهش اضافه میکنند و در نهایت به مقدار Id در جدول مخصوص نگهداری Id ها هم یکی اضافه میکنند و هر دو را باهم Commit میکنند این برای مواقعی است که یکسری از پایگاه داده ها یا انجین ها قابلیت Sequence را ندارند و ما محبور میشیم از این روش استفاده کنیم و Hibernate این مورد را هم ساپورت میکند و میتوانیم براحتی بهش بگوییم که ID را توسط جدول مخصوصش که ایجاد کردیم پیدا کند و استفاده کند
Auto : در این حالت نسبت به نوع پایگاه داده ، هایبرنیت بهترین روش را استفاده میکند و اگر تصمیم بگیرد که از sequence استفاده کند و پایگاه داده از این ویزگی حمایت نکند خودش یک جدول temporary بنام hibernate_sequence میسازد
در هایبرنیت از روش های دیگری هم پشتیبانی میکند که در زیر میتوان مجموعه کاملترش را ببینید :
Hibernate provides a range of built-in implementations. The shortcut names for the built-in generators are as follows:
increment
generates identifiers of type long, short or int that are unique only when no other process is inserting data into the same table. Do not use in a cluster.
identity
supports identity columns in DB2, MySQL, MS SQL Server, Sybase and HypersonicSQL. The returned identifier is of type long, short or int.
sequence
uses a sequence in DB2, PostgreSQL, Oracle, SAP DB, McKoi or a generator in Interbase. The returned identifier is of type long, short or int
hilo
uses a hi/lo algorithm to efficiently generate identifiers of type long, short or int, given a table and column (by default hibernate_unique_key and next_hi respectively) as a source of hi values. The hi/lo algorithm generates identifiers that are unique only for a particular database.
seqhilo
uses a hi/lo algorithm to efficiently generate identifiers of type long, short or int, given a named database sequence.
uuid
uses a 128-bit UUID algorithm to generate identifiers of type string that are unique within a network (the IP address is used). The UUID is encoded as a string of 32 hexadecimal digits in length.
guid
uses a database-generated GUID string on MS SQL Server and MySQL.
native
selects identity, sequence or hilo depending upon the capabilities of the underlying database.
assigned
lets the application assign an identifier to the object before save() is called. This is the default strategy if no element is specified.
select
retrieves a primary key, assigned by a database trigger, by selecting the row by some unique key and retrieving the primary key value.
foreign
uses the identifier of another associated object. It is usually used in conjunction with a primary key association.
sequence-identity
a specialized sequence generation strategy that utilizes a database sequence for the actual value generation, but combines this with JDBC3 getGeneratedKeys to return the generated identifier value as part of the insert statement execution. This strategy is only supported on Oracle 10g drivers targeted for JDK 1.4. Comments on these insert statements are disabled due to a bug in the Oracle drivers.
قابلیت Caching در Hibernate : خروجی کوئری ها در حافظه ذخیره میشوند و اگر در سری های بعدی همان کوئری ارسال شد بدون اینکه به دیتابیس مراجعه و کوئری را اجرا کند همان خروجی قبلی را مورد استفاده قرار دهد که دارای دو Level است :
Second Level Cache : که روی Session Factory پیاده سازی میشود و بصورت Global است یعنی اگر یک کاربر یک کوئری را اجرا کرد در درخواست های کاربران دیگر نیز از همان استفاده خواهد شد و به درد جداولی میخورد که دیتای آن تغییر نمیکند و همیشه ثابت است مثل لیست زبان های موجود و با استفاده از Cache@ قابل تعریف است
First Level Cache : روی Session است و چرخه حیاطش از زمان دریافت یک session تا close شدنش این cache فعال است و کاربردش در حالت transactional است
** نکته : در سیستم های Caching جدید از سرویس هایی مثل GemFire (مثل Apache Ignite ، Apache Geode , Hazelcast و ... ) و یا SpringCache استفاده کرد و به غیر از ابجکت های هایبرنیتی هر چیزی که نیاز بود قابل Cache شدن است
خواص ACID در تراکنش :
Atomicity : یعنی کل تراکنش یا به طور کامل انجام میشود یا کاملا هیچ کدامش انجامش نمیشود
Consistency : اگر یک تراکنش Commit شد باید در دیتابیس ثبت و پایدار بماند برای این خاصیت از Locking استفاده میکنند
Isolation : هر تراکنش باید محیط ایزوله خودش را داشته باشد تا تغییرات تراکنش جاری روی تراکنش دیگر تاثیری نداشته باشد
Durability : وقتی دیتایی commit شد باید بطور دایم حفظ شود مگر کوئری ای آنرا تغییر دهد
نحوه ایجاد Transaction :
نوع اول به این صورت است که خودمان در کد آنرا ایجاد و مدیریت میکنیم
نوع دوم استفاده از Container است که معرفترینش JTA است که ایجاد و مدیریت تراکنش ها به Container سپرده میشود مانند Weblogic که مدیریت تراکنش خودش را دارد و خوبیش اینه که سیستم مانیتورینگ و لاگ را دارد و میتوانیم آمار تراکنش ها را هم ببینیم که بیشترین تراکنش کدام ها بوده و چقدر سر بار ایجاد کرده و ...