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