المنشور مصمم للمبرمجين المبتدئين ولا يحتوي على أي أشياء صعبة للغاية.
أدعو الجميع إلى اليوم التجريبي للدورة التدريبية عبر الإنترنت "Java Developer. محترف " . كجزء من الحدث ، سوف أخبرك بالتفصيل عن برنامج الدورة التدريبية ، بالإضافة إلى الإجابة على أسئلتك.
يتم استخدام جزء كبير من الحل مثل Hibernate ، لأن هذا مناسب جدًا للعمل مع الكائنات المتداخلة.
على سبيل المثال ، هناك فئة RecordPackage ، أحد حقول هذه الفئة هو مجموعة من الكائنات الفرعية (أو المتداخلة): السجلات.
إذا كنت تستخدم Jdbc ، فسيتعين عليك كتابة الكثير من التعليمات البرمجية الروتينية. قلة من الناس يحبونها ، وهذا جزئيًا سبب استخدامهم Hiberhate.
باستخدام Hibernate ، يمكنك استدعاء طريقة واحدة في وقت واحد للحصول على RecordPackage مع جميع السجلات التابعة لها.
من ناحية ، أريد استخدام طريقة واحدة للحصول على الكائن بالكامل ، ومن ناحية أخرى ، لا أريد العبث مع وحش السبات.
يتيح لك Spring Data Jdbc الحصول على أفضل ما في هذين العالمين (أو على الأقل شيء مقبول).
خذ بعين الاعتبار حالتين:
- علاقة رأس بأطراف
- علاقة واحد لواحد
غالبًا ما يتم مصادفة هذه الروابط في الممارسة.
يمكن العثور على الكود الكامل للأمثلة على GitHub ، وسأقدم هنا الحد الأدنى فقط.
بادئ ذي بدء ، تجدر الإشارة إلى أن Spring Data Jdbc ليست أداة سحرية لحل أي مشكلة. من المؤكد أن لها عيوبها وقيودها.
ومع ذلك ، بالنسبة لعدد من المهام النموذجية ، يعد هذا حلاً مناسبًا تمامًا.
علاقة رأس بأطراف
كمثال حقيقي ، يمكنك التفكير في: رأس حزمة من بعض البيانات وخطوط البيانات المضمنة في هذه الحزمة. على سبيل المثال ، الملف عبارة عن حزمة وخطوط الملف عبارة عن خطوط بيانات تدخل في تلك الحزمة.
هيكل الجداول على النحو التالي:
create table record_package
(
record_package_id bigserial not null
constraint record_package_pk primary key,
name varchar(256) not null
);
create table record
(
record_id bigserial not null
constraint record_pk primary key,
record_package_id bigint not null,
data varchar(256) not null
);
alter table record
add foreign key (record_package_id) references record_package;
جدولين:
record_package(رأس حزمة معينة) و record(السجلات المدرجة في الحزمة).
كيف يتم عرض هذه العلاقة في كود جافا:
@Table("record_package")
public class RecordPackage {
@Id
private final Long recordPackageId;
private final String name;
@MappedCollection(idColumn = "record_package_id")
private final Set<Record> records;
….
}
نحن هنا مهتمون بتحديد علاقة رأس بأطراف. يتم ترميز هذا باستخدام التعليق التوضيحي
@MappedCollection.
يحتوي هذا التعليق التوضيحي على معلمتين:
idColumn - الحقل الذي يتم من خلاله إجراء الاتصال
؛ keyColumn - الحقل الذي يتم من خلاله ترتيب السجلات في الجدول الفرعي .
هذا الترتيب جدير بالذكر بشكل منفصل. في هذا المثال ، ليس من المهم بالنسبة لنا الترتيب الذي سيتم فيه إدراج السجلات الفرعية في جدول السجلات ، ولكن في بعض الحالات قد يكون ذلك مهمًا. لمثل هذا الطلب ، سيكون لجدول التسجيل حقل مثل record_no ، وهذا هو الحقل الذي سيتعين كتابته في keyColumn في التعليق التوضيحي MappedCollection. عند تنفيذ الإدراج ، فإن Spring Data Jdbc ستولد قيم هذا الحقل. بالإضافة إلى التعليق التوضيحي ، يجب استبدال
<السجل >المعيّن بالقائمة<سجل >، وهو أمر منطقي ومفهوم تمامًا. سيؤخذ التسلسل المحدد صراحة للصفوف الفرعية في الاعتبار عند تكوين التحديد ، لكننا سنعود إلى هذا لاحقًا.
لذلك ، حددنا الروابط ونحن مستعدون لتجربتها.
نقوم بإنشاء كيانات ذات صلة ونحصل عليها من القاعدة:
var record1 = new Record("r1");
var record2 = new Record("r2");
var record3 = new Record("r3");
var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
var recordPackageSaved = repository.save(recordPackage);
var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());
لاحظ أننا نحتاج فقط إلى استدعاء طريقة واحدة
repository.findByIdللحصول على مثيل RecordPackageبمجموعة ممتلئة من السجلات.
بالطبع ، نحن مهتمون بنوع استعلام SQL الذي تم تنفيذه للحصول على مجموعة متداخلة من السجلات.
مقارنةً بـ Hibernate ، فإن Spring Data Jdbc جيد لبساطته. يمكن تصحيحه بسهولة للكشف عن النقاط الرئيسية.
بعد إجراء تحقيق بسيط في حزمة org.springframework.data.jdbc.core.convert ، وجدنا فئة DefaultDataAccessStrategy . هذه الفئة مسؤولة عن إنشاء استعلامات SQL بناءً على معلومات الفئة. الآن في هذا الفصل نحن مهتمون بالطريقة
<كائن> findAllByPath قابل للتكرار
وبشكل أكثر دقة الخط:
String findAllByProperty = sql(actualType)
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
هنا يتم استرداد استعلام SQL المطلوب من ذاكرة التخزين المؤقت الداخلية.
في حالتنا ، يبدو كالتالي:
SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id"
FROM "record"
WHERE "record"."record_package_id" = :record_package_id
كل شيء واضح ويمكن التنبؤ به.
كيف سيبدو إذا استخدمنا ترتيب السجلات في الجدول الفرعي؟ من الواضح أن الأمر سيكون مطلوبًا.
دعنا ننتقل إلى فئة BasicRelationalPersistentProperty لحزمة org.springframework.data.relational.core.mapping. تحتوي هذه الفئة على طريقة تحدد ما إذا كنت تريد إضافة طلب إلى الاستعلام أم لا.
public boolean isOrdered() {
return isListLike();
}
و
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
يتحقق isCollectionLike من أن لدينا بالفعل "مجموعة" (بما في ذلك المصفوفة).
ومن الشرط ! Set.class.isAssignableFrom (this.getType ())؛ يصبح من الواضح أن Set لا تُستخدم بالصدفة ، ولكن لاستبعاد الفرز غير الضروري. وفي يوم من الأيام سوف نستخدم القائمة عن قصد لتمكين الفرز.
أعتقد أن مبدأ واحد إلى متعدد هو أكثر أو أقل وضوحًا ، دعنا ننتقل إلى الحالة التالية.
علاقة واحد لواحد
لنفترض أن لدينا مثل هذا الهيكل.
create table info_main
(
info_main_id bigserial not null
constraint info_pk primary key,
main_data varchar(256) not null
);
create table info_additional
(
info_additional_id bigserial not null
constraint additional_pk primary key,
info_main_id bigint not null,
additional_data varchar(256) not null
);
alter table info_additional
add foreign key (info_main_id) references info_main;
يوجد جدول بالمعلومات الأساسية عن كائن معين (info_main) وهناك معلومات إضافية (info_additional).
كيف يمكن تمثيل ذلك في الكود:
@Table("info_main")
public class InfoMain {
@Id
private final Long infoMainId;
private final String mainData;
@MappedCollection(idColumn = "info_main_id")
private final InfoAdditional infoAdditional;
…
}
للوهلة الأولى ، يبدو الأمر وكأنه أول حالة من شخص إلى متعدد ، ولكن هناك فرق. هذه المرة ، يعتبر الطفل حقًا كائنًا ، وليس مجموعة كما في الحالة السابقة.
يبدو رمز الاختبار كما يلي:
var infoAdditional = new InfoAdditional("InfoAdditional");
var infoMain = new InfoMain("mainData", infoAdditional);
var infoMainSaved = repository.save(infoMain);
var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());
دعونا نرى ما هو تعبير sql الذي تم إنشاؤه هذه المرة. للقيام بذلك ، سنكشف عن طريقة findById في المكان:
Package org.springframework.data.jdbc.core.convert class DefaultDataAccessStrategy . نحن بالفعل على دراية بهذا الفصل ، والآن نحن مهتمون بالطريقة.
public <T> T findById(Object id, Class<T> domainType)
نرى أنه تم استرداد الطلب التالي من ذاكرة التخزين المؤقت:
SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id"
FROM "info_main"
LEFT OUTER JOIN "info_additional" "infoAdditional"
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id"
WHERE "info_main"."info_main_id" = :id
الآن ، الوصلة الخارجية اليسرى تناسبنا ، لكن ماذا لو لم تفعل ذلك. كيف أحصل على الانضمام الداخلي؟
يتم إنشاء عمليات الانضمام الوظيفية في الحزمة org.springframework.data.jdbc.core.convert ، class SqlGenerator ، الطريقة:
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)
نحن مهتمون بهذا الجزء:
for (Join join : joinTables) {
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}
إذا كنت بحاجة إلى ضم الجداول ، فلا يوجد خيار سوى مع صلة خارجية يسرى.
يبدو أن الصلة الداخلية لا يمكن إجراؤها بعد.
خاتمة
لقد نظرنا في حالتين نموذجيتين لكيفية ضم الجداول في Spring Data Jdbc.
من حيث المبدأ ، تعد الوظيفة المتاحة الآن مناسبة تمامًا لحل المشكلات العملية ، على الرغم من وجود قيود غير حرجة.
يمكن العثور على النص الكامل للمثال هنا .
وهنا نسخة فيديو من هذا المنشور.
