Java JDBC API介紹與實(shí)現(xiàn)數(shù)據(jù)庫連接池流程
前言:上一節(jié)我?guī)Т蠹疑鲜至?a href="http://www.dbjr.com.cn/article/269474.htm" target="_blank">JDBC的基本代碼,這節(jié)我們仔細(xì)講一下JDBC的API和數(shù)據(jù)庫連接池。
JDBC API詳解
DriverManager
DriverManager(驅(qū)動管理類)作用:注冊驅(qū)動、獲取數(shù)據(jù)庫連接
注冊驅(qū)動
registerDriver方法是用于注冊驅(qū)動的,但是我們之前做的入門案例并不是這樣寫的。而是如下實(shí)現(xiàn)
Class.forName("com.mysql.jdbc.Driver");
我們查詢MySQL提供的Driver類,看它是如何實(shí)現(xiàn)的,源碼如下:
static { try { DriverManager.registerDriver(new Driver()); }catch (SQLException var1) { throw new RuntimeException( "can 't register driver! "); } }
在該類中的靜態(tài)代碼塊中已經(jīng)執(zhí)行了 DriverManager 對象的registerDriver() 方法進(jìn)行驅(qū)動的注冊了,那么我們只需要加載 Driver 類,該靜態(tài)代碼塊就會執(zhí)行。而Class.forName("com.mysql.jdbc.Driver");
就可以加載Driver 類。
提示:MySQL 5之后的驅(qū)動包,可以省略注冊驅(qū)動的步驟,自動加載jar包中META-INF/services/java.sql.Driver文件中的驅(qū)動類
獲取數(shù)據(jù)庫連接
參數(shù)說明:
- user :用戶名
- password:密碼
- url : 連接路徑
語法:
jdbc:mysql://ip地址(域名):端口號/數(shù)據(jù)庫名稱?參數(shù)鍵值對1&參數(shù)鍵值對2…
示例:
jdbc:mysql://localhost:3306/jdbc
補(bǔ)充:
- 如果連接的是本機(jī)mysql服務(wù)器,并且mysql服務(wù)默認(rèn)端口是3306,則url可以簡寫為:jdbc:mysql:///數(shù)據(jù)庫名稱?參數(shù)鍵值對
- 配置 useSSL=false 參數(shù),禁用安全連接方式,解決警告提示
Connection
Connection(數(shù)據(jù)庫連接對象)作用:獲取執(zhí)行 SQL 的對象、管理事務(wù)
獲取執(zhí)行對象
普通執(zhí)行SQL對象
Statement createStatement()
入門案例中就是通過該方法獲取的執(zhí)行對象。
預(yù)編譯SQL的執(zhí)行SQL對象:防止SQL注入(重要?。。。?/p>
PreparedStatement prepareStatement(sql)
執(zhí)行存儲過程的對象
CallableStatement prepareCall(sql)
事務(wù)管理
回顧一下MySQL事務(wù)管理的操作:
開啟事務(wù) :
BEGIN;
或者
START TRANSACTION;
提交事務(wù) :
COMMIT;
回滾事務(wù) :
ROLLBACK;
MySQL默認(rèn)是自動提交事務(wù)
JDBC事務(wù)管理的方法
Connection幾口中定義了3個對應(yīng)的方法:
開啟事務(wù)
參與autoCommit 表示是否自動提交事務(wù),true表示自動提交事務(wù),false表示手動提交事務(wù)。而開啟事務(wù)需要將該參數(shù)設(shè)為為false。
提交事務(wù)
回滾事務(wù)
案例測試事務(wù)管理
編寫代碼
package com.bby; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class JdbcTransaction { public static void main(String[] args) throws Exception { String url = "jdbc:mysql:///jdbc?useSSL=false"; String username = "root"; String password = "1234"; String sql = "update acount set money = 1002 where id = 1"; String sql2 = "update acount set money = 1002 whe id = 1"; //注意這里SQL語句故意將where寫錯 Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, username, password); Statement statement = connection.createStatement(); try { connection.setAutoCommit(false); // 開啟手動事務(wù) //執(zhí)行SQL語句并處理結(jié)果 int count = statement.executeUpdate(sql); System.out.println(count); int count2 = statement.executeUpdate(sql2); System.out.println(count2); //提交事務(wù) connection.commit(); } catch (Exception e) { //程序出現(xiàn)異常,回滾事務(wù) connection.rollback(); e.printStackTrace(); } } }
執(zhí)行程序,看控制臺結(jié)果
數(shù)據(jù)庫更新前
數(shù)據(jù)庫更新后
這里程序出現(xiàn)異常,進(jìn)行了事務(wù)的回滾,所以數(shù)據(jù)都沒有被更新
將代碼更改正確
String sql2 = "update acount set money = 1002 wher id = 2";
執(zhí)行程序測試
Statement
概述
Statement對象的作用就是用來執(zhí)行SQL語句。而針對不同類型的SQL語句使用的方法也不一樣。
執(zhí)行DDL、DML語句
執(zhí)行DQL語句
代碼演示
此處只展示核心代碼,具體代碼可以參考上面的代碼案例
DML語句
// 定義sql String sql = "update account set money = 3000 where id = 1"; // 獲取執(zhí)行sql的對象 Statement Statement stmt = conn.createStatement(); // 執(zhí)行sql int count = stmt.executeUpdate(sql); //執(zhí)行完DML語句,受影響的行數(shù)
執(zhí)行DDL語句
// 定義sql String sql = "drop database db2"; // 獲取執(zhí)行sql的對象 Statement Statement stmt = conn.createStatement(); // 執(zhí)行sql int count = stmt.executeUpdate(sql); //執(zhí)行完DDL語句,可能是0
ResultSet
概述
ResultSet(結(jié)果集對象)作用:封裝了SQL查詢語句的結(jié)果
執(zhí)行了DQL語句后就會返回該對象,對應(yīng)執(zhí)行DQL語句的方法如下:
ResultSet executeQuery(sql):執(zhí)行DQL 語句,返回 ResultSet 對象
那么我們就需要從 ResultSet 對象中獲取我們想要的數(shù)據(jù)。ResultSet 對象提供了操作查詢結(jié)果數(shù)據(jù)的方法,如下:
如下圖為執(zhí)行SQL語句后的結(jié)果
一開始光標(biāo)指定于第一行前,如圖所示紅色箭頭指向于表頭行。當(dāng)我們調(diào)用了 next() 方法后,光標(biāo)就下移到第一行數(shù)據(jù),并且方法返回true,此時就可以通過 getInt(“id”) 獲取當(dāng)前行id字段的值,也可以通過 getString(“name”) 獲取當(dāng)前行name字段的值。如果想獲取下一行的數(shù)據(jù),繼續(xù)調(diào)用 next() 方法,以此類推。
代碼演示
編寫JdbcResultSet
package com.bby; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcResultSet { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/jdbc?useSSL=false"; String username = "root"; String password = "1234"; Connection connection = DriverManager.getConnection(url, username, password); Statement statement = connection.createStatement(); //定義查詢的SQL語句 String sql = "select * from acount"; //執(zhí)行查詢語句,獲取結(jié)果集 ResultSet resultSet = statement.executeQuery(sql); //遍歷結(jié)果集 while(resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String money = resultSet.getString("money"); System.out.println("id:" + id); System.out.println("name:" + name); System.out.println("money:" + money); System.out.println("-----------------"); } resultSet.close(); statement.close(); connection.close(); } }
查看控制臺輸出
PreparedStatement
SQL注入
SQL注入是通過操作輸入來修改事先定義好的SQL語句,用以達(dá)到執(zhí)行代碼對服務(wù)器進(jìn)行攻擊的方法。
代碼模擬SQL注入問題
package com.bby; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcSqlInjection { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/jdbc?useSSL=false"; String username = "root"; String password = "1234"; Connection connection = DriverManager.getConnection(url, username, password); // 接收用戶輸入 用戶名和密碼 String name = "abcdefg"; String pwd = "' or '1' = '1"; String sql = "select * from users where name = '" + name + "' and password = '" + pwd + "'"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); // 判斷登錄是否成功 if (resultSet.next()) { System.out.println("登錄成功~"); } else { System.out.println("登錄失敗~"); } resultSet.close(); statement.close(); connection.close(); } }
上面代碼是將用戶名和密碼拼接到sql語句中,拼接后的sql語句如下:
select * from users where name = 'abcdefg' and password = '' or '1' = '1'
從上面語句可以看出條件name = 'abcdefg' and password = ''
不管是否滿足,而 or
后面的 '1' = '1'
是始終滿足的,最終條件是成立的,就可以正常的進(jìn)行登陸了。
所以不管登錄的密碼是否正確都可以登錄成功,控制臺輸出如下
PreparedStatement 概述
PreparedStatement作用:預(yù)編譯SQL語句并執(zhí)行,預(yù)防SQL注入問題
獲取 PreparedStatement 對象
// SQL語句中的參數(shù)值,使用?占位符替代 String sql = "select * from users where name = ? and password = ?"; // 通過Connection對象獲取,并傳入對應(yīng)的sql語句 PreparedStatement ps = connection.prepareStatement(sql);
設(shè)置參數(shù)值
上面的sql語句中參數(shù)使用 ? 進(jìn)行占位,在之前之前肯定要設(shè)置這些 ? 的值。
PreparedStatement對象:
setXxx(參數(shù)1,參數(shù)2);//給 ? 賦值, 參數(shù)1是編號(從1開始) 參數(shù)2是值
(1)Xxx:數(shù)據(jù)類型 ; 如 setInt (參數(shù)1,參數(shù)2)
(2)參數(shù)1: ?的位置編號,從1 開始 參數(shù)2: ?的值
執(zhí)行SQL語句
executeUpdate(); // 執(zhí)行DDL語句和DML語句
executeQuery(); // 執(zhí)行DQL語句
注意:調(diào)用這兩個方法時不需要傳遞SQL語句,因?yàn)楂@取SQL語句執(zhí)行對象時已經(jīng)對SQL語句進(jìn)行預(yù)編譯了。
代碼演示
編寫JdbcPreparedStatement
package com.bby; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class JdbcPreparedStatement { public static void main(String[] args) throws Exception { String url = "jdbc:mysql:///jdbc?useSSL=false"; String username = "root"; String password = "1234"; String name = "abcdefg"; String pwd = "' or '1' = '1"; Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, username, password); //定義SQL語句 String sql = "select * from users where name = ? and password = ?"; //預(yù)編譯SQL語句 PreparedStatement ps = connection.prepareStatement(sql); //填充占位符 ps.setString(1, name); ps.setString(2, pwd); //執(zhí)行SQL語句 ResultSet resultSet = ps.executeQuery(); //判斷登錄是否成功 if(resultSet.next()) { System.out.println("登陸成功!"); } else { System.out.println("登錄失敗"); } resultSet.close(); ps.close(); connection.close(); } }
查看控制臺結(jié)果
執(zhí)行上面語句就可以發(fā)現(xiàn)不會出現(xiàn)SQL注入漏洞問題了。那么PreparedStatement又是如何解決的呢?它是將特殊字符進(jìn)行了轉(zhuǎn)
義,轉(zhuǎn)義的SQL如下:
select * from `users` where name = 'abcdefg' and password = '\'or \'1\' = \'1'
PreparedStatement原理
PreparedStatement 好處:1.預(yù)編譯SQL,性能更高。2.防止SQL注入:將敏感字符進(jìn)行轉(zhuǎn)義
Java代碼操作數(shù)據(jù)庫流程如圖所示:
將sql語句發(fā)送到MySQL服務(wù)器端
MySQL服務(wù)端會對sql語句進(jìn)行如下操作
檢查SQL語句
檢查SQL語句的語法是否正確。
編譯SQL語句。將SQL語句編譯成可執(zhí)行的函數(shù)。
檢查SQL和編譯SQL花費(fèi)的時間比執(zhí)行SQL的時間還要長。如果我們只是重新設(shè)置參數(shù),那么檢查SQL語句和編譯SQL語句將不需要重復(fù)執(zhí)行。這樣就提高了性能。
執(zhí)行SQL語句
MySQL服務(wù)端將結(jié)果返回
數(shù)據(jù)庫連接池
數(shù)據(jù)庫連接池簡介
- 數(shù)據(jù)庫連接池是個容器,負(fù)責(zé)分配、管理數(shù)據(jù)庫連接(Connection)
- 它允許應(yīng)用程序重復(fù)使用一個現(xiàn)有的數(shù)據(jù)庫連接,而不是再重新建立一個;
- 釋放空閑時間超過最大空閑時間的數(shù)據(jù)庫連接來避免因?yàn)闆]有釋放數(shù)據(jù)庫連接而引起的數(shù)據(jù)庫連接遺漏
- 好處:資源重用、提升系統(tǒng)響應(yīng)速度、避免數(shù)據(jù)庫連接遺漏
之前我們代碼中使用連接使沒有使用都創(chuàng)建一個Connection對象,使用完畢就會將其銷毀。這樣重復(fù)創(chuàng)建銷毀的過程是特別耗費(fèi)計(jì)算機(jī)的性能的及消耗時間的。而數(shù)據(jù)庫使用了數(shù)據(jù)庫連接池后,就能達(dá)到Connection對象的復(fù)用,如下圖:
連接池是在一開始就創(chuàng)建好了一些連接(Connection)對象存儲起來。用戶需要連接數(shù)據(jù)庫時,不需要自己創(chuàng)建連接,而只需要從連
接池中獲取一個連接進(jìn)行使用,使用完畢后再將連接對象歸還給連接池;這樣就可以起到資源重用,也節(jié)省了頻繁創(chuàng)建連接銷毀連接
所花費(fèi)的時間,從而提升了系統(tǒng)響應(yīng)的速度。
數(shù)據(jù)庫連接池實(shí)現(xiàn)
標(biāo)準(zhǔn)接口:DataSource
官方(SUN) 提供的數(shù)據(jù)庫連接池標(biāo)準(zhǔn)接口,由第三方組織實(shí)現(xiàn)此接口。該接口提供了獲取連接的功能:
Connection getConnection()
那么以后就不需要通過 DriverManager
對象獲取 Connection
對象,而是通過連接池(DataSource)獲取 Connection
對象。
常見的數(shù)據(jù)庫連接池:DBCP 、C3P0 、Druid
我們現(xiàn)在使用更多的是Druid,它的性能比其他兩個會好一些
Druid(德魯伊)
Druid連接池是阿里巴巴開源的數(shù)據(jù)庫連接池項(xiàng)目,功能強(qiáng)大,性能優(yōu)秀,是Java語言最好的數(shù)據(jù)庫連接池之一
Driud 的使用
導(dǎo)入jar包 druid-1.1.12.jar
將druid的jar包放到項(xiàng)目下的lib下并添加為庫文件
編寫配置文件 druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbc
useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化連接數(shù)量
initialSize=5
# 最大連接數(shù)
maxActive=10
# 最大等待時間
maxWait=3000
編寫Java代碼(JdbcDruid)
package com.bby; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.util.Properties; public class JdbcDruid { public static void main(String[] args) throws Exception { // 加載配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); // 獲取連接池對象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); // 獲取數(shù)據(jù)庫連接 Connection Connection connection = dataSource.getConnection(); System.out.println(connection); //獲取到了連接后就可以繼續(xù)做其他操作了 } }
運(yùn)行查看控制臺結(jié)果
到此這篇關(guān)于Java JDBC API介紹與實(shí)現(xiàn)數(shù)據(jù)庫連接池流程的文章就介紹到這了,更多相關(guān)Java JDBC API內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java?Synchronized的實(shí)現(xiàn)原理
談到多線程就不得不談到Synchronized,重要性不言而喻,今天主要談?wù)凷ynchronized的實(shí)現(xiàn)原理。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09Java面試必考之如何在項(xiàng)目中優(yōu)雅的拋出異常
這篇文章主要為大家詳細(xì)介紹了Java中的幾種異常關(guān)鍵字和異常類相關(guān)知識,本文比較適合剛?cè)肟覬ava的小白以及準(zhǔn)備秋招的大佬閱讀,需要的可以收藏一下2023-06-06java+mysql實(shí)現(xiàn)圖書館管理系統(tǒng)實(shí)戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了java+mysql實(shí)現(xiàn)圖書館管理系統(tǒng)實(shí)戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12ActiveMQ基于zookeeper的主從(levelDB Master/Slave)搭建
這篇文章主要介紹了ActiveMQ基于zookeeper的主從levelDB Master/Slave搭建,以及Spring-boot下的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Java經(jīng)典排序算法之冒泡排序代碼實(shí)例
這篇文章主要介紹了Java經(jīng)典排序算法之冒泡排序代碼實(shí)例,相鄰兩元素進(jìn)行比較,如過左側(cè)元素大于右側(cè)元素,則進(jìn)行交換,每完成一次循環(huán)就將最大元素排在最后,下一次循環(huán)是將其它的數(shù)進(jìn)行類似操作,需要的朋友可以參考下2023-11-11SpringBoot項(xiàng)目啟動打包報(bào)錯類文件具有錯誤的版本 61.0, 應(yīng)為 52.0的解決
這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目啟動打包報(bào)錯類文件具有錯誤的版本 61.0, 應(yīng)為 52.0的解決方法,文中有詳細(xì)的排查過程和解決方法,通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11