JavaRush /จาวาบล็อก /Random-TH /JDBC หรือจุดเริ่มต้นทั้งหมด
Viacheslav
ระดับ

JDBC หรือจุดเริ่มต้นทั้งหมด

เผยแพร่ในกลุ่ม
ในโลกสมัยใหม่ ไม่มีทางเลยหากไม่มีการจัดเก็บข้อมูล และประวัติความเป็นมาของการทำงานกับฐานข้อมูลก็เริ่มขึ้นเมื่อนานมาแล้วด้วยการถือกำเนิดของ JDBC ฉันเสนอให้จดจำบางสิ่งที่ไม่มีเฟรมเวิร์กสมัยใหม่ที่สร้างขึ้นบน JDBC สามารถทำได้หากไม่มี นอกจากนี้ แม้จะทำงานร่วมกับพวกเขา แต่บางครั้งคุณอาจต้องการโอกาส "กลับคืนสู่รากเหง้าของคุณ" ฉันหวังว่ารีวิวนี้จะช่วยเป็นการแนะนำหรือช่วยฟื้นฟูความจำของคุณได้
JDBC หรือจุดเริ่มต้นทั้งหมด - 1

การแนะนำ

วัตถุประสงค์หลักประการหนึ่งของภาษาการเขียนโปรแกรมคือการจัดเก็บและประมวลผลข้อมูล เพื่อให้เข้าใจวิธีการทำงานของพื้นที่จัดเก็บข้อมูลได้ดีขึ้น คุณควรใช้เวลาเพียงเล็กน้อยกับทฤษฎีและสถาปัตยกรรมของแอปพลิเคชัน ตัวอย่างเช่น คุณสามารถอ่านวรรณกรรม เช่น หนังสือ " คู่มือสถาปนิกซอฟต์แวร์: มาเป็นสถาปนิกซอฟต์แวร์ที่ประสบความสำเร็จโดยการใช้ 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 หรือจุดเริ่มต้นทั้งหมด - 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 ได้ในบทวิจารณ์สั้น ๆ: " 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 ยอดเยี่ยม. เราต้องการอะไรอีก? มีแผนภาพที่ยอดเยี่ยมเช่นนี้:
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 or с чего всё начинается - 5

การเชื่อมต่อ

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

งบ

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

ชุดผลลัพธ์

แนวคิดของ 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 "
JDBC or с чего всё начинается - 8

ข้อมูลเมตา

นอกเหนือจากการสืบค้นแล้ว การเชื่อมต่อกับฐานข้อมูล (เช่น อินสแตนซ์ของคลาสการเชื่อมต่อ) ยังให้การเข้าถึงข้อมูลเมตา - ข้อมูลเกี่ยวกับวิธีกำหนดค่าและจัดระเบียบฐานข้อมูลของเรา แต่ก่อนอื่น เรามาพูดถึงประเด็นสำคัญบางประการ: 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 or с чего всё начинается - 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 or с чего всё начинается - 10

การทำธุรกรรม

สิ่งที่น่าสนใจที่สุดอย่างหนึ่งเกี่ยวกับ JDBC คือธุรกรรม ในข้อกำหนด JDBC พวกเขาถูกกำหนดไว้ในบท "ธุรกรรมบทที่ 10" ก่อนอื่น ควรทำความเข้าใจว่าธุรกรรมคืออะไร ธุรกรรมคือกลุ่มของการดำเนินการตามลำดับที่รวมตรรกะกับข้อมูล ประมวลผลหรือยกเลิกโดยรวม ธุรกรรมจะเริ่มเมื่อใดเมื่อใช้ JDBC ตามที่ระบุไว้ในข้อกำหนด สิ่งนี้ได้รับการจัดการโดยตรงโดยไดรเวอร์ JDBC แต่โดยปกติแล้ว ธุรกรรมใหม่จะเริ่มต้นเมื่อคำสั่ง SQL ปัจจุบันจำเป็นต้องมีธุรกรรม และธุรกรรมยังไม่ได้ถูกสร้างขึ้น การทำธุรกรรมจะสิ้นสุดเมื่อใด? สิ่งนี้ถูกควบคุมโดยแอตทริบิวต์การคอมมิตอัตโนมัติ หากเปิดใช้งานการส่งอัตโนมัติ ธุรกรรมจะเสร็จสมบูรณ์หลังจากคำสั่ง SQL "เสร็จสมบูรณ์" คำว่า "เสร็จสิ้น" หมายถึงอะไรขึ้นอยู่กับประเภทของนิพจน์ SQL:
  • ภาษาการจัดการข้อมูลหรือที่เรียกว่า DML (Insert, Update, Delete)
    ธุรกรรมจะเสร็จสมบูรณ์ทันทีที่การดำเนินการเสร็จสิ้น
  • เลือกใบแจ้งยอด
    ธุรกรรมจะเสร็จสมบูรณ์เมื่อ ResultSet ถูกปิด ( ResultSet#close )
  • CallableStatement และนิพจน์ที่ส่งคืนผลลัพธ์หลายรายการ
    เมื่อปิด ResultSets ที่เกี่ยวข้องทั้งหมดแล้วและได้รับเอาต์พุตทั้งหมด (รวมถึงจำนวนการอัปเดต)
นี่คือลักษณะการทำงานของ 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 or с чего всё начинается - 11

ระดับฉนวน

มาเปิดส่วนย่อย "10.2 ระดับการแยกธุรกรรม" ของข้อกำหนด JDBC ที่นี่ก่อนที่จะไปไกลกว่านี้ฉันอยากจะจำเรื่องกรดไว้ ACID อธิบายข้อกำหนดสำหรับระบบธุรกรรม
  • ความเป็นปรมาณู:
    ไม่มีการทำธุรกรรมใด ๆ ที่จะถูกผูกมัดกับระบบบางส่วน การดำเนินการย่อยทั้งหมดจะดำเนินการหรือไม่จะดำเนินการใดๆ เลย
  • ความสอดคล้อง:
    แต่ละธุรกรรมที่ประสบความสำเร็จ ตามคำจำกัดความ จะบันทึกเฉพาะผลลัพธ์ที่ถูกต้องเท่านั้น
  • การแยก:
    ในขณะที่ธุรกรรมกำลังทำงานอยู่ ธุรกรรมที่เกิดขึ้นพร้อมกันไม่ควรส่งผลกระทบต่อผลลัพธ์
  • ความทนทาน:
    หากธุรกรรมเสร็จสมบูรณ์ การเปลี่ยนแปลงที่เกิดขึ้นจะไม่ถูกยกเลิกเนื่องจากความล้มเหลวใดๆ
เมื่อพูดถึงระดับการแยกธุรกรรม เรากำลังพูดถึงข้อกำหนด "การแยกตัว" การแยกเป็นข้อกำหนดที่มีราคาแพง ดังนั้นในฐานข้อมูลจริงจึงมีโหมดที่ไม่สามารถแยกธุรกรรมได้อย่างสมบูรณ์ (ระดับการแยกการอ่านซ้ำได้และต่ำกว่า) Wikipedia มีคำอธิบายที่ดีเยี่ยมเกี่ยวกับปัญหาที่อาจเกิดขึ้นเมื่อทำงานกับธุรกรรม ควรอ่านเพิ่มเติมที่นี่: “ ปัญหาการเข้าถึงแบบขนานโดยใช้ธุรกรรม ” ก่อนที่เราจะเขียนการทดสอบ เรามาเปลี่ยนสคริปต์ 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' และสูงกว่านั้น READ UNCOMMITTED จะหยุดทำงาน แม้ว่าเราจะไม่ได้เปลี่ยนโค้ดก็ตาม นี่เป็นการพิสูจน์อีกครั้งว่าการเลือกผู้จำหน่ายและเวอร์ชันไดรเวอร์ไม่ใช่แค่ตัวอักษรเท่านั้น แต่จะเป็นตัวกำหนดว่าคำขอของคุณจะถูกดำเนินการอย่างไร คุณสามารถอ่านเกี่ยวกับวิธีแก้ไขพฤติกรรมนี้ในเวอร์ชัน 1.4.177 และวิธีที่ไม่ทำงานในเวอร์ชันที่สูงกว่าได้ที่นี่: “ รองรับระดับการแยก READ UNCOMMITTED ในโหมด MVStore
JDBC or с чего всё начинается - 12

บรรทัดล่าง

ดังที่เราเห็น JDBC เป็นเครื่องมือที่ทรงพลังในมือของ Java สำหรับการทำงานกับฐานข้อมูล ฉันหวังว่าบทวิจารณ์สั้นๆ นี้จะช่วยให้คุณเป็นจุดเริ่มต้นหรือช่วยฟื้นฟูความจำของคุณได้ สำหรับของว่างมีวัสดุเพิ่มเติมบางอย่าง: #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION