JavaRush /مدونة جافا /Random-AR /JDBC أو حيث يبدأ كل شيء
Viacheslav
مستوى

JDBC أو حيث يبدأ كل شيء

نشرت في المجموعة
في العالم الحديث، لا توجد وسيلة دون تخزين البيانات. وبدأ تاريخ العمل مع قواعد البيانات منذ وقت طويل جدًا، مع ظهور JDBC. أقترح أن نتذكر شيئًا لا يمكن لأي إطار عمل حديث مبني على JDBC الاستغناء عنه. بالإضافة إلى ذلك، حتى عند العمل معهم، في بعض الأحيان قد تحتاج إلى فرصة "للعودة إلى جذورك". آمل أن تساعد هذه المراجعة كمقدمة أو تساعد في تحديث ذاكرتك.
JDBC أو حيث يبدأ كل شيء - 1

مقدمة

أحد الأغراض الرئيسية للغة البرمجة هو تخزين المعلومات ومعالجتها. لفهم كيفية عمل تخزين البيانات بشكل أفضل، من المفيد قضاء بعض الوقت في دراسة نظرية التطبيقات وبنيتها. على سبيل المثال، يمكنك قراءة الأدبيات، وبالتحديد كتاب " دليل مهندس البرمجيات: كن مهندس برمجيات ناجحًا من خلال تنفيذ القوس الفعال... " بقلم جوزيف إنجينو. كما قيل، هناك طبقة بيانات معينة أو "طبقة بيانات". يتضمن مكانًا لتخزين البيانات (على سبيل المثال، قاعدة بيانات SQL) وأدوات للعمل مع مخزن البيانات (على سبيل المثال، JDBC، والتي سيتم مناقشتها). يوجد أيضًا مقال على موقع Microsoft على الويب: " تصميم طبقة ثبات البنية التحتية "، والذي يصف الحل المعماري لفصل طبقة إضافية عن طبقة البيانات - طبقة الثبات. في هذه الحالة، طبقة البيانات هي مستوى تخزين البيانات نفسها، في حين أن طبقة الثبات هي مستوى معين من التجريد للعمل مع البيانات من التخزين من مستوى طبقة البيانات. يمكن أن تتضمن طبقة الثبات قالب "DAO" أو ORMs المختلفة. لكن ORM موضوع لمناقشة أخرى. كما كنت قد فهمت بالفعل، ظهرت طبقة البيانات أولاً. منذ JDK 1.1، ظهر JDBC (اتصال قاعدة بيانات Java - الاتصال بقواعد البيانات في Java) في عالم Java. يعد هذا معيارًا لتفاعل تطبيقات Java مع أنظمة إدارة قواعد البيانات المختلفة، ويتم تنفيذه في شكل حزم java.sql وjavax.sql المضمنة في Java SE:
JDBC أو حيث يبدأ كل شيء - 2
تم وصف هذا المعيار من خلال المواصفات " JSR 221 JDBC 4.1 API ". تخبرنا هذه المواصفات أن JDBC API توفر وصولاً برمجيًا إلى قواعد البيانات العلائقية من البرامج المكتوبة بلغة Java. ويوضح أيضًا أن JDBC API جزء من نظام Java الأساسي وبالتالي فهو مضمن في Java SE وJava EE. يتم توفير JDBC API في حزمتين: java.sql وjavax.sql. دعونا نتعرف عليهم بعد ذلك.
JDBC أو حيث يبدأ كل شيء - 3

بداية العمل

لفهم ماهية JDBC API بشكل عام، نحتاج إلى تطبيق Java. من الأكثر ملاءمة استخدام أحد أنظمة تجميع المشروع. على سبيل المثال، دعونا نستخدم Gradle . يمكنك قراءة المزيد عن Gradle في هذه المراجعة القصيرة: " مقدمة موجزة عن Gradle ". أولاً، لنقم بتهيئة مشروع Gradle جديد. نظرًا لأنه يتم تنفيذ وظيفة Gradle من خلال المكونات الإضافية، فإننا نحتاج إلى استخدام " Gradle Build Init Plugin " للتهيئة:
gradle init --type java-application
بعد ذلك، لنفتح برنامج البناء النصي - ملف build.gradle ، الذي يصف مشروعنا وكيفية العمل معه. نحن مهتمون بكتلة " التبعيات "، حيث يتم وصف التبعيات - أي تلك المكتبات/الأطر/واجهة برمجة التطبيقات، التي بدونها لا يمكننا العمل والتي نعتمد عليها. بشكل افتراضي سنرى شيئًا مثل:
dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}
لماذا نرى هذا هنا؟ هذه هي تبعيات مشروعنا التي أنشأها Gradle تلقائيًا لنا عند إنشاء المشروع. وأيضًا لأن الجوافة عبارة عن مكتبة منفصلة غير مضمنة في Java SE. لم يتم تضمين JUnit أيضًا في Java SE. ولكن لدينا JDBC خارج الصندوق، أي أنه جزء من Java SE. اتضح أن لدينا JDBC. عظيم. ماذا نحتاج ايضا؟ هناك مثل هذا المخطط الرائع:
JDBC أو حيث يبدأ كل شيء - 4
كما نرى، وهذا أمر منطقي، فإن قاعدة البيانات هي مكون خارجي ليس أصليًا في Java SE. يتم شرح ذلك ببساطة - يوجد عدد كبير من قواعد البيانات ويمكنك العمل مع أي منها. على سبيل المثال، هناك PostgreSQL، وOracle، وMySQL، وH2. يتم توفير كل قاعدة من قواعد البيانات هذه بواسطة شركة منفصلة تسمى بائعي قواعد البيانات. تتم كتابة كل قاعدة بيانات بلغة البرمجة الخاصة بها (وليس بالضرورة Java). لكي تتمكن من العمل مع قاعدة البيانات من خلال تطبيق Java، يقوم موفر قاعدة البيانات بكتابة برنامج تشغيل خاص، وهو محول الصور الخاص به. تُسمى أيضًا تلك المتوافقة مع JDBC (أي تلك التي تحتوي على برنامج تشغيل JDBC) بـ "قاعدة البيانات المتوافقة مع JDBC". هنا يمكننا رسم تشبيه بأجهزة الكمبيوتر. على سبيل المثال، يوجد في المفكرة زر "طباعة". في كل مرة تضغط عليها، يخبر البرنامج نظام التشغيل أن تطبيق المفكرة يريد الطباعة. ولديك طابعة. لتعليم نظام التشغيل الخاص بك كيفية الاتصال بشكل موحد مع طابعة Canon أو HP، ستحتاج إلى برامج تشغيل مختلفة. لكن بالنسبة لك، كمستخدم، لن يتغير شيء. ستظل تضغط على نفس الزر. نفس الشيء مع JDBC. أنت تقوم بتشغيل نفس الكود، كل ما في الأمر هو أن قواعد بيانات مختلفة قد تكون قيد التشغيل تحت الغطاء. أعتقد أن هذا نهج واضح للغاية. كل برنامج تشغيل JDBC هو نوع من القطع الأثرية أو المكتبة أو ملف jar. هذه هي التبعية لمشروعنا. على سبيل المثال، يمكننا تحديد قاعدة البيانات " H2 Database " ثم نحتاج إلى إضافة تبعية مثل هذا:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
تتم الإشارة إلى كيفية العثور على التبعية وكيفية وصفها على المواقع الرسمية لموفر قاعدة البيانات أو على " Maven Central ". برنامج تشغيل JDBC ليس قاعدة بيانات، كما تفهم. وإنما هو إلا دليل عليه. ولكن هناك شيء مثل " في قواعد بيانات الذاكرة ". هذه هي قواعد البيانات الموجودة في الذاكرة طوال عمر التطبيق الخاص بك. عادة، يتم استخدام هذا غالبًا لأغراض الاختبار أو التدريب. يتيح لك ذلك تجنب تثبيت خادم قاعدة بيانات منفصل على الجهاز. وهو مناسب جدًا لنا للتعرف على JDBC. لذا فإن صندوق الرمل الخاص بنا جاهز ونبدأ.
JDBC أو حيث يبدأ كل شيء - 5

اتصال

لذلك، لدينا برنامج تشغيل JDBC، ولدينا واجهة برمجة تطبيقات JDBC. كما نتذكر، JDBC يرمز إلى Java DataBase Connectivity. لذلك، يبدأ كل شيء بالاتصال - القدرة على إنشاء اتصال. والاتصال هو الاتصال. دعنا ننتقل مرة أخرى إلى نص مواصفات JDBC وننظر إلى جدول المحتويات. في الفصل " نظرة عامة على الفصل 4 " (نظرة عامة) ننتقل إلى القسم " 4.1 إنشاء اتصال " (إنشاء اتصال) يقال أن هناك طريقتين للاتصال بقاعدة البيانات:
  • عبر مدير السائق
  • عبر مصدر البيانات
دعونا نتعامل مع DriverManager. كما ذكرنا، يسمح لك DriverManager بالاتصال بقاعدة البيانات على عنوان URL المحدد، كما يقوم بتحميل برامج تشغيل JDBC التي وجدها في CLASSPATH (وقبل ذلك، قبل JDBC 4.0، كان عليك تحميل فئة برنامج التشغيل بنفسك). يوجد فصل منفصل "الفصل 9 الاتصالات" حول الاتصال بقاعدة البيانات. نحن مهتمون بكيفية الحصول على اتصال من خلال DriverManager، لذلك نحن مهتمون بقسم "9.3 فئة DriverManager". يشير إلى كيفية الوصول إلى قاعدة البيانات:
Connection con = DriverManager.getConnection(url, user, passwd);
يمكن أخذ المعلمات من الموقع الإلكتروني لقاعدة البيانات التي اخترناها. في حالتنا، هذا هو H2 - " ورقة الغش H2 ". دعنا ننتقل إلى فئة AppTest التي أعدها Gradle. أنه يحتوي على اختبارات JUnit. اختبار JUnit هو أسلوب تم وضع علامة عليه بتعليق توضيحي @Test. اختبارات الوحدة ليست موضوع هذه المراجعة، لذلك سنقتصر ببساطة على فهم أن هذه الطرق موصوفة بطريقة معينة، والغرض منها هو اختبار شيء ما. وفقًا لمواصفات JDBC وموقع H2، سنتحقق من أننا تلقينا اتصالاً بقاعدة البيانات. لنكتب طريقة للحصول على الاتصال:
private Connection getNewConnection() throws SQLException {
	String url = "jdbc:h2:mem:test";
	String user = "sa";
	String passwd = "sa";
	return DriverManager.getConnection(url, user, passwd);
}
لنكتب الآن اختبارًا لهذه الطريقة للتحقق من إنشاء الاتصال بالفعل:
@Test
public void shouldGetJdbcConnection() throws SQLException {
	try(Connection connection = getNewConnection()) {
		assertTrue(connection.isValid(1));
		assertFalse(connection.isClosed());
	}
}
سيتحقق هذا الاختبار، عند تنفيذه، من أن الاتصال الناتج صالح (تم إنشاؤه بشكل صحيح) وأنه غير مغلق. باستخدام أداة المحاولة باستخدام الموارد، سنحرر الموارد عندما لا نكون بحاجة إليها. هذا سوف يحمينا من تراجع الاتصالات وتسرب الذاكرة. نظرًا لأن أي إجراءات في قاعدة البيانات تتطلب اتصالاً، فلنوفر طرق الاختبار المتبقية التي تحمل علامة @Test مع اتصال في بداية الاختبار، والتي سنصدرها بعد الاختبار. للقيام بذلك، نحتاج إلى تعليقين: @Before و@After. لنضيف حقلًا جديدًا إلى فئة AppTest الذي سيخزن اتصال JDBC للاختبارات:
private static Connection connection;
ودعونا نضيف أساليب جديدة:
@Before
public void init() throws SQLException {
	connection = getNewConnection();
}
@After
public void close() throws SQLException {
	connection.close();
}
الآن، يتم ضمان أن يكون لأي طريقة اختبار اتصال JDBC ولا يلزم إنشائها بنفسها في كل مرة.
JDBC أو حيث يبدأ كل شيء - 6

صياغات

بعد ذلك نحن مهتمون بالبيانات أو التعبيرات. تم وصفها في الوثائق في الفصل " الفصل 13 البيانات ". أولاً، تقول أن هناك عدة أنواع أو أنواع من البيانات:
  • البيان: تعبير SQL لا يحتوي على أي معلمات
  • PreparedStatement: عبارة SQL مُجهزة تحتوي على معلمات الإدخال
  • CallableStatement: تعبير SQL مع القدرة على الحصول على قيمة إرجاع من إجراءات SQL المخزنة.
لذا، بوجود اتصال، يمكننا تنفيذ بعض الطلبات في إطار هذا الاتصال. ولذلك، فمن المنطقي أن نحصل في البداية على مثيل لتعبير SQL من Connection. عليك أن تبدأ بإنشاء جدول. دعونا نصف طلب إنشاء الجدول كمتغير سلسلة. كيف افعلها؟ دعنا نستخدم بعض البرامج التعليمية مثل " sqltutorial.org "، و" sqlbolt.com "، و" postgresqltutorial.com "، و" codecademy.com ". دعونا نستخدم، على سبيل المثال، مثالاً من دورة SQL على khanacademy.org . دعونا نضيف طريقة لتنفيذ تعبير في قاعدة البيانات:
private int executeUpdate(String query) throws SQLException {
	Statement statement = connection.createStatement();
	// Для Insert, Update, Delete
	int result = statement.executeUpdate(query);
	return result;
}
لنضيف طريقة لإنشاء جدول اختبار باستخدام الطريقة السابقة:
private void createCustomerTable() throws SQLException {
	String customerTableQuery = "CREATE TABLE customers " +
                "(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)";
	String customerEntryQuery = "INSERT INTO customers " +
                "VALUES (73, 'Brian', 33)";
	executeUpdate(customerTableQuery);
	executeUpdate(customerEntryQuery);
}
الآن دعونا نختبر هذا:
@Test
public void shouldCreateCustomerTable() throws SQLException {
	createCustomerTable();
	connection.createStatement().execute("SELECT * FROM customers");
}
الآن لننفذ الطلب، وحتى باستخدام المعلمة:
@Test
public void shouldSelectData() throws SQLException {
 	createCustomerTable();
 	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
}
لا يدعم JDBC المعلمات المسماة لـ PreparationStatement، لذلك يتم تحديد المعلمات نفسها عن طريق الأسئلة، ومن خلال تحديد القيمة نشير إلى فهرس الأسئلة (يبدأ من 1، وليس صفر). في الاختبار الأخير تلقينا صحيحا كدليل على ما إذا كانت هناك نتيجة. ولكن كيف يتم تمثيل نتيجة الاستعلام في JDBC API؟ ويتم تقديمه كـ ResultSet.
JDBC أو حيث يبدأ كل شيء - 7

مجموعة النتائج

تم وصف مفهوم ResultSet في مواصفات JDBC API في الفصل "الفصل 15 مجموعات النتائج". أولاً، تقول أن ResultSet توفر طرقًا لاسترداد نتائج الاستعلامات المنفذة ومعالجتها. أي أنه إذا عادت طريقة التنفيذ إلينا، فيمكننا الحصول على ResultSet. لننقل الاستدعاء إلى الأسلوب createCustomerTable() إلى الأسلوب init، والذي تم وضع علامة @Before عليه. الآن دعونا ننتهي من اختبار mustSelectData:
@Test
public void shouldSelectData() throws SQLException {
	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
	// Обработаем результат
	ResultSet resultSet = statement.getResultSet();
	resultSet.next();
	int age = resultSet.getInt("age");
	assertEquals(33, age);
}
تجدر الإشارة هنا إلى أن الطريقة التالية هي تحريك ما يسمى بـ "المؤشر". يشير المؤشر الموجود في ResultSet إلى بعض الصفوف. وبالتالي، من أجل قراءة السطر، تحتاج إلى وضع هذا المؤشر عليه. عند تحريك المؤشر، ترجع طريقة تحريك المؤشر صحيحًا إذا كان المؤشر صالحًا (صحيحًا)، أي أنه يشير إلى البيانات. إذا أعادت خطأ، فهذا يعني أنه لا توجد بيانات، أي أن المؤشر لا يشير إلى البيانات. إذا حاولنا الحصول على بيانات باستخدام مؤشر غير صالح، فسوف نحصل على الخطأ: لا توجد بيانات متاحة. ومن المثير للاهتمام أيضًا أنه من خلال ResultSet يمكنك تحديث أو حتى إدراج صفوف:
@Test
public void shouldInsertInResultSet() throws SQLException {
	Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
	ResultSet resultSet = statement.executeQuery("SELECT * FROM customers");
	resultSet.moveToInsertRow();
	resultSet.updateLong("id", 3L);
	resultSet.updateString("name", "John");
	resultSet.updateInt("age", 18);
	resultSet.insertRow();
	resultSet.moveToCurrentRow();
}

مجموعة الصفوف

بالإضافة إلى ResultSet، يقدم JDBC مفهوم RowSet. يمكنك قراءة المزيد هنا: " أساسيات JDBC: استخدام كائنات مجموعة الصفوف ". هناك اختلافات مختلفة في الاستخدام. على سبيل المثال، قد تبدو أبسط حالة كما يلي:
@Test
public void shouldUseRowSet() throws SQLException {
 	JdbcRowSet jdbcRs = new JdbcRowSetImpl(connection);
 	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	jdbcRs.next();
	String name = jdbcRs.getString("name");
	assertEquals("Brian", name);
}
كما ترون، RowSet يشبه تكافل البيان (حددنا الأمر من خلاله) والأمر المنفذ. ومن خلاله نتحكم في المؤشر (عن طريق استدعاء الطريقة التالية) ونحصل على البيانات منه. ليس هذا النهج مثيرًا للاهتمام فحسب، بل أيضًا التطبيقات الممكنة. على سبيل المثال، CachedRowSet. إنه "غير متصل" (أي أنه لا يستخدم اتصالاً مستمرًا بقاعدة البيانات) ويتطلب مزامنة صريحة مع قاعدة البيانات:
CachedRowSet jdbcRsCached = new CachedRowSetImpl();
jdbcRsCached.acceptChanges(connection);
يمكنك قراءة المزيد في البرنامج التعليمي على موقع Oracle الإلكتروني: " استخدام CachedRowSetObjects ".
JDBC أو حيث يبدأ كل شيء - 8

البيانات الوصفية

بالإضافة إلى الاستعلامات، يوفر الاتصال بقاعدة البيانات (أي مثيل لفئة الاتصال) الوصول إلى البيانات الوصفية - بيانات حول كيفية تكوين قاعدة البيانات الخاصة بنا وتنظيمها. لكن أولاً، دعنا نذكر بعض النقاط الأساسية: عنوان URL للاتصال بقاعدة البيانات الخاصة بنا: "jdbc:h2:mem:test". الاختبار هو اسم قاعدة البيانات الخاصة بنا. بالنسبة لواجهة برمجة تطبيقات JDBC، هذا دليل. وسيكون الاسم بالأحرف الكبيرة، أي TEST. المخطط الافتراضي لـ H2 هو PUBLIC. الآن، لنكتب اختبارًا يُظهر جميع جداول المستخدم. لماذا العرف؟ لأن قواعد البيانات لا تحتوي فقط على جداول المستخدم (تلك التي أنشأناها بأنفسنا باستخدام إنشاء تعبيرات الجدول)، ولكن أيضًا جداول النظام. وهي ضرورية لتخزين معلومات النظام حول بنية قاعدة البيانات. يمكن لكل قاعدة بيانات تخزين جداول النظام هذه بشكل مختلف. على سبيل المثال، في H2 يتم تخزينها في مخطط " INFORMATION_SCHEMA ". ومن المثير للاهتمام أن مخطط المعلومات يعد أسلوبًا شائعًا، لكن Oracle سلكت طريقًا مختلفًا. يمكنك قراءة المزيد هنا: " INFORMATION_SCHEMA و Oracle ". لنكتب اختبارًا يتلقى البيانات الوصفية على جداول المستخدم:
@Test
public void shoudGetMetadata() throws SQLException {
	// У нас URL = "jdbc:h2:mem:test", где test - название БД
	// Название БД = catalog
	DatabaseMetaData metaData = connection.getMetaData();
	ResultSet result = metaData.getTables("TEST", "PUBLIC", "%", null);
	List<String> tables = new ArrayList<>();
	while(result.next()) {
		tables.add(result.getString(2) + "." + result.getString(3));
	}
	assertTrue(tables.contains("PUBLIC.CUSTOMERS"));
}
JDBC أو حيث يبدأ كل شيء - 9

تجمع اتصال

يحتوي تجمع الاتصال في مواصفات JDBC على قسم يسمى "تجمع الاتصال للفصل 11". كما أنه يوفر المبرر الرئيسي للحاجة إلى تجمع اتصال. كل Coonection هو اتصال فعلي بقاعدة البيانات. إن إنشائها وإغلاقها مهمة "باهظة الثمن" إلى حد ما. يوفر JDBC فقط واجهة API لتجميع الاتصالات. ولذلك يبقى خيار التنفيذ خيارنا. على سبيل المثال، تتضمن هذه التطبيقات HikariCP . وفقًا لذلك، سنحتاج إلى إضافة تجمع إلى تبعية مشروعنا:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
    implementation 'com.zaxxer:HikariCP:3.3.1'
    testImplementation 'junit:junit:4.12'
}
الآن نحن بحاجة إلى استخدام هذا التجمع بطريقة أو بأخرى. للقيام بذلك، تحتاج إلى تهيئة مصدر البيانات، المعروف أيضًا باسم مصدر البيانات:
private DataSource getDatasource() {
	HikariConfig config = new HikariConfig();
	config.setUsername("sa");
	config.setPassword("sa");
	config.setJdbcUrl("jdbc:h2:mem:test");
	DataSource ds = new HikariDataSource(config);
	return ds;
}
ودعنا نكتب اختبارًا لتلقي اتصال من التجمع:
@Test
public void shouldGetConnectionFromDataSource() throws SQLException {
	DataSource datasource = getDatasource();
	try (Connection con = datasource.getConnection()) {
		assertTrue(con.isValid(1));
	}
}
JDBC أو حيث يبدأ كل شيء - 10

المعاملات

واحدة من الأشياء الأكثر إثارة للاهتمام حول JDBC هي المعاملات. في مواصفات JDBC، تم تخصيص الفصل "الفصل العاشر من المعاملات". بادئ ذي بدء، من المفيد أن نفهم ما هي الصفقة. المعاملة عبارة عن مجموعة من العمليات المتسلسلة المدمجة منطقيًا على البيانات، والتي تتم معالجتها أو إلغاؤها ككل. متى تبدأ المعاملة عند استخدام JDBC؟ كما تنص المواصفات، يتم التعامل مع هذا مباشرة بواسطة برنامج تشغيل JDBC. ولكن عادة، تبدأ معاملة جديدة عندما تتطلب عبارة SQL الحالية معاملة ولم يتم إنشاء المعاملة بعد. متى تنتهي الصفقة؟ يتم التحكم في ذلك من خلال سمة الالتزام التلقائي. إذا تم تمكين الالتزام التلقائي، فسيتم إكمال المعاملة بعد "اكتمال" عبارة SQL. يعتمد معنى "تم" على نوع تعبير SQL:
  • لغة معالجة البيانات، والمعروفة أيضًا باسم DML (إدراج، تحديث، حذف)
    تكتمل المعاملة بمجرد اكتمال الإجراء
  • تحديد البيانات
    تكتمل المعاملة عند إغلاق ResultSet ( ResultSet# Close )
  • CallableStatement والتعبيرات التي تُرجع نتائج متعددة
    عند إغلاق جميع مجموعات النتائج المرتبطة وتلقي جميع المخرجات (بما في ذلك عدد التحديثات)
هذا هو بالضبط كيف تتصرف واجهة برمجة تطبيقات JDBC. كالعادة، دعونا نكتب اختبارا لهذا:
@Test
public void shouldCommitTransaction() throws SQLException {
	connection.setAutoCommit(false);
	String query = "INSERT INTO customers VALUES (1, 'Max', 20)";
	connection.createStatement().executeUpdate(query);
	connection.commit();
	Statement statement = connection.createStatement();
 	statement.execute("SELECT * FROM customers");
	ResultSet resultSet = statement.getResultSet();
	int count = 0;
	while(resultSet.next()) {
		count++;
	}
	assertEquals(2, count);
}
انه سهل. ولكن هذا صحيح طالما لدينا معاملة واحدة فقط. ماذا تفعل عندما يكون هناك العديد منهم؟ يجب عزلهم عن بعضهم البعض. لذلك، دعونا نتحدث عن مستويات عزل المعاملات وكيفية تعامل JDBC معها.
JDBC أو حيث يبدأ كل شيء - 11

مستويات العزل

دعونا نفتح القسم الفرعي "10.2 مستويات عزل المعاملات" من مواصفات JDBC. هنا، قبل المضي قدمًا، أود أن أتذكر شيئًا مثل ACID. يصف ACID متطلبات نظام المعاملات.
  • الذرية:
    لن يتم الالتزام بأي معاملة جزئيًا بالنظام. إما سيتم تنفيذ كافة العمليات الفرعية الخاصة به، أو لن يتم تنفيذ أي منها.
  • الاتساق:
    كل معاملة ناجحة، بحكم تعريفها، تسجل نتائج صالحة فقط.
  • العزلة:
    أثناء تشغيل المعاملة، يجب ألا تؤثر المعاملات المتزامنة على نتائجها.
  • المتانة:
    إذا اكتملت المعاملة بنجاح، فلن يتم التراجع عن التغييرات التي تم إجراؤها عليها بسبب أي فشل.
عندما نتحدث عن مستويات عزل المعاملات، فإننا نتحدث عن متطلبات "العزل". يعد العزل متطلبًا مكلفًا، لذلك توجد في قواعد البيانات الحقيقية أوضاع لا تعزل المعاملة تمامًا (مستويات عزل القراءة المتكررة وأقل). تحتوي ويكيبيديا على شرح ممتاز للمشكلات التي قد تنشأ عند التعامل مع المعاملات. يجدر قراءة المزيد هنا: " مشاكل الوصول الموازي باستخدام المعاملات ". قبل أن نكتب اختبارنا، دعونا نغير برنامجنا النصي Gradle Build قليلاً: أضف كتلة تحتوي على خصائص، أي مع إعدادات مشروعنا:
ext {
    h2Version = '1.3.176' // 1.4.177
    hikariVersion = '3.3.1'
    junitVersion = '4.12'
}
بعد ذلك، نستخدم هذا في الإصدارات:
dependencies {
    implementation "com.h2database:h2:${h2Version}"
    implementation "com.zaxxer:HikariCP:${hikariVersion}"
    testImplementation "junit:junit:${junitVersion}"
}
ربما لاحظت أن إصدار h2 أصبح أقل. سنرى لماذا لاحقا. فكيف يمكنك تطبيق مستويات العزل؟ دعونا نلقي نظرة على مثال عملي صغير على الفور:
@Test
public void shouldGetReadUncommited() throws SQLException {
	Connection first = getNewConnection();
	assertTrue(first.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));
	first.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
	first.setAutoCommit(false);
	// Транзакиця на подключение. Поэтому первая транзакция с ReadUncommited вносит изменения
	String insertQuery = "INSERT INTO customers VALUES (5, 'Max', 15)";
	first.createStatement().executeUpdate(insertQuery);
	// Вторая транзакция пытается их увидеть
	int rowCount = 0;
	JdbcRowSet jdbcRs = new JdbcRowSetImpl(getNewConnection());
	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	while (jdbcRs.next()) {
		rowCount++;
	}
	assertEquals(2, rowCount);
}
ومن المثير للاهتمام أن هذا الاختبار قد يفشل مع بائع لا يدعم TRANSACTION_READ_UNCOMMITTED (على سبيل المثال، sqlite أو HSQL). وقد لا يعمل مستوى المعاملة ببساطة. هل تتذكر أننا أشرنا إلى إصدار برنامج تشغيل قاعدة بيانات H2؟ إذا قمنا برفعه إلى h2Version = '1.4.177' وأعلى، فإن القراءة غير الملتزم بها ستتوقف عن العمل، على الرغم من أننا لم نغير الكود. وهذا يثبت مرة أخرى أن اختيار البائع وإصدار برنامج التشغيل لا يقتصر على الحروف فحسب، بل سيحدد في الواقع كيفية تنفيذ طلباتك. يمكنك القراءة عن كيفية إصلاح هذا السلوك في الإصدار 1.4.177 وكيف أنه لا يعمل في الإصدارات الأعلى هنا: " دعم قراءة مستوى العزل غير الملتزم به في وضع MVStore ".
JDBC أو حيث يبدأ كل شيء - 12

الحد الأدنى

كما نرى، JDBC هي أداة قوية في يد Java للعمل مع قواعد البيانات. آمل أن تساعدك هذه المراجعة القصيرة في إعطائك نقطة بداية أو المساعدة في تحديث ذاكرتك. حسنا، لتناول وجبة خفيفة، بعض المواد الإضافية: # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION