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資料庫
https://dev.mysql.com/downloads/connector/
-
Oracle資料庫
https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html
-
SQL Server 資料庫
https://docs.microsoft.com/zh-cn/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server?view=sql-server-2017
-
-
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,@濫好人