ในโลกสมัยใหม่ ไม่มีทางเลยหากไม่มีการจัดเก็บข้อมูล และประวัติความเป็นมาของการทำงานกับฐานข้อมูลก็เริ่มขึ้นเมื่อนานมาแล้วด้วยการถือกำเนิดของ JDBC ฉันเสนอให้จดจำบางสิ่งที่ไม่มีเฟรมเวิร์กสมัยใหม่ที่สร้างขึ้นบน JDBC สามารถทำได้หากไม่มี นอกจากนี้ แม้จะทำงานร่วมกับพวกเขา แต่บางครั้งคุณอาจต้องการโอกาส "กลับคืนสู่รากเหง้าของคุณ" ฉันหวังว่ารีวิวนี้จะช่วยเป็นการแนะนำหรือช่วยฟื้นฟูความจำของคุณได้
มาตรฐานนี้อธิบายโดยข้อกำหนด " JSR 221 JDBC 4.1 API " ข้อมูลจำเพาะนี้บอกเราว่า JDBC API ให้การเข้าถึงฐานข้อมูลเชิงสัมพันธ์ทางโปรแกรมจากโปรแกรมที่เขียนด้วย Java นอกจากนี้ยังแจ้งด้วยว่า JDBC API เป็นส่วนหนึ่งของแพลตฟอร์ม Java และรวมอยู่ใน Java SE และ Java EE JDBC API มีให้ในสองแพ็คเกจ: java.sql และ javax.sql มาทำความรู้จักกับพวกเขากันดีกว่า
ดังที่เราเห็นและนี่คือตรรกะ ฐานข้อมูลเป็นส่วนประกอบภายนอกที่ไม่ใช่ของ Java SE นี่เป็นคำอธิบายง่ายๆ - มีฐานข้อมูลจำนวนมากและคุณสามารถทำงานกับฐานข้อมูลใดก็ได้ ตัวอย่างเช่น มี PostgreSQL, Oracle, MySQL, H2 แต่ละฐานข้อมูลเหล่านี้จัดทำโดยบริษัทแยกต่างหากที่เรียกว่าผู้จำหน่ายฐานข้อมูล แต่ละฐานข้อมูลเขียนด้วยภาษาโปรแกรมของตัวเอง (ไม่จำเป็นต้องเป็น Java) เพื่อให้สามารถทำงานกับฐานข้อมูลจากแอปพลิเคชัน Java ผู้ให้บริการฐานข้อมูลจะเขียนไดรเวอร์พิเศษซึ่งเป็นอะแดปเตอร์รูปภาพของตัวเอง รายการที่เข้ากันได้กับ JDBC ดังกล่าว (นั่นคือ ผู้ที่มีไดรเวอร์ JDBC) เรียกอีกอย่างว่า "ฐานข้อมูลที่สอดคล้องกับ JDBC" ที่นี่เราสามารถวาดความคล้ายคลึงกับอุปกรณ์คอมพิวเตอร์ได้ ตัวอย่างเช่น ในแผ่นจดบันทึกจะมีปุ่ม "พิมพ์" ทุกครั้งที่คุณกด โปรแกรมจะแจ้งระบบปฏิบัติการว่าแอพพลิเคชั่นแผ่นจดบันทึกต้องการพิมพ์ และคุณมีเครื่องพิมพ์ หากต้องการสอนระบบปฏิบัติการของคุณให้สื่อสารกับเครื่องพิมพ์ Canon หรือ HP อย่างเท่าเทียมกัน คุณจะต้องมีไดรเวอร์ที่แตกต่างกัน แต่สำหรับคุณในฐานะผู้ใช้จะไม่มีอะไรเปลี่ยนแปลง คุณจะยังคงกดปุ่มเดิม เช่นเดียวกับ JDBC คุณกำลังใช้รหัสเดียวกัน เพียงแต่ว่าฐานข้อมูลที่แตกต่างกันอาจทำงานภายใต้ประทุน ฉันคิดว่านี่เป็นแนวทางที่ชัดเจนมาก ไดรเวอร์ JDBC แต่ละตัวนั้นเป็นอาร์ติแฟกต์ ไลบรารี ไฟล์ jar นี่คือการพึ่งพาสำหรับโครงการของเรา ตัวอย่างเช่น เราสามารถเลือกฐานข้อมูล " H2 Database " จากนั้นเราจำเป็นต้องเพิ่มการขึ้นต่อกันดังนี้:
การแนะนำ
วัตถุประสงค์หลักประการหนึ่งของภาษาการเขียนโปรแกรมคือการจัดเก็บและประมวลผลข้อมูล เพื่อให้เข้าใจวิธีการทำงานของพื้นที่จัดเก็บข้อมูลได้ดีขึ้น คุณควรใช้เวลาเพียงเล็กน้อยกับทฤษฎีและสถาปัตยกรรมของแอปพลิเคชัน ตัวอย่างเช่น คุณสามารถอ่านวรรณกรรม เช่น หนังสือ " คู่มือสถาปนิกซอฟต์แวร์: มาเป็นสถาปนิกซอฟต์แวร์ที่ประสบความสำเร็จโดยการใช้ Arch... " โดย Joseph Ingeno อย่างที่บอกไปว่ามี Data Tier หรือ “Data Layer” อยู่ระดับหนึ่ง ประกอบด้วยสถานที่จัดเก็บข้อมูล (เช่น ฐานข้อมูล SQL) และเครื่องมือสำหรับการทำงานกับที่เก็บข้อมูล (เช่น JDBC ซึ่งจะกล่าวถึง) นอกจากนี้ยังมีบทความบนเว็บไซต์ Microsoft: “ การออกแบบเลเยอร์การคงอยู่ของโครงสร้างพื้นฐาน ” ซึ่งอธิบายโซลูชันทางสถาปัตยกรรมของการแยกเลเยอร์เพิ่มเติมจาก Data Tier - Persistence Layer ในกรณีนี้ Data Tier คือระดับของการจัดเก็บข้อมูล ในขณะที่ Persistence Layer คือระดับนามธรรมสำหรับการทำงานกับข้อมูลจากที่จัดเก็บข้อมูลจากระดับ Data Tier Persistence Layer สามารถรวมเทมเพลต "DAO" หรือ ORM ต่างๆ ได้ แต่ ORM เป็นหัวข้อสำหรับการสนทนาอื่น ดังที่คุณอาจเข้าใจแล้ว Data Tier ปรากฏขึ้นก่อน ตั้งแต่ JDK 1.1 JDBC (การเชื่อมต่อ Java DataBase - การเชื่อมต่อกับฐานข้อมูลใน Java) ได้ปรากฏตัวในโลก Java นี่เป็นมาตรฐานสำหรับการโต้ตอบของแอปพลิเคชัน Java กับ DBMS ต่างๆ ซึ่งใช้งานในรูปแบบของแพ็คเกจ java.sql และ javax.sql ที่รวมอยู่ใน Java SE:

จุดเริ่มต้นของการทำงาน
เพื่อให้เข้าใจว่า JDBC API โดยทั่วไปคืออะไร เราจำเป็นต้องมีแอปพลิเคชัน Java สะดวกที่สุดในการใช้ระบบประกอบโครงการอย่างใดอย่างหนึ่ง ตัวอย่าง เช่นลองใช้Gradle คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Gradle ได้ในบทวิจารณ์สั้น ๆ: " A Brief Introduction to 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 สร้างขึ้นให้เราโดยอัตโนมัติเมื่อสร้างโครงการ และเนื่องจากฝรั่งเป็นไลบรารี่แยกต่างหากที่ไม่รวมอยู่ใน Java SE JUnit ไม่ได้รวมอยู่ใน Java SE เช่นกัน แต่เรามี JDBC อยู่แล้ว นั่นคือมันเป็นส่วนหนึ่งของ Java SE ปรากฎว่าเรามี JDBC ยอดเยี่ยม. เราต้องการอะไรอีก? มีแผนภาพที่ยอดเยี่ยมเช่นนี้:

dependencies {
implementation 'com.h2database:h2:1.4.197'
วิธีค้นหาการพึ่งพาและวิธีอธิบายมีการระบุไว้ในเว็บไซต์อย่างเป็นทางการของผู้ให้บริการฐานข้อมูลหรือใน " Maven Central " ไดรเวอร์ JDBC ไม่ใช่ฐานข้อมูลตามที่คุณเข้าใจ แต่เขาเป็นเพียงผู้ชี้ทางเท่านั้น แต่มีสิ่งเช่น " ในฐานข้อมูลหน่วยความจำ " เหล่านี้เป็นฐานข้อมูลที่มีอยู่ในหน่วยความจำตลอดอายุการใช้งานของแอปพลิเคชันของคุณ โดยทั่วไปมักใช้เพื่อวัตถุประสงค์ในการทดสอบหรือการฝึกอบรม ซึ่งช่วยให้คุณหลีกเลี่ยงการติดตั้งเซิร์ฟเวอร์ฐานข้อมูลแยกต่างหากบนเครื่องได้ ซึ่งเหมาะมากสำหรับเราในการทำความคุ้นเคยกับ JDBC ดังนั้นแซนด์บ็อกซ์ของเราจึงพร้อมแล้วและเริ่มกันเลย

การเชื่อมต่อ
ดังนั้นเราจึงมีไดรเวอร์ JDBC เรามี JDBC API อย่างที่เราจำได้ JDBC ย่อมาจาก Java DataBase Connectivity ดังนั้นทุกอย่างจึงเริ่มต้นด้วยการเชื่อมต่อ - ความสามารถในการสร้างการเชื่อมต่อ และการเชื่อมต่อก็คือการเชื่อมต่อ มาดูข้อความของข้อกำหนด JDBC อีกครั้ง และดูที่สารบัญ ในบท " บทที่ 4 ภาพรวม " (ภาพรวม) เราจะไปที่ส่วน " 4.1 การสร้างการเชื่อมต่อ " (การสร้างการเชื่อมต่อ) ว่ากันว่ามีสองวิธีในการเชื่อมต่อกับฐานข้อมูล:- ผ่านทาง DriverManager
- ผ่านแหล่งข้อมูล
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());
}
}
เมื่อดำเนินการทดสอบนี้ จะตรวจสอบว่าการเชื่อมต่อที่ได้นั้นถูกต้อง (สร้างขึ้นอย่างถูกต้อง) และไม่ได้ปิดอยู่ ด้วยการใช้ทรัพยากรแบบลองใช้เราจะปล่อยทรัพยากรเมื่อเราไม่ต้องการมันอีกต่อไป วิธีนี้จะช่วยปกป้องเราจากการเชื่อมต่อที่หย่อนคล้อยและหน่วยความจำรั่ว เนื่องจากการดำเนินการใดๆ กับฐานข้อมูลจำเป็นต้องมีการเชื่อมต่อ เราจะจัดเตรียมวิธีทดสอบที่เหลือที่ทำเครื่องหมายว่า @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 และไม่จำเป็นต้องสร้างเองทุกครั้ง

งบ
ต่อไปเราจะสนใจเรื่องงบหรือสำนวน มีการอธิบายไว้ในเอกสารประกอบในบท " คำชี้แจงบทที่ 13 " ประการแรกบอกว่ามีงบหลายประเภท:- คำสั่ง: นิพจน์ SQL ที่ไม่มีพารามิเตอร์
- PreparedStatement : คำสั่ง SQL ที่เตรียมไว้ซึ่งมีพารามิเตอร์อินพุต
- CallableStatement: นิพจน์ SQL ที่มีความสามารถในการรับค่าส่งคืนจาก SQL Stored Procedures
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 ไม่รองรับพารามิเตอร์ที่มีชื่อสำหรับ ReadyStatement ดังนั้นพารามิเตอร์จึงถูกระบุด้วยคำถาม และโดยการระบุค่า เราจึงระบุดัชนีคำถาม (เริ่มจาก 1 ไม่ใช่ศูนย์) ในการทดสอบครั้งล่าสุดเราได้รับค่า True เป็นการบ่งชี้ว่ามีผลลัพธ์หรือไม่ แต่ผลลัพธ์ของการสืบค้นแสดงใน JDBC API เป็นอย่างไร และนำเสนอเป็น ResultSet

ชุดผลลัพธ์
แนวคิดของ ResultSet อธิบายไว้ในข้อกำหนด JDBC API ในบท "บทที่ 15 ชุดผลลัพธ์" ก่อนอื่น มันบอกว่า ResultSet จัดเตรียมวิธีการในการดึงข้อมูลและจัดการผลลัพธ์ของการสืบค้นที่ดำเนินการ นั่นคือหากวิธีการดำเนินการส่งคืนค่าจริงมาให้เรา เราก็จะได้รับ 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 ชี้ไปที่บางแถว ดังนั้นเพื่อที่จะอ่านบรรทัด คุณต้องวางเคอร์เซอร์นี้ไว้บนบรรทัดนั้น เมื่อเคอร์เซอร์ถูกย้าย วิธีการย้ายเคอร์เซอร์จะส่งกลับค่าจริงหากเคอร์เซอร์นั้นถูกต้อง (ถูกต้อง ถูกต้อง) กล่าวคือ ชี้ไปที่ข้อมูล หากคืนค่าเป็นเท็จ แสดงว่าไม่มีข้อมูล กล่าวคือ เคอร์เซอร์ไม่ได้ชี้ไปที่ข้อมูล หากเราพยายามรับข้อมูลด้วยเคอร์เซอร์ที่ไม่ถูกต้อง เราจะได้รับข้อผิดพลาด: ไม่มีข้อมูล ที่น่าสนใจคือคุณสามารถอัปเดตหรือแทรกแถวผ่าน 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 Objects " การใช้งานมีหลากหลายรูปแบบ ตัวอย่างเช่น กรณีที่ง่ายที่สุดอาจมีลักษณะดังนี้:@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 นั้นคล้ายคลึงกับ symbiosis ของคำสั่ง (เราระบุคำสั่งผ่านมัน) และดำเนินการคำสั่ง เราควบคุมเคอร์เซอร์ผ่านมัน (โดยการเรียกวิธีถัดไป) และรับข้อมูลจากมัน แนวทางนี้ไม่เพียงแต่น่าสนใจเท่านั้น แต่ยังรวมถึงการใช้งานที่เป็นไปได้ด้วย ตัวอย่างเช่น CachedRowSet "ตัดการเชื่อมต่อ" (นั่นคือ ไม่ได้ใช้การเชื่อมต่อแบบถาวรกับฐานข้อมูล) และต้องการการซิงโครไนซ์กับฐานข้อมูลอย่างชัดเจน:
CachedRowSet jdbcRsCached = new CachedRowSetImpl();
jdbcRsCached.acceptChanges(connection);
คุณสามารถอ่านเพิ่มเติมในบทช่วยสอนบนเว็บไซต์ Oracle: " การใช้ CachedRowSetObjects "

ข้อมูลเมตา
นอกเหนือจากการสืบค้นแล้ว การเชื่อมต่อกับฐานข้อมูล (เช่น อินสแตนซ์ของคลาสการเชื่อมต่อ) ยังให้การเข้าถึงข้อมูลเมตา - ข้อมูลเกี่ยวกับวิธีกำหนดค่าและจัดระเบียบฐานข้อมูลของเรา แต่ก่อนอื่น เรามาพูดถึงประเด็นสำคัญบางประการ: URL สำหรับเชื่อมต่อกับฐานข้อมูลของเรา: “jdbc:h2:mem:test” test คือชื่อของฐานข้อมูลของเรา สำหรับ JDBC API นี่คือไดเร็กทอรี และชื่อจะเป็นตัวพิมพ์ใหญ่นั่นคือ TEST สคีมาเริ่มต้นสำหรับ H2 คือสาธารณะ ตอนนี้ เรามาเขียนการทดสอบที่แสดงตารางผู้ใช้ทั้งหมดกัน ทำไมต้องกำหนดเอง? เนื่องจากฐานข้อมูลไม่เพียงมีตารางผู้ใช้เท่านั้น (ที่เราสร้างขึ้นเองโดยใช้การสร้างนิพจน์ตาราง) แต่ยังมีตารางระบบด้วย จำเป็นต้องจัดเก็บข้อมูลระบบเกี่ยวกับโครงสร้างของฐานข้อมูล แต่ละฐานข้อมูลสามารถจัดเก็บตารางระบบดังกล่าวได้แตกต่างกัน ตัวอย่างเช่น ใน H2 ข้อมูลเหล่านี้จะถูกจัดเก็บไว้ในสคีมา " INFORMATION_SCHEMA " สิ่งที่น่าสนใจคือ 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 มีส่วนที่เรียกว่า "บทที่ 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 คือธุรกรรม ในข้อกำหนด JDBC พวกเขาถูกกำหนดไว้ในบท "ธุรกรรมบทที่ 10" ก่อนอื่น ควรทำความเข้าใจว่าธุรกรรมคืออะไร ธุรกรรมคือกลุ่มของการดำเนินการตามลำดับที่รวมตรรกะกับข้อมูล ประมวลผลหรือยกเลิกโดยรวม ธุรกรรมจะเริ่มเมื่อใดเมื่อใช้ JDBC ตามที่ระบุไว้ในข้อกำหนด สิ่งนี้ได้รับการจัดการโดยตรงโดยไดรเวอร์ JDBC แต่โดยปกติแล้ว ธุรกรรมใหม่จะเริ่มต้นเมื่อคำสั่ง SQL ปัจจุบันจำเป็นต้องมีธุรกรรม และธุรกรรมยังไม่ได้ถูกสร้างขึ้น การทำธุรกรรมจะสิ้นสุดเมื่อใด? สิ่งนี้ถูกควบคุมโดยแอตทริบิวต์การคอมมิตอัตโนมัติ หากเปิดใช้งานการส่งอัตโนมัติ ธุรกรรมจะเสร็จสมบูรณ์หลังจากคำสั่ง SQL "เสร็จสมบูรณ์" คำว่า "เสร็จสิ้น" หมายถึงอะไรขึ้นอยู่กับประเภทของนิพจน์ SQL:- ภาษาการจัดการข้อมูลหรือที่เรียกว่า DML (Insert, Update, Delete)
ธุรกรรมจะเสร็จสมบูรณ์ทันทีที่การดำเนินการเสร็จสิ้น เลือกใบแจ้งยอด
- CallableStatement และนิพจน์ที่ส่งคืนผลลัพธ์หลายรายการ
เมื่อปิด ResultSets ที่เกี่ยวข้องทั้งหมดแล้วและได้รับเอาต์พุตทั้งหมด (รวมถึงจำนวนการอัปเดต)
ธุรกรรมจะเสร็จสมบูรณ์เมื่อ ResultSet ถูกปิด ( ResultSet#close )
@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 จัดการกับระดับเหล่านั้น

ระดับฉนวน
มาเปิดส่วนย่อย "10.2 ระดับการแยกธุรกรรม" ของข้อกำหนด JDBC ที่นี่ก่อนที่จะไปไกลกว่านี้ฉันอยากจะจำเรื่องกรดไว้ ACID อธิบายข้อกำหนดสำหรับระบบธุรกรรม- ความเป็นปรมาณู:
ไม่มีการทำธุรกรรมใด ๆ ที่จะถูกผูกมัดกับระบบบางส่วน การดำเนินการย่อยทั้งหมดจะดำเนินการหรือไม่จะดำเนินการใดๆ เลย - ความสอดคล้อง:
แต่ละธุรกรรมที่ประสบความสำเร็จ ตามคำจำกัดความ จะบันทึกเฉพาะผลลัพธ์ที่ถูกต้องเท่านั้น - การแยก:
ในขณะที่ธุรกรรมกำลังทำงานอยู่ ธุรกรรมที่เกิดขึ้นพร้อมกันไม่ควรส่งผลกระทบต่อผลลัพธ์ - ความทนทาน:
หากธุรกรรมเสร็จสมบูรณ์ การเปลี่ยนแปลงที่เกิดขึ้นจะไม่ถูกยกเลิกเนื่องจากความล้มเหลวใดๆ
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 และวิธีที่ไม่ทำงานในเวอร์ชันที่สูงกว่าได้ที่นี่: “ รองรับระดับการแยก READ UNCOMMITTED ในโหมด MVStore ”

บรรทัดล่าง
ดังที่เราเห็น JDBC เป็นเครื่องมือที่ทรงพลังในมือของ Java สำหรับการทำงานกับฐานข้อมูล ฉันหวังว่าบทวิจารณ์สั้นๆ นี้จะช่วยให้คุณเป็นจุดเริ่มต้นหรือช่วยฟื้นฟูความจำของคุณได้ สำหรับของว่างมีวัสดุเพิ่มเติมบางอย่าง:- รายงานไฟไหม้: " ธุรกรรม: ตำนาน ความประหลาดใจ และโอกาส " จาก Martin Kleppmann
- Yuri Tkach: " JPA ธุรกรรม "
- Yurik Tkach: " JDBC - Java สำหรับผู้ทดสอบ "
- หลักสูตรฟรีบน Udemy: " JDBC และ MySQL "
- " การจัดการวัตถุ CallableStatement "
- นักพัฒนา IBM: " การเชื่อมต่อฐานข้อมูล Java "
- IBM Knowledge Center: " เริ่มต้นใช้งาน JDBC "
GO TO FULL VERSION