Java JDBC API介紹與實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池流程
前言:上一節(jié)我?guī)Т蠹疑鲜至?a href="http://www.dhdzp.com/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 使用Axis調(diào)用WebService的示例代碼
這篇文章主要介紹了Java 使用Axis調(diào)用WebService的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-09-09
詳解Java中static關(guān)鍵字和內(nèi)部類(lèi)的使用
這篇文章主要為大家詳細(xì)介紹了Java中static關(guān)鍵字和內(nèi)部類(lèi)的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-08-08
java兩個(gè)線(xiàn)程同時(shí)寫(xiě)一個(gè)文件
這篇文章主要為大家詳細(xì)介紹了java兩個(gè)線(xiàn)程同時(shí)寫(xiě)一個(gè)文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
spring解決循環(huán)依賴(lài)的簡(jiǎn)單方法
這篇文章主要給大家介紹了關(guān)于spring解決循環(huán)依賴(lài)的簡(jiǎn)單方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Spring5中SpringWebContext方法過(guò)時(shí)的解決方案
這篇文章主要介紹了Spring5中SpringWebContext方法過(guò)時(shí)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
springboot實(shí)現(xiàn)圖片上傳與下載功能
這篇文章主要為大家詳細(xì)介紹了后端spring項(xiàng)目經(jīng)常要做的功能,實(shí)現(xiàn)圖片上傳和下載,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12
SpringBoot使用Apache?Tika檢測(cè)敏感信息
Apache?Tika?是一個(gè)功能強(qiáng)大的內(nèi)容分析工具,它能夠從多種文件格式中提取文本、元數(shù)據(jù)以及其他結(jié)構(gòu)化信息,下面我們來(lái)看看如何使用Apache?Tika檢測(cè)敏感信息從而實(shí)現(xiàn)數(shù)據(jù)泄露防護(hù)吧2025-01-01

