JDBC JDBC(Java DataBase Connectivty,Java資料庫連接)API,是一種用於執行Sql語句的Java API,可以為關係型資料庫提供統一的訪問,其由一組Java編寫的類和介面組成. JDBC驅動程式 起初,SUN公司推出JDBC API希望能適用於所有資料庫,但實際 ...
JDBC
JDBC(Java DataBase Connectivty,Java資料庫連接)API,是一種用於執行Sql語句的Java API,可以為關係型資料庫提供統一的訪問,其由一組Java編寫的類和介面組成.
JDBC驅動程式
起初,SUN公司推出JDBC API希望能適用於所有資料庫,但實際中是不可能實現的,各個廠商提供的資料庫差異太大,SUN公司於資料庫廠商協同之後決定:由SUN公司提供一套訪問資料庫的API,各個廠商根據規範提供一套訪問自家資料庫API的介面,SUN公司提供的規範API稱之為JDBC,廠商提供的自家資料庫API介面稱之為 驅動
JDBC原理
- 經過SUN公司於各個資料庫廠商的協同,JDBC的結構如下圖:
JDBC驅動的原理
我們知道了JDBC運行的原理,那麼JDBC驅動是怎麼運行的呢,我們開始進行探究
-
JDBC驅動的類型
根據訪問資料庫資料庫技術不同,JDBC驅動程式被訪問四類
- Type1:
JDBC-ODBC
橋驅動程式- 此類驅動程式由JDBC-ODBC橋金額一個ODBC驅動程式組成
- Type2:部分Java本地JDBC API驅動程式
- 此類驅動程式必須在本地電腦上先安裝好特點的驅動程式才能進行使用
- Type3:純Java的資料庫中間件驅動程式(目前主流)
Pure Java Driver for Database Middleware
使用此類驅動時,不需要再本地電腦上安裝任何附加軟體,但必須再安裝資料庫管理系統的伺服器端加裝中間件(Middleware)
,這個中間件負責所有存取資料庫時的必要轉換.中間件的工作原理是:驅動程式將JDBC訪問轉換為於資料庫無關的標準網路協議(通常是HTTP或HTTPS)送出,然後再由中間件伺服器將其轉換為資料庫專用的訪問指令,完成對資料庫的操作.中間件可以支持對多種資料庫的訪問.
- Type4:純Java的JDBC驅動程式(最理想的驅動程式)
Direct-toDatabasePureJavaDriver
此類驅動程式是之間面向資料庫的Java驅動程式,即所謂的”瘦”驅動程式.使用該驅動程式無需安裝如何附加軟體(包括本地電腦或是資料庫伺服器端),所有存取的資料庫操作都直接由JDBC驅動程式來完成
- Type1:
-
JDBC驅動工作動作
- 對於第三類Type3驅動程式來說,其是由純Java語言開發的,此類驅動程式體積最小
- 下麵給出Type3驅動程式的結構圖
- 對於第三類Type3驅動程式來說,其是由純Java語言開發的,此類驅動程式體積最小
- 可見,Type 3,JDBC驅動程式為兩層結構,分別為驅動程式客戶端和驅動程式服務端,客戶端直接於用戶交互,**其為用戶提供符合JDBC規範的資料庫統一編程介面,**客戶端將數據請求通過**特定的網路請求**發送值伺服器.伺服器作為中間件的角色,其負責接收和處理用戶的請求,JDBC驅動程式本身不進行直接與資料庫的交互,而是藉助其他已經實現的驅動,稱之為”雇佣”.當然”雇佣”的數量越多,可支持的資料庫數量就越多.”雇佣”的數量和成員可以動態的改變,以滿足業務的擴展,這也是Type3JDBC性能強大的原因
將JDBC驅動導入idea(以Mysql為例)
- 第一步肯定是下載JDBC驅動程式了,各個廠商把特定的驅動程式打包成.jar發佈在其官網上大伙可用到官網下載
-
常用的各大廠商驅動下載地址
-
Mysql驅動下載
- 通過選擇 Connector/J→選項選擇Platform Independent→在根據自身電腦配置下載
-
下載好後,解壓至文件夾備用
- 打開idea,在要導入驅動的項目中新建一個名為lib的文件夾(建議命名)
- 將下載的zip文件解壓,找到其中的mysql-connector-java-8.0.26.jar包,將其複製
- 複製其中的mysql-connector-java-8.0.18.jar文件,在lib文件夾上右鍵,粘貼到IDEA中,剛剛新建的lib文件夾里
使導入的驅動生效
- 在idea點擊File→Project Structure
- 在Modules中選擇Denpendencies
- 點擊左側+號,選擇 JARS or directories
- 在彈出的視窗中選擇剛剛導入 lib 文件夾的驅動,點擊Ok
- 可以看到Module模塊中,多出了一個mysql驅動,選擇之後點擊Apply,然後Ok
JDBC中的的類及其應用
JDBC API中包含四個常用的介面和一個類,分別是
Connection
介面,Statement
介面,PreparedStatement
介面,ResultSet
介面,DriverManager
類,jar包中已經包含這些介面的實現類直接使用即可
Statement介面
Statement介面是Java程式執行資料庫操作的重要介面,用於已經建立了資料庫連接的基礎上,向資料庫發送要執行的Sql語句
- 作用:執行不帶參數的簡單Sql語句
- 主要方法
void addBatch(String sql )throws SQLException
:該方法用於將Sql語句添加到Statement對象的當前命令列表中,用於Sql語句的批量處理void clearBatch() throws SQLException
:立即釋放Statement對象中的命令列表boolean excute(String sql) throws SQLException
:執行指定的Sql語句,成功返回true
否則返回false
int[] excuteBatch() throws SQLException
:將命令列表中的sql命令提交執行,返回一個int數組表示每個sql語句影響的行數ResultSet excuteQuery(String sql) throws SQLException
:該方法用於執行查詢類型(Select類型)的Sql語句,返回的查詢所獲取的結果集ResultSet
對象void close() throws SQLException
:用於立即釋放此Statement對象的資料庫和JDBC資源
Connection介面
Connection介面位於java.sql包中,是用於與資料庫連接的對象,只有獲取了與資料庫連接的對象後,才能訪問資料庫進行操作
-
作用:與資料庫進行連接
-
主要方法
-
Statement createStatement() throws SQLException
:用於創建一個Statement對象,用於執行Sql語句 -
PreparedStatement prepareStatement(String sql) throws SQLException
: 創建一個PreparedStatement對象,用於執行預編譯的Sql語句 -
CallableStatement prepareCall(String sql)throws SQLException
:創建一個CallableStatement對象用於執行存儲過程或函數 -
void commit() throws SQLException
:提交當前事務 -
void rollback() throws SQLException
:回滾事務 -
void close() throws SQLException
:關閉連接在進行資料庫連接的時候還要用到DriverManager類中的
getConnection(url,username,password)
方法
E.g:
String url = "jdbc:mysql://localhost:3306/demo"; String username = "root"; String password = "root"; //建立連接 Connection connection = DriverManager.getConnection(url, username, password); String sql = "SELECT * FROM dept"; //創建Statement對象執行查詢操作 Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); //處理查詢結果 while (resultSet.next()) { String dId = resultSet.getString("d_id"); String dName = resultSet.getString("d_name"); String loc = resultSet.getString("loc"); System.out.println(dId + " " + dName + " " + loc); } connection.close(); statement.close(); resultSet.close(); }
-
DriverManager類
DriverManager類是JDBC API的核心,該類中包含了與資料庫交互操作的方法,類中的方法都由資料庫廠商提供
- 作用:管理和協調不同的JDBC驅動程式
- 主要方法
public static Connection getConnection(String url, String user, String password)throws SQLException
:根據指定的資料庫url,用戶名以及密碼建立資料庫連接public static Connection getConnection(String url,Properties info)
:根據指定的資料庫url以及連接屬性建立資料庫連接public static synchronized void deregisterDriver(Driver driver) throws SQLException
:從DriverManager管理列表中刪除一個驅動,driver參數是要刪除的驅動對象
PreparedStatement介面
PreparedStatement介面位於java.servlet包中,其繼承了Statement介面
- 與Statement的區別:
- 執行速度較快:PreparedStatement對象是已經預編譯過的,執行速度快於Statement.因此若要執行大量的Sql語句時使用PreparedStatement以提高效率
- 主要方法
-
setXXX()
此類方法都是設置sql語句中傳入的參數里類型
void setBinaryStream(int parameterIndex,InputStream x) throws SQLException
:將二進位流作為sql語句傳入的參數,二進位流可用高效地處理圖片,音頻,視頻登媒介,parameterIndex
是參數位置索引void setBoolean(int parameterIndex,boolean x) throws SQLException
:將boolen作為sql傳入的參數類型,parameterIndex為參數位置索引void setByte(int parameterIndex,byte x) throws SQLException
:將byte作為sql傳入的參數類型void setDate(int parameterIndex,Date x) throws SQLException
: 將java.sql.Date值x做為SQL語句中的參數值void setDouble(int parameterIndex,double x)
:將double值x做為SQL語句中的參數值void setInt(int parameterIndex,int x) throws SQLException
:將int值x做為SQL語句中的參數值void setObject(int parameterIndex,Object x) throws SQLException
:將object對象x做為SQL語句中的參數值void setString(int parameterIndex,String x) throws SQLException
: 將String值x做為SQL語句的參數值void setTimestamp(int parameterIndex,Timestamp x) throws SQLException
: 將java.sql.Timestamp值x做為SQL語句中的參數值
-
int executeUpdate() throws SQLException
:executeUpdate()
方法返回的int
值表示受影響的行數。如果返回值為 0,則可能表示沒有符合條件的記錄被修改;執行INSERT
、UPDATE
或DELETE
這些DML語句時同理
-
ResultSet介面
是用於接收查詢的結果,是結果集合,當你執行一個SELECT語句時DBMS會返回一個包含查詢結果的數據表,ResultSet接收用於表現這個數據表的對象
- 作用:表示查詢後的返回值
- 主要方法
Boolean next() throws SQLException
:移動游標到結果集的下一行,並返回一個boolen值,結尾返回falsegetXXX(String columnLabel)
:獲取指定列名的值,XXX表示Java數據類返回XXX類型,如getString(),columnLabel
表示列名getXXX(int columnIndex)
:獲取指定列索引的值,列索引從 1 開始
SQL註入
所謂SQL註入,是值通過把惡意SQL語句插入到Web表單提交或頁面請求的查詢字元串,最終達到欺騙伺服器的結果
SQL註入實例
- 對於一個簡單的登入功能,關鍵函數如下:
static boolean noProtectLogin(String username, String password, Statement statement) throws SQLException {
//username="abc";
//password = "or '1'='1'";
String sql = "SELECT *FROM user WHERE username= '" + username + "'AND password=+''";
ResultSet resultSet = statement.executeQuery(sql);
return resultSet.next();
}
- 方法中的username於password沒有進行任何處理,直接接受前端傳入的數據,這樣拼接的SQL語句會發送註入漏洞
- 若把password參數修改成
"or '1'='1'"
,username為任意值,那麼這條語句結果為SELECT *FROM user WHERE username= 'abc' AND password= 'or '1'='1''
顯然這條語句的一直是true,這樣可以把user表中的所有用戶信息查詢到,就可以成功實現無密碼登入
SQL預編譯
也稱之為SQL預處理是一種,可以提高sql語句安全性和性能的技術
-
預編譯SQL語句允許在運行之前定義SQL語句結構,同時使用占位符(通常是問號
?
)來動態表示數據部分 -
在Java中可以使用
PreparedStatement
來創建和執行預編譯的SQL語句,剛剛的登入操作使用PreparedStatement
操作的代碼如下static boolean noProtectLogin(String username, String password, Connection connection) throws SQLException { // username="abc"; // password = "or '1'='1'"; String sql = "SELECT * FROM user WHERE username= ? AND password = ? "; PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1, username); statement.setString(2, password); ResultSet resultSet = statement.executeQuery(); return resultSet.next(); }
- 這樣我們在進行惡意的SQL註入,如把password定義成
"or '1'='1'"
執行結果直接返回false
- 這樣我們在進行惡意的SQL註入,如把password定義成
事務
什麼是事務? 官方的說法:事務是訪問資料庫的一個操作序列,資料庫應用系統通過執行業務集合要完成對資料庫的存取,簡單來說:事務是執行工作操作中最小的不可再分的工作單位,通常一個業務對應一個事務,多個操作同時進行要麼同時成功,要麼同時失敗,這就是事務
事務的理解
事務的特性
- 原子性: 即不可分割,事務要麼全部被執行,要麼全部不執行.若所有的事務都提交成功,那麼資料庫操作被提交,資料庫狀態發生變化,若有一個子事務失敗,那麼其餘事務的資料庫操作都會回滾,即資料庫狀態回到事務執行之前,保持狀態不變
- 一致性:事務的執行使得資料庫從一種正確狀態轉換為另一個正確狀態
- 隔離性:在事務正確提交之前,不允許把事務對該數據的改變提交給其他事務,即在正確提交之前,其可能的結果不應該用於給其他事務
- 持久性:即事務正確提交之後,其結果會永遠保存在資料庫之中,即事務提交之後有了其他故障,事務的處理結果也會得到保存
事務的通俗例子
- 假設張三要給李四轉賬,要完成這個操作,要執行兩個事務,一個是:扣除張三的賬戶餘額;另一個是:李四的賬餘額增加,這兩個事務是不可分割的
事務的作用
- 主要作用:保證了用戶的每一次操作都是可靠的,即便出現了異常的訪問情況,也不會破壞後臺的數據完整性,拿ATM機舉例子,若ATM在操作過程中突然出現故障,此時事務必須確保故障前對賬號的操作不生效,確保用戶於銀行的利益不受損
JDBC中對事物進行管理
在JDBC中,Connection介面中定義了幾個對事務操作的方法,我們一一講解
void setAutoCommit*(*boolean autoCommit*)* throws SQLException
:在預設情況下JDBC連接處於自動提交模式,這意味著每個SQL語句執行後都會立即提交,這明顯不符合事務的特性,要我們進行顯性關閉,即將autoCommit
設置為falsevoid commit()
:若所有操作都執行成功調用該方法提交事務void rollback()
:若事務中任何操作失敗或出現異常錯誤則需調用rollback()
回滾事務
銀行存/取款舉例
拿剛剛的張三和李四的例子說明
try {
String sql1 = "UPDATE bank SET balance = balance - ? WHERE b_id=?";
String sql2 = "UPDATE bank SET balance = balance + ? WHERE b_id=?";
stmt1 = conn.prepareStatement(sql1);
stmt2 = conn.prepareStatement(sql2);
//事務1執行
stmt1.setDouble(1, 500);
stmt1.setString(2, "01");
int r1 = stmt1.executeUpdate();
//事務2執行
stmt2.setDouble(1, 500);
stmt2.setString(2, "02");
int r2 = stmt2.executeUpdate();
if (r1 == 1 && r2 == 1) {
System.out.println("業務執行成功");
conn.rollback();
} else {
System.out.println("業務執行失敗");
conn.commit();
}
} catch (SQLException e) {
e.printStackTrace();
//有異常回滾事務
conn.rollback();
} finally {
Objects.requireNonNull(stmt1).close();
Objects.requireNonNull(stmt2).close();
conn.close();
}
連接池
連接池是創建和管理資料庫連接的技術,這些連接隨時準備被任何需要它的線程使用
連接池的原理
- 連接池的基本思想是在系統初始化時,將資料庫連接作為對象存儲在運行記憶體中,當用戶需要訪問資料庫時,並非建立一個新的連接,而是從連接池中取出一個已建立的空閑連接對象.使用完畢後,用戶也並非將連接之間關閉,而是將連接放回連接池中,提供給下一次請求訪問使用,而連接池的建立,斷開都由連接池自身來管理.同時,還可以通過設置連接池的參數來控制連接池中的初始連接數,連接的上下限數以及每個連接的最大使用次數,最大空閑時間等等
- 連接池參數作用
- 最小連接數:是連接池一直保持於資料庫連接的數量,因此若應用程式對資料庫連接的使用量不大還設置較大的最小連接數,會造成大量的連接資源的浪費
- 最大連接數:是連接池能申請的最大連接數,若資料庫連接請求超過最大連接數,後續的資料庫連接請求將被加入到等待隊列中
- 若min連接於max連接相差很大時,那麼最先連接請求將會獲利,之後超過min連接的連接請求等價於新建一個資料庫連接,但這些大於min連接的資料庫連接在使用之後不會馬上被釋放,將被放入連接池中等待重覆利用
C3P0
C3P0是一個開放源代碼的JDBC連接池,包括了jdbc3和jdbc2擴展規範說明的Connection和Statement池的DataSources對象
C3P0的配置
- C3P0所需的兩個必要jar包如下圖:可以去官網直接下載zip文件:c3p0:JDBC DataSources/Resource Pools download | SourceForge.net
導入方法於導入JDBC-Mysql jar包類似這裡不再贅述
- 接著要配置c3p0-config.xml文件(這裡選擇xml配置)→直接配置到src文件夾下,否則會報配置錯誤
-
c3p0-config.xml文件的一般模板(可直接拷貝使用):
<c3p0-config> <default-config> <!-- 資料庫驅動名 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!-- 資料庫的url --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/demo</property> <!--用戶名。Default: null --> <property name="user">root</property> <!--密碼。Default: null --> <property name="password">root</property> <!--初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。Default: 3 --> <property name="initialPoolSize">3</property> <!--連接池中保留的最大連接數。Default: 15 --> <property name="maxPoolSize">5</property> <!--當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default: 3 --> <property name="acquireIncrement">3</property> <!--最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。Default: 0 --> <property name="maxIdleTime">60</property> <!--當連接池用完時客戶端調用getConnection()後等待獲取新連接的時間,超時後將拋出 SQLException,如設為0則無限期等待。單位毫秒。Default: 0 --> <property name="checkoutTimeout">0</property> </default-config> </c3p0-config>
C3P0的使用方法
C3P0
中只有一個類,顯然這個類是最主要的,其包含了C3P0所有對資料庫連接的操作,接下來我們開始講解
ComboPooledDataSource類
-
主要方法
Connection getConnection()
:從連接池中獲取一個資料庫連接,若當前沒有空閑連接,則新建連接,直到達到最大連接數.其返回一個Connection
對象
除了使用xml文件配置還可以在代碼中直接使用
ComboPooledDataSource
類中的方法配置,但一般使用xml文件,避免冗餘void setXXX()
:設置C3P0中的各類屬性,XXX表示屬性,例如setUser(),setMinPoolSize(int min)
等等
-
使用連接池測試更新語句
public static void main(String[] args) throws SQLException { //建立連接池,獲取連接 ComboPooledDataSource dataSource = new ComboPooledDataSource(); Connection conn = dataSource.getConnection(); //用連接池測試更新語句 String sql = "SELECT * FROM user WHERE username= ? AND password = ? "; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, 11111); stmt.setString(2, "11111111"); ResultSet resultSet = stmt.executeQuery(); if (resultSet.next()) { int username = resultSet.getInt("username"); String password = resultSet.getString("password"); System.out.println("username:" + username + "password:" + password); } stmt.close(); conn.close(); }
Druid(最好用的Java連接池)
Druid是目前最好資料庫連接池,在功能.性能.擴展性方面都弔打其他連接池,包括
DBCP,C3P0,BoneCP,Proxool
等等
Druid配置
-
Druid執行要一個jar包大家可以去官網:https://repo1.maven.org/maven2/com/alibaba/druid/下載所需的jar包,在導入即可
-
Druid的參數列表
屬性(Parameter) 預設值(Default) 描述(Description) username **** 連接資料庫的用戶名 password **** 連接資料庫的密碼 jdbcUrl **** 同C3P0中的jdbcUrl屬性 driverClassName 根據url自動識別 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然後選擇相應的driverClassName initialSize 0 *初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時 參見DBCP中的initialSize屬性 maxActive 8 最大連接池數量(Maximum number of Connections a pool will maintain at any given time. maxIdle 8 已經不再使用,配置了也沒效果 minIdle **** 最小連接池數量 maxWait **** 獲取連接時最大等待時間,單位毫秒。配置了maxWait之後,預設啟用公平鎖,併發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 poolPreparedState- ments false 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的資料庫性能提升巨大,比如說oracle。 maxOpenPrepared- Statements -1 要啟用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改為true。 在Druid中,不會存在Oracle下PSCache占用記憶體過多的問題,可以把這個數值配置大一些,比如說100 testOnBorrow true 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 testOnReturn false 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能 testWhileIdle false 建議配置為true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閑時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 validationQuery **** 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、 testWhileIdle都不會其作用。在mysql中通常為select 'x',在oracle中通常為select 1 from dual timeBetweenEviction-RunsMillis **** 1) Destroy線程會檢測連接的間隔時間 2) testWhileIdle的判斷依據 minEvictableIdle- TimeMillis **** Destory線程中如果檢測到當前連接的最後活躍時間和當前時間的差值大於minEvictableIdleTimeMillis,則關閉當前連接。 removeAbandoned **** 對於建立時間超過removeAbandonedTimeout的連接強制關閉 removeAbandoned-Timeout **** 指定連接建立多長時間就需要被強制關閉 logAbandoned false 指定發生removeabandoned的時候,是否記錄當前線程的堆棧信息到日誌中 filters **** 屬性類型是字元串,通過別名的方式配置擴展插件,常用的插件有: 1)監控統計用的filter:stat 2)日誌用的filter:log4j 3)防禦sql註入的filter:wall - 紅色屬性為必要配置屬性
- 定義application.properties(名字可以順便取但必須是properties),可以放置任意目錄下
- application.properties文件一般模板如下
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/test?userSSL=false&serverTimezone=Asia/Shanghai username=root password=123456 initialSize=3 maxActive=5 maxWait=1000
Druid使用方法
Druid也是只要一個核心類
DruidDataSource
它實現了javax.sql.DataSource
介面
-
主要類與方法
DruidDataSource()構造方法
:構造一個預設的 DruidDataSource實例這個實例主要用於顯性配置Druid屬性setXXX():
設置屬性值,如setUrl(String url)
,setInitialSize(int initialSize)
等等
DruidDataSourceFactory
類:是一個工廠類,用於根據提供的配置信息創建DruidDataSource
實例,這個類簡化了從配置文件中載入配置信息並創建DruidDataSource
的過程createDataSource(Properties properties)
:最常用的方法之一,其接受一個Properties對象作為參數,從該對象讀取配置信息,並創建一個DruidDataSource
-
將Druid封裝為JdbcUtils類
public class JdbcUtil { private static DataSource dataSource; static { //先建立配置,連接連接池 try { InputStream inputStream = JdbcUtil.class.getResourceAsStream("resource/application.properties"); Properties props = new Properties(); props.load(inputStream); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() { Connection conn = null; try { conn = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return conn; } public static void close(ResultSet rs, PreparedStatement stmt, Connection conn) { try { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
-
本文藉鑒同平臺許多作者如@少平的博客人生,@chy_18883701161,@濫好人