JavaRush /وبلاگ جاوا /Random-FA /JDBC یا جایی که همه چیز شروع می شود
Viacheslav
مرحله

JDBC یا جایی که همه چیز شروع می شود

در گروه منتشر شد
در دنیای مدرن هیچ راهی بدون ذخیره سازی داده ها وجود ندارد. و تاریخچه کار با پایگاه های داده از مدت ها قبل با ظهور JDBC آغاز شد. من پیشنهاد می کنم چیزی را به خاطر بسپارم که هیچ چارچوب مدرنی که بر روی JDBC ساخته شده است نمی تواند بدون آن انجام دهد. علاوه بر این، حتی در هنگام کار با آنها، گاهی اوقات ممکن است به فرصتی برای "بازگشت به ریشه های خود" نیاز داشته باشید. امیدوارم این بررسی به عنوان مقدمه یا کمک به تجدید حافظه شما باشد.
JDBC یا جایی که همه چیز شروع می شود - 1

معرفی

یکی از اهداف اصلی زبان برنامه نویسی ذخیره و پردازش اطلاعات است. برای درک بهتر نحوه عملکرد ذخیره سازی داده ها، ارزش آن را دارد که کمی برای تئوری و معماری برنامه ها وقت بگذارید. به عنوان مثال، می‌توانید ادبیات، یعنی کتاب « راهنمای معمار نرم‌افزار: با پیاده‌سازی آرش مؤثر... به یک معمار موفق نرم‌افزار تبدیل شوید » اثر جوزف اینجنو را مطالعه کنید. همانطور که گفته شد، یک ردیف داده یا "لایه داده" وجود دارد. این شامل مکانی برای ذخیره داده ها (به عنوان مثال، پایگاه داده SQL) و ابزارهایی برای کار با یک فروشگاه داده است (به عنوان مثال، JDBC، که مورد بحث قرار خواهد گرفت). همچنین مقاله ای در وب سایت مایکروسافت وجود دارد: " طراحی یک لایه پایداری زیرساخت "، که راه حل معماری جداسازی یک لایه اضافی از لایه داده - لایه پایداری را توضیح می دهد. در این مورد، ردیف داده، سطح ذخیره‌سازی خود داده است، در حالی که لایه پایداری، سطحی از انتزاع برای کار با داده‌های ذخیره‌سازی از سطح ردیف داده است. لایه Persistence می تواند شامل قالب "DAO" یا ORM های مختلف باشد. اما ORM موضوعی برای بحث دیگری است. همانطور که ممکن است قبلاً متوجه شده باشید، اولین ردیف داده ظاهر شد. از زمان JDK 1.1، JDBC (اتصال پایگاه داده جاوا - اتصال به پایگاه های داده در جاوا) در دنیای جاوا ظاهر شده است. این استانداردی برای تعامل برنامه های کاربردی جاوا با DBMS های مختلف است که در قالب بسته های java.sql و javax.sql موجود در Java SE پیاده سازی شده است:
JDBC یا جایی که همه چیز شروع می شود - 2
این استاندارد با مشخصات " JSR 221 JDBC 4.1 API " توصیف شده است. این مشخصات به ما می گوید که JDBC API دسترسی برنامه ای به پایگاه های داده رابطه ای از برنامه های نوشته شده در جاوا را فراهم می کند. همچنین می گوید که JDBC API بخشی از پلتفرم جاوا است و بنابراین در Java SE و Java EE گنجانده شده است. JDBC API در دو بسته java.sql و javax.sql ارائه شده است. پس بیایید با آنها آشنا شویم.
JDBC یا جایی که همه چیز شروع می شود - 3

شروع کار

برای اینکه بفهمیم JDBC API به طور کلی چیست، به یک برنامه جاوا نیاز داریم. استفاده از یکی از سیستم های مونتاژ پروژه راحت تر است. به عنوان مثال، اجازه دهید از Gradle استفاده کنیم . می‌توانید در یک بررسی کوتاه درباره Gradle بیشتر بخوانید: " معرفی مختصر بر Gradle ". ابتدا، اجازه دهید یک پروژه Gradle جدید را مقداردهی اولیه کنیم. از آنجایی که عملکرد Gradle از طریق پلاگین ها پیاده سازی می شود، باید از “ Gradle Build Init Plugin ” برای مقداردهی اولیه استفاده کنیم:
gradle init --type java-application
پس از این، اجازه دهید اسکریپت ساخت - فایل build.gradle را باز کنیم ، که پروژه ما و نحوه کار با آن را توضیح می دهد. ما به بلوک « وابستگی‌ها » علاقه‌مندیم ، جایی که وابستگی‌ها توصیف می‌شوند - یعنی آن دسته از کتابخانه‌ها/چارچوب‌ها/api، که بدون آن‌ها نمی‌توانیم کار کنیم و به آن‌ها وابسته هستیم. به طور پیش فرض چیزی شبیه به:
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 به طور خودکار هنگام ایجاد پروژه برای ما ایجاد می کند. و همچنین به این دلیل که guava یک کتابخانه جداگانه است که با Java SE گنجانده نشده است. JUnit همچنین در Java SE گنجانده نشده است. اما JDBC خارج از جعبه داریم، یعنی بخشی از Java SE است. معلوم شد که ما JDBC داریم. عالی. دیگر چه نیازداریم؟ چنین نمودار شگفت انگیزی وجود دارد:
JDBC یا جایی که همه چیز شروع می شود - 4
همانطور که می بینیم، و این منطقی است، پایگاه داده یک مؤلفه خارجی است که بومی Java SE نیست. این به سادگی توضیح داده شده است - تعداد زیادی پایگاه داده وجود دارد و می توانید با هر کدام کار کنید. به عنوان مثال، PostgreSQL، Oracle، MySQL، H2 وجود دارد. هر یک از این پایگاه‌های اطلاعاتی توسط یک شرکت مجزا به نام فروشنده‌های پایگاه‌داده عرضه می‌شوند. هر پایگاه داده به زبان برنامه نویسی خودش (نه لزوما جاوا) نوشته شده است. برای اینکه بتواند با پایگاه داده از یک برنامه جاوا کار کند، ارائه دهنده پایگاه داده یک درایور ویژه می نویسد که آداپتور تصویر خودش است. چنین موارد سازگار با JDBC (یعنی آنهایی که دارای درایور JDBC هستند) "پایگاه داده سازگار با JDBC" نیز نامیده می شوند. در اینجا می توانیم قیاسی با دستگاه های کامپیوتری ترسیم کنیم. به عنوان مثال، در یک دفترچه یادداشت دکمه "چاپ" وجود دارد. هر بار که آن را فشار می دهید، برنامه به سیستم عامل می گوید که برنامه notepad می خواهد چاپ کند. و شما یک چاپگر دارید. برای آموزش سیستم عامل خود برای برقراری ارتباط یکسان با چاپگر Canon یا HP، به درایورهای مختلفی نیاز دارید. اما هیچ چیز برای شما به عنوان یک کاربر تغییر نخواهد کرد. شما همچنان همان دکمه را فشار می دهید. JDBC هم همینطور. شما همان کد را اجرا می کنید، فقط ممکن است پایگاه داده های مختلفی در زیر هود اجرا شوند. من فکر می کنم این یک رویکرد بسیار روشن است. هر یک از این درایورهای JDBC نوعی مصنوع، کتابخانه، فایل jar است. این وابستگی پروژه ما است. به عنوان مثال، ما می توانیم پایگاه داده " H2 Database " را انتخاب کنیم و سپس باید یک وابستگی مانند این اضافه کنیم:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
نحوه یافتن یک وابستگی و نحوه توصیف آن در وب سایت های رسمی ارائه دهنده پایگاه داده یا در " Maven Central " نشان داده شده است. همانطور که می دانید درایور JDBC یک پایگاه داده نیست. اما او فقط راهنمای آن است. اما چیزی به نام " در پایگاه های داده حافظه " وجود دارد. اینها پایگاه داده هایی هستند که در طول عمر برنامه شما در حافظه وجود دارند. به طور معمول، این اغلب برای اهداف آزمایشی یا آموزشی استفاده می شود. این به شما امکان می دهد از نصب یک سرور پایگاه داده جداگانه روی دستگاه خودداری کنید. که برای آشنایی ما با JDBC بسیار مناسب است. بنابراین جعبه ما آماده است و ما شروع می کنیم.
JDBC یا جایی که همه چیز شروع می شود - 5

ارتباط

بنابراین، ما یک درایور JDBC داریم، یک API JDBC داریم. همانطور که به یاد داریم، JDBC مخفف Java DataBase Connectivity است. بنابراین، همه چیز با اتصال شروع می شود - توانایی برقراری ارتباط. و اتصال اتصال است. بیایید دوباره به متن مشخصات JDBC بپردازیم و فهرست مطالب را بررسی کنیم. در فصل " بررسی اجمالی فصل 4 " (نمای کلی) به بخش " 4.1 ایجاد اتصال " (ایجاد اتصال) می پردازیم، گفته می شود که دو راه برای اتصال به پایگاه داده وجود دارد:
  • از طریق DriverManager
  • از طریق DataSource
بیایید با DriverManager کار کنیم. همانطور که گفته شد، DriverManager به شما اجازه می دهد تا در URL مشخص شده به پایگاه داده متصل شوید، و همچنین درایورهای JDBC را که در CLASSPATH پیدا کرده بود بارگیری می کند (و قبل از JDBC 4.0، باید کلاس درایور را خودتان بارگیری می کردید). یک فصل جداگانه "فصل 9 اتصالات" در مورد اتصال به پایگاه داده وجود دارد. ما علاقه مندیم که چگونه از طریق DriverManager اتصال برقرار کنیم، بنابراین ما به بخش "9.3 The DriverManager Class" علاقه مندیم. این نشان می دهد که چگونه می توانیم به پایگاه داده دسترسی پیدا کنیم:
Connection con = DriverManager.getConnection(url, user, passwd);
پارامترها را می توان از وب سایت پایگاه داده ای که انتخاب کرده ایم گرفته شود. در مورد ما، این H2 است - " H2 Cheat Sheet ". بیایید به کلاس 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());
	}
}
این تست، زمانی که اجرا می شود، تأیید می کند که اتصال حاصل معتبر است (به درستی ایجاد شده است) و بسته نشده است. با استفاده از try-with-resources ما منابع را زمانی که دیگر به آنها نیاز نداریم آزاد می کنیم. این ما را از افتادگی اتصالات و نشت حافظه محافظت می کند. از آنجایی که هر عملی در پایگاه داده نیاز به اتصال دارد، بیایید روش‌های آزمایشی باقیمانده با علامت @Test را با یک Connection در ابتدای آزمایش ارائه دهیم که پس از آزمایش آن را منتشر خواهیم کرد. برای انجام این کار، به دو حاشیه‌نویسی نیاز داریم: @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 Stored Procedures.
بنابراین با داشتن یک اتصال، می‌توانیم درخواستی را در چارچوب این اتصال اجرا کنیم. بنابراین، منطقی است که در ابتدا نمونه ای از عبارت SQL را از Connection بدست آوریم. شما باید با ایجاد یک جدول شروع کنید. بیایید درخواست ایجاد جدول را به عنوان یک متغیر String توصیف کنیم. چگونه انجامش بدهیم؟ بیایید از برخی آموزش‌ها مانند « 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 پارامترهای نامگذاری شده را برای PreparedStatement پشتیبانی نمی کند، بنابراین خود پارامترها با سؤالات مشخص می شوند و با تعیین مقدار، شاخص سؤال را نشان می دهیم (از 1 شروع می شود نه از صفر). در آخرین آزمایش ما true را به عنوان نشانه ای از وجود نتیجه دریافت کردیم. اما نتیجه پرس و جو در API JDBC چگونه نمایش داده می شود؟ و به صورت ResultSet ارائه می شود.
JDBC یا جایی که همه چیز شروع می شود - 7

مجموعه نتیجه

مفهوم ResultSet در مشخصات API JDBC در فصل "فصل 15 مجموعه نتایج" توضیح داده شده است. اول از همه، می گوید که ResultSet روش هایی را برای بازیابی و دستکاری نتایج پرس و جوهای اجرا شده ارائه می دهد. یعنی اگر متد execute به ما درست برگردانده شود، می توانیم ResultSet دریافت کنیم. بیایید فراخوانی را به متد createCustomerTable() به متد init منتقل کنیم که با علامت @Before مشخص شده است. حالا بیایید تست shouldSelectData خود را نهایی کنیم:
@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 به یک ردیف اشاره می کند. بنابراین، برای خواندن یک خط، باید همین مکان نما را روی آن قرار دهید. هنگامی که مکان نما جابجا می شود، در صورتی که مکان نما معتبر باشد (درست، صحیح)، یعنی به داده اشاره می کند، روش حرکت مکان نما درست برمی گردد. اگر false را برگرداند، داده ای وجود ندارد، یعنی مکان نما به داده اشاره نمی کند. اگر بخواهیم داده‌هایی را با مکان‌نمای نامعتبر دریافت کنیم، با این خطا مواجه می‌شویم: هیچ داده‌ای در دسترس نیست. همچنین جالب است که از طریق 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: استفاده از اشیاء RowSet ". انواع مختلفی از استفاده وجود دارد. برای مثال، ساده ترین حالت ممکن است به این صورت باشد:
@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 بیشتر بخوانید: " Using CachedRowSetObjects ".
JDBC یا جایی که همه چیز شروع می شود - 8

فراداده

علاوه بر پرس و جوها، اتصال به پایگاه داده (به عنوان مثال، نمونه ای از کلاس Connection) دسترسی به ابرداده را فراهم می کند - داده هایی در مورد نحوه پیکربندی و سازماندهی پایگاه داده ما. اما ابتدا به چند نکته کلیدی اشاره می کنیم: URL برای اتصال به پایگاه داده ما: "jdbc:h2:mem:test". test نام پایگاه داده ما است. برای JDBC API، این یک دایرکتوری است. و نام با حروف بزرگ، یعنی TEST خواهد بود. طرح پیش فرض برای H2 PUBLIC است. حالا بیایید تستی بنویسیم که تمام جداول کاربر را نشان دهد. چرا سفارشی؟ زیرا پایگاه داده ها نه تنها شامل جداول کاربر (آنهایی که خودمان با استفاده از عبارات جدول ایجاد کرده ایم)، بلکه جداول سیستم نیز هستند. آنها برای ذخیره اطلاعات سیستم در مورد ساختار پایگاه داده ضروری هستند. هر پایگاه داده می تواند چنین جداول سیستمی را به طور متفاوت ذخیره کند. به عنوان مثال، در H2 آنها در طرح " INFORMATION_SCHEMA " ذخیره می شوند. جالب اینجاست که INFORMATION SCHEMA یک رویکرد رایج است، اما اوراکل مسیر دیگری را طی کرد. می‌توانید در اینجا بیشتر بخوانید: " INFORMATION_SCHEMA and 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'
}
حال باید به نحوی از این استخر استفاده کنیم. برای انجام این کار، باید منبع داده را که به عنوان Datasource نیز شناخته می شود، مقداردهی اولیه کنید:
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، فصل "فصل 10 معاملات" به آنها اختصاص داده شده است. اول از همه، ارزش این را دارد که بفهمیم معامله چیست. تراکنش گروهی از عملیات متوالی منطقی ترکیب شده روی داده ها است که به طور کلی پردازش یا لغو شده است. هنگام استفاده از JDBC یک تراکنش چه زمانی شروع می شود؟ همانطور که مشخصات بیان می کند، این به طور مستقیم توسط درایور JDBC مدیریت می شود. اما معمولاً یک تراکنش جدید زمانی شروع می شود که دستور SQL فعلی به تراکنش نیاز داشته باشد و تراکنش هنوز ایجاد نشده باشد. معامله چه زمانی به پایان می رسد؟ این توسط ویژگی auto-commit کنترل می شود. اگر Autocommit فعال باشد، تراکنش پس از "تکمیل" عبارت SQL تکمیل خواهد شد. معنای "انجام شد" به نوع عبارت SQL بستگی دارد:
  • زبان دستکاری داده ها، همچنین به عنوان DML (درج، به روز رسانی، حذف) شناخته شده است
    .
  • انتخاب بیانیه ها
    با بسته شدن ResultSet تراکنش کامل می شود ( ResultSet#close )
  • CallableStatement و عباراتی که چندین نتیجه را برمی گرداند
    وقتی همه ResultSet های مرتبط بسته شده و همه خروجی ها دریافت شده است (از جمله تعداد به روز رسانی ها)
این دقیقاً نحوه رفتار JDBC API است. طبق معمول، بیایید یک تست برای این بنویسیم:
@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 الزامات یک سیستم تراکنشی را توصیف می کند.
  • اتمیسیته:
    هیچ تراکنشی تا حدی به سیستم متعهد نخواهد شد. یا تمام عملیات فرعی آن انجام می شود یا هیچ کدام انجام نمی شود.
  • سازگاری:
    طبق تعریف، هر تراکنش موفق فقط نتایج معتبر را ثبت می کند.
  • جداسازی:
    در حالی که یک تراکنش در حال اجرا است، تراکنش های همزمان نباید بر نتیجه آن تأثیر بگذارد.
  • دوام:
    اگر تراکنش با موفقیت انجام شود، تغییرات ایجاد شده در آن به دلیل عدم موفقیت لغو نمی شود.
هنگامی که در مورد سطوح انزوا تراکنش صحبت می کنیم، در مورد نیاز "Isolation" صحبت می کنیم. جداسازی یک نیاز گران است، بنابراین در پایگاه‌های داده واقعی حالت‌هایی وجود دارد که تراکنش را کاملاً ایزوله نمی‌کنند (سطوح جداسازی خواندن تکراری و پایین‌تر). ویکی‌پدیا توضیحی عالی درباره مشکلاتی که هنگام کار با تراکنش‌ها ممکن است ایجاد شود، ارائه می‌کند. ارزش خواندن بیشتر در اینجا را دارد: " مشکلات دسترسی موازی با استفاده از تراکنش ها ." قبل از اینکه آزمایش خود را بنویسیم، بیایید اسکریپت ساخت Gradle خود را کمی تغییر دهیم: یک بلوک با ویژگی ها، یعنی با تنظیمات پروژه خود اضافه کنیم:
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' و بالاتر برسانیم، READ UNCOMMITTED کار نخواهد کرد، اگرچه ما کد را تغییر ندادیم. این یک بار دیگر ثابت می کند که انتخاب نسخه فروشنده و درایور فقط حروف نیست، در واقع نحوه اجرای درخواست های شما را تعیین می کند. می‌توانید در مورد نحوه رفع این رفتار در نسخه 1.4.177 و نحوه کار نکردن آن در نسخه‌های بالاتر اینجا بخوانید: " در حالت MVStore از سطح جداسازی READ UNCOMMITTED پشتیبانی کنید ".
JDBC یا جایی که همه چیز شروع می شود - 12

خط پایین

همانطور که می بینیم JDBC یک ابزار قدرتمند در دست جاوا برای کار با پایگاه های داده است. امیدوارم این بررسی کوتاه به شما کمک کند تا نقطه شروعی داشته باشید یا به تجدید حافظه شما کمک کند. خوب، برای یک میان وعده، برخی از مواد اضافی: #ویاچسلاو
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION