یادداشتهایی بر رهیافت ORM با تاکید بر Hibernate – بخش پنجم

معمولاً در اکثر پروژه های مبتنی بر db فرایند تولید مقدار برای ویژگی id را خودکار در نظر می‌گیرند مگر در موقعیتهای استثنایی که ما مجبور به دستی ست کردن مقدار برای فیلد id باشیم ( یکی از موارد کاربرد می‌تواند به هنگام کار با سیستم‌های قدیمی باشد. ) موقعی که بخواهیم یک entity را ذخیره کنیم ، معمولاً از سیستم می‌خواهیم برای فیلد id یک مقدار را تولید کند . بنابراین روی فیلدی که به عنوان id در نظر گرفتیم ( معمولاً با علامت @Id مشخص می‌شود ) به سیستم با گذاشتن علامت @GeneratedValue روی همان فیلد اعلام می‌کنیم زحمت تولید مقدار برای فیلد Id با تو هست . در این مقاله در مورد مطالب زیر صحبت خواهیم کرد :روشهای تولید id در JPA الگوریتم Hiloروشهای تولید id در JPA در این بخش در مورد روشها و استراتژی های تولید id در JPA صحبت می کنیم . ابتدا در مورد استراتژی های تولید id در JPA مزایا و معایب هر یک صحبت می کنیم در مقاله بعدی سراغ استراتژی های Hibernate می رویم . قبل از اینکه سراغ معرفی استراتژی های تولید id برویم بهتر است چند نکته را مدنظر قرار داشته باشیم :یک - بخاطر مسایل بهینه سازی ، سیاست Hibernate اغلب این است که عمل درج (insert) موجودیت ها تا جایی که می تواند به تعویق بیندازد و عمل درج را به صف برده و در نهایت همه درخواستهای insert را به صورت یکجا و دسته ای ( batch) انجام دهد . دو - بخاطر در پیش گرفتن سیاست ذکر شده در بند یک ترجیح ما (برنامه نویسان ) این است که مقدار id حتی قبل از عمل insert واقعی در DB تولید و در دست ما باشد . سه - در اغلب روشها ما با مفهومی بنام Sequence سروکار داریم که یا در سمت DB توسط DBMS هندل می شود یا توسط Hibernate . در واقع Sequence یک ویژگی است در دنیای DB . که وظیفه اش تولید مقادیر منحصربفرد برای ستون های Primary key در جدول های یک DB است . نکته مهمی که در مورد Sequence باید بهش توجه کنیم این است که فرایند تولید مقادیر id در اغلب DBMS‌ها خارج از تراکنش رخ می دهد به عبارت دیگر transaction-less است یعنی یک مقدار تولید شده نمی تواند همزمان به چندین تراکنش همروند در حال اجرا منتسب بشود . مطلب بعدی در مورد Sequence این است که مقداری که برای یک id تولید می شود اگر به یک تراکنش خاصی منتسب بشود چه اون تراکنش commit گردد و چه rollback بشود مقدار منتسب شده ازش در جای دیگر استفاده نمی شود . بخاطر مطالب گفته شده در بندهای یک و دو ترجیح ما در انتخاب استراتژی تولید id این است که نیاز ما برنامه نویسان از نظر مطالب گفته شده در بند دو را تامین کند . استراتژی های تولید id در استاندارد JPA در JPA چهار روش برای تولید مقدار برای id وجود دارد :روش Autoروش Identityروش Sequenceروش Tableدر ادامه هر یک را به طور خلاصه بررسی می کنیم :روش Idnetity :این روش توسط محصولات زیر پشتیبانی می شود :Oracle 12cSQL ServerMySQL (AUTO_INCREMENT)DB2HSQLDBدراین روش مقدار منتسب شده به id برای entity ی که می خواهد در جدول درج بشود تا زمان درج مشخص نیست به عبارت بهتر اول باید عمل درج در جدول رخ دهد بعد مقدار id بدست آید در این حالت اگر در سطح app بخواهیدبلافاصله بعد از عمل درج عمل زیر را برای بازیابی مقدار id انجام دهید ممکن است مقدار null دریافت کنید someItem.getId()در این روش یک ستون داریم که مقدارش از نوع bigint یا integer است و به ازای هر عمل درج مقدارش به ترتیب یک واحد زیاد می شود . پس مقدار تولید شده فقط در سطح همان جدول منحصر بفرد است . مثال :@Entity(name = "IdentitySample")public class IdentitySample { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;}تذکر : وقتی از این استراتژی استفاده کنید Hibernate عمل درج دسته ای ( batching) را غیر فعال می کند. جدای از غیر فعال شدن JDBC batching این روش با مدل ارث بری Table -per-class هم نمی تواند کار کند زیرا اون می تواند چندین subclass داشته باشد که دارای یک id باشند روش Sequence : این روش توسط محصولات زیر پشتیبانی می شود :OracleSQL Server 2012PostgreSQLDB2HSQLDBبرعکس روش قبلی که مقدار تولید شده برای id‌ از نظر منحصربفردی فقط محدود به یک جدول می شد ( به عنوان مثال اگر نام جدول شما student باشد و از روش Identity برای تولید جدول استفاده کرده باشید در این صورت اگر مقداری برای id تولید بشود برابر ۴۳ باشد در این صورت این عدد فقط در داخل جدول student منحصربفرد است نه جداول دیگر ) . در روش Sequence یک جا و مکان خاصی برای تولید مقادیر منحصر بفرد وجود دارد و نام آن جا و مکان sequence object می باشد . این روش مستقل از جدول است و مقدار تولید شده توسط این روش در کل دیتابیس منحصربفرد است . این روش معایب روش قبلی را ندارد یعنی امکان بدست اوردن مقدار id تولید شده حتی قبل از عمل درج واقعی وجود دارد . ( بخاطر استفاده از الگوریتم Hilo که بعدا توضیحش خواهیم داد . ) عمل درج دسته ای ( JDBC batching ) را در Hibernate غیر فعال نمی کند . همچنین مدل ارث بری در Hibernate را محدود نمی کند . @Entity(name = "sequenceSample")public class sequenceSample { @Id @GenericGenerator( name = "sequence" , strategy = "sequence" parameters = { @org.hibernate.annotations.Parameter( name = "sequence", value = "sequenc" ) }) @GeneratedValue(generator = "sequence") private Long id;}این روش مستقل از DB است و توسط خود Hibernate مدیریت می شود . روش Table :یک روش جایگزین مستقل از DB دیگری برای تولید id این روش جدول می باشد به طوری از یک یا چند جدول استفاده می شود تا مقدار id بعدی را در این جدول ها نگهداری کند . درحالی که دو روش قبلی فارغ از تراکنش هستند (transaction-less) استفاده از جدول در DB مارا مجبور می کند که اصول ACID را رعایت کنیم . برای مدیریت هم روندی چندین درخواست تولید id . این روش بخاطر هزینه های مدیریت هم روندی بالایی که دارد توصیه نمی شود .@Entity(name = "tableSample")public class TableSequenceSample { @Id @GenericGenerator( name = "table", strategy = "enhanced-table" parameters = { @org.hibernate.annotations.Parameter( name = "table_name", value = "sequence_table" ) }) @GeneratedValue(generator = "table", strategy=GenerationType.TABLE) private Long id;}الگوریتم Hilo : همانطور که پیشتر گفتیم سیاست هایبرنت این است که عمل درج entity های تازه ایجاد شده تا جایی که می تواند به تاخیر بیاندازد و از طرف دیگر خواسته ما برنامه نویسان این است بلافاصله بعد از فراخوانی متد persist بتونیم مقدار id را برای آن entity در دست داشته باشیم . برای تامین این خواسته از یک الگوریتمی بنام hilo استفاده می شود این الگوریتم نسخه ها مختلفی دارد و در کل فلسفه ان به این شرح است :به کمک دو مقدار عمل تولید اعداد متوالی منحصربفرد صورت می گیرد و در جایی نگهداری می شودهرموقع appی درخواست id برای entity خود کرد بهش منتسب کند حتی قبل از اینکه واقعا اون entity در DB درج شده باشد . مراحل الگوریتم :۱- پایه اعداد متوالی را در متغیر high value ذخیره کن ۲- اندازه این توالی از اعداد را در متغیر low value ذخیره کن ۳- مقدار متغیر high value را بردار و یک واحد بهش اضافه کن ۴- حال low value را ضربدر high value کن تا کران پایین بدست اید :Lower bound= low value * high value ۵- طبق فرمول زیر کران بالا را محاسبه کن :Upper bounds=(high value * low value) + low value -1حالا اعداد داخل این دو کران را می توان به هر کسی که درخواست مقدار برای id اش می کند تخصیص داد .در مقاله بعدی در مورد استراتژی های تولید id در خود Hibernate صحبت خواهیم کرد .