JDBC——概述与JDBC的使用
引言
一直希望深入學(xué)習(xí)一下數(shù)據(jù)庫持久化技術(shù),接觸過Hibernate、Mybatis,也使用過Spring事務(wù)管理來控制回滾操作,但是越發(fā)覺得底層知識(shí)有一定的知識(shí)盲區(qū)和空洞。
很多ORM框架都是基于JDBC規(guī)范來進(jìn)行構(gòu)建的,因此,學(xué)習(xí)JDBC的基礎(chǔ)知識(shí)勢(shì)在必行。雖然不建議在實(shí)際開發(fā)中使用 JDBC API,但了解其技術(shù)背景和使用過程無疑會(huì)更好的理解構(gòu)建于其上的高級(jí)框架。
本篇博客總結(jié)自尚硅谷宋紅康老師的視頻教程,旨在記錄和總結(jié)JDBC API的使用步驟和常見問題,方便未來面試和深入理解其他框架。
一、JDBC概述
1.1 JDBC 介紹
JDBC,Java Database Connectivity,Java數(shù)據(jù)庫連接技術(shù)。這是JDK原生的獨(dú)立于特定數(shù)據(jù)庫管理系統(tǒng)、通用的SQL數(shù)據(jù)庫存取和操作的公共接口(一組API)。
這套API包含在 java.sql. 和 javax.sql 包下。使用這套API 可以以一種標(biāo)準(zhǔn)的方法訪問數(shù)據(jù)庫資源。
JDBC產(chǎn)生的原因是由于各個(gè)數(shù)據(jù)庫廠商,如MySQL、Oracle、SQL Server、DB2(IBM)等,都有屬于自己數(shù)據(jù)庫特有的連接和訪問方式,從連接驅(qū)動(dòng)到增刪改查,都存在各種差異。
JDBC的目標(biāo)是使Java程序員無需對(duì)特定的數(shù)據(jù)庫系統(tǒng)的特點(diǎn)有過多了解,也能夠快速完成數(shù)據(jù)庫訪問等開發(fā)工作。
1.2 JDBC體系結(jié)構(gòu)
JDBC接口包括兩個(gè)層次:
- 面向應(yīng)用的API:Java API,抽象接口,供應(yīng)用開發(fā)者使用,包括連接數(shù)據(jù)庫、執(zhí)行SQL語句、獲得結(jié)果等。
- 面向數(shù)據(jù)庫的API:Java Driver API,供數(shù)據(jù)庫服務(wù)商實(shí)現(xiàn)特定于自家數(shù)據(jù)庫的驅(qū)動(dòng)程序。MySQL的驅(qū)動(dòng)就是我們經(jīng)常需要引入的mysql-connector-java.jar 。
1.3 JDBC的編程步驟
jdbc編程步驟有以下幾步固定操作:
1、添加數(shù)據(jù)庫驅(qū)動(dòng)依賴,例如:MySQL的 mysql-connector-java.jar,這是一種純Java實(shí)現(xiàn)的驅(qū)動(dòng)程序,除此之外,微軟的SQL Server,需要JDBC-ODBC橋方式。
2、加載并注冊(cè)驅(qū)動(dòng)程序
3、創(chuàng)建 Connection 對(duì)象
4、創(chuàng)建 Statement 對(duì)象
5、執(zhí)行 SQL 語句
6、若為查詢操作,需要額外處理 ResultSet 結(jié)果集
7、關(guān)閉 Statement 對(duì)象
8、關(guān)閉 Connection 對(duì)象
補(bǔ)充:ODBC(Open Database Connectivity 開放式數(shù)據(jù)庫連接),是微軟在 Windows 平臺(tái)下推出的。
二、JDBC編程實(shí)踐
在使用 JDBC 進(jìn)行編程前,有一些不可或缺的要素:
1、Driver 接口實(shí)現(xiàn):是JDBC API中對(duì)數(shù)據(jù)庫驅(qū)動(dòng)程序的接口定義,不同的數(shù)據(jù)庫廠商會(huì)自定義實(shí)現(xiàn),但都需要符合Driver接口標(biāo)準(zhǔn)。一般使用MySQL,就需要引入mysql-connector-java驅(qū)動(dòng)包。
包中提供的?com.mysql.jdbc.Driver(高版本已經(jīng)廢棄,改為com.mysql.cj.jdbc.Driver) 就是java.sql.Driver接口的實(shí)現(xiàn)類。
2、數(shù)據(jù)庫連接信息:URL、用戶名、密碼等。
JDBC URL的標(biāo)準(zhǔn)由三部分組成,各部分用冒號(hào)分隔,例如:
jdbc:mysql://localhost:3306/test?serverTimezone=UTCjdbc是主協(xié)議名,mysql是子協(xié)議,JDBC URL的主協(xié)議始終是 jdbc。
以下代碼為 jdbc 實(shí)現(xiàn)入庫操作的最終版本,后面的章節(jié)將會(huì)進(jìn)一步探討可以優(yōu)化或需要注意的點(diǎn),但是以下代碼作為學(xué)習(xí)范例,已經(jīng)足夠展現(xiàn)JDBC 編程的絕大部分內(nèi)容。
import java.sql.*; import java.text.DateFormat; import java.text.SimpleDateFormat;public class PreparedStatementUpdTest {public static void main(String[] args) {jdbcTest();}public static void jdbcTest() {// 創(chuàng)建驅(qū)動(dòng)Connection connection = null;PreparedStatement ps = null;try {Class.forName("com.mysql.cj.jdbc.Driver");// 聲明連接信息String url = "jdbc:mysql://localhost:3306/learn_mysql?serverTimezone=UTC";String username = "root";String password = "123456";// 創(chuàng)建連接connection = DriverManager.getConnection(url, username, password);String sql = "INSERT INTO user(name, birth_day) VALUES(?, ?)";// SQL預(yù)編譯ps = connection.prepareStatement(sql);ps.setObject(1, "Lisa");DateFormat format = new SimpleDateFormat("yyyy-MM-dd");java.util.Date birthday = format.parse("2021-02-12");// util.Date和sql.Date的共同點(diǎn)是毫秒數(shù)一樣ps.setObject(2, new Date(birthday.getTime()));// 執(zhí)行SQL語句,executeUpdate()方法可以返回 int 受影響行數(shù)ps.execute();} catch (Exception e) {e.printStackTrace();} finally {// 關(guān)閉資源try {if (ps != null)ps.close();} catch (SQLException e) {e.printStackTrace();}try {if (connection != null)connection.close();} catch (SQLException e) {e.printStackTrace();}}} }三、JDBC編程實(shí)踐的優(yōu)化與思考
3.1 創(chuàng)建驅(qū)動(dòng)實(shí)例
在程序編寫之初,我們已經(jīng)通過依賴管理,將mysql-connector-java.jar 包引入類路徑下,那么 JDBC 的 Driver 接口就有了針對(duì)于MySQL的驅(qū)動(dòng)器實(shí)現(xiàn)——com.mysql.cj.jdbc.Driver。
實(shí)際上,我們可以通過最原始的方式 new 一個(gè)出來
// 直接 new 創(chuàng)建驅(qū)動(dòng)實(shí)例,不要這么做! Driver driver = new com.mysql.cj.jdbc.Driver(); // 聲明連接信息(略) // 從 Driver 中獲取 Connection Connection connection = driver.connect(連接信息);但是通過 Class.forName(..) 獲得驅(qū)動(dòng)對(duì)象的好處就是可以將類名作為配置放到程序外,方便替換其他的數(shù)據(jù)庫驅(qū)動(dòng),有更好的移植性:
// 通過反射獲取驅(qū)動(dòng),有更強(qiáng)的可移植性 Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); // 聲明連接信息(略) // 從 Driver 中獲取 Connection Connection connection = driver.connect(連接信息);3.2 DriverManager與Connection獲取方式的
Driver 接口有獲取Connection的方法,這是最初獲取Connection的方式。
Connection connection = driver.connect(連接信息);JDBC API提供了一個(gè)名為 DriverManager 的基礎(chǔ)服務(wù)類,可以管理 Driver 驅(qū)動(dòng)程序,并創(chuàng)建 Connection 對(duì)象,于是有了通過 DriverManager 獲取 Connection的方式,但前提是需要將驅(qū)動(dòng)對(duì)象注冊(cè)到 DriverManager 中。
// 創(chuàng)建驅(qū)動(dòng) Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); // 注冊(cè)驅(qū)動(dòng) DriverManager.registerDriver(driver); // 連接信息(略) // 從驅(qū)動(dòng)管理器中獲取連接 Connection connection = DriverManager.getConnection(url, username, password);其實(shí),“注冊(cè)驅(qū)動(dòng)”的操作并不需要應(yīng)用開發(fā)者來完成,在 Driver 類加載時(shí),就會(huì)自動(dòng)注冊(cè),原因就是以下靜態(tài)代碼塊:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}} }因此,在 Class.forName(..) 完成后,mysql Driver 類已經(jīng)成功加載,即已經(jīng)完成了驅(qū)動(dòng)注冊(cè)步驟,因此我們可以更加簡(jiǎn)化我們獲取Connection的代碼:
Class.forName("com.mysql.cj.jdbc.Driver"); // 聲明數(shù)據(jù)庫連接信息(略) Connection connection = DriverManager.getConnection(url, user, password);另外,值得一提的是,Class.forName("com.mysql.cj.jdbc.Driver") 加載驅(qū)動(dòng)這一步也可以省略,
這是因?yàn)楫?dāng) mysql-connector-java.jar 引入類路徑后,會(huì)默認(rèn)自動(dòng)加載 META-INF/services/java.sql.Driver中配置的驅(qū)動(dòng)實(shí)現(xiàn)類,但是建議不要省略,因?yàn)槿绻w移了其他數(shù)據(jù)庫驅(qū)動(dòng),可能不會(huì)有這樣的默認(rèn)操作。
3.3 配置化數(shù)據(jù)庫連接信息
在前面的例子中,其實(shí)已經(jīng)可以輕松的獲取到數(shù)據(jù)庫連接:
Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/learn_mysql?serverTimezone=UTC"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password);我們可以進(jìn)一步將驅(qū)動(dòng)類信息、連接信息轉(zhuǎn)移到程序之外,以配置的形式存在:
然后通過 Properties 獲取配置數(shù)據(jù):
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties props = new Properties(); props.load(is); String url = props.getProperty("url"); String username = props.getProperty("username"); String password = props.getProperty("password"); String driverName = props.getProperty("driverName"); // 加載驅(qū)動(dòng)類 Class.forName(driverName); // 獲取連接 Connection connection = DriverManager.getConnection(url, username, password);通過系統(tǒng)類加載器獲取配置文件數(shù)據(jù),默認(rèn)讀取 src 目錄下的文件,以 properties 結(jié)尾的文件可以直接被 load 進(jìn) Properties 對(duì)象中。
3.4 Statement與PreparedStatement
數(shù)據(jù)庫連接被用于向數(shù)據(jù)庫服務(wù)器發(fā)送命令和SQL語句,并接收數(shù)據(jù)庫返回的結(jié)果。其實(shí),一個(gè)數(shù)據(jù)庫連接就是一個(gè)Socket連接。
在java.sql包中有 3 個(gè)接口分別定義了對(duì)數(shù)據(jù)庫的調(diào)用方式:
- Statement:用于執(zhí)行靜態(tài)SQL語句并返回它所生成結(jié)果的對(duì)象。
- PreparedStatement:SQL語句被預(yù)編譯并存儲(chǔ)在此對(duì)象中,可以使用此對(duì)象多次高效地執(zhí)行SQL語句。
- CallableStatement:用于執(zhí)行SQL存儲(chǔ)過程。
?原始的Statement存在一定的弊端,由于只能處理靜態(tài)SQL語句,因此只能將變量直接拼接到SQL中才可以使用,除此之外,更嚴(yán)重的問題還是SQL注入。
例如下面這條拼接的SQL語句:
String sql = "SELECT user, password FROM user_table WHERE user='" + username + "' AND password='" + password + "'";如果username和password分別是:
String username = "1' OR "; String password = " = 1 OR '1' = '1";那么sql 的WHERE條件就是一個(gè)恒成立的情況,這就是SQL注入:
SELECT user, password FROM user_table WHERE user='1' OR ' AND password=' = 1 OR '1' = '1'PreparedStatement表示一個(gè)可以預(yù)編譯SQL語句的對(duì)象,使用“?”占位符來明確區(qū)分 SQL語法與參數(shù),可以有效防止SQL注入問題。
因此,Statement 已不再使用了,取而代之的是可以進(jìn)行預(yù)編譯SQL的PreparedStatement。?
總結(jié)
JDBC是數(shù)據(jù)庫訪問的公共規(guī)范,它的編程步驟主要分為幾點(diǎn):
1、引入對(duì)應(yīng)數(shù)據(jù)庫廠商的驅(qū)動(dòng)包
2、加載、注冊(cè)驅(qū)動(dòng)(類加載的同時(shí)即完成注冊(cè))
3、從驅(qū)動(dòng)管理器 DriverManager 中直接通過連接信息創(chuàng)建一個(gè) Connection 對(duì)象
4、使用 Connection 對(duì)象以預(yù)編譯方式創(chuàng)建 PreparedStatement 對(duì)象
5、填充?PreparedStatement 屬性值
6、使用PreparedStatement 執(zhí)行SQL 操作(查詢操作需要處理結(jié)果集)
7、以資源創(chuàng)建的先后順序,逆向關(guān)閉資源
總結(jié)
以上是生活随笔為你收集整理的JDBC——概述与JDBC的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python阴阳师_如何用Python找
- 下一篇: 微服务架构 —— 服务雪崩与容错方案