前言
- 大二上學期跟著學校里面的老師學JavaSE的時候?qū)懥艘黄浅@腏DBC的文章JDBC,那時候自認為自己對JDBC的認識已經(jīng)很高了,認為它很簡單,現(xiàn)在再回頭看,發(fā)現(xiàn)自己那時候完全是井底之蛙,所以我覺得得再寫一篇文章深入談談JDBC,算是給自己的黑歷史抹白吧。
- 我之前寫的那篇jdbc文章,使用
statement操作sql語句會存在SQL注入問題,之后會再寫一篇文件談談這個問題 - 鑒于之前寫了關(guān)于MySQL事務機制的文章,之后,我也會談談JDBC事務機制的使用
JDBC本質(zhì)
JDBC,本質(zhì)上就是一套接口,是SUN公司制定的一套接口(interface),包名為:java.sql.*(這個軟件包下有很多接口)
為什么面向接口編程
我們先來看看什么是面向接口編程:
接口都有調(diào)用者和實現(xiàn)者,面向接口調(diào)用、面向接口寫實現(xiàn)類,這都屬于面向接口編程。
為什么要面向接口編程?
- 解耦合:
- 降低程序的耦合度
- 提高程序的擴展力。
多態(tài)機制就是非常典型的:面向抽象編程。(不要面向具體編程)
思考
現(xiàn)在思考:為什么SUN公司制定一套JDBC接口呢?
- 因為每一個數(shù)據(jù)庫的底層實現(xiàn)原理都不一樣。
- Oracle數(shù)據(jù)庫有自己的原理。
- MySQL數(shù)據(jù)庫也有自己的原理。
- MS SqlServer數(shù)據(jù)庫也有自己的原理。
每一個數(shù)據(jù)庫產(chǎn)品都有自己獨特的實現(xiàn)原理,如果我們不寫一套接口,那么我們這些程序員對同一套Java程序,需要準備幾套面向不同數(shù)據(jù)庫的Java代碼,程序員會累死在重復且無意義的工作上。
而面向接口編程可以實現(xiàn)一個規(guī)范,從而讓我們java程序員寫一套java代碼,適配不同的數(shù)據(jù)庫,達到解放程序員的效果
三方模擬JDBC本質(zhì)
我們扮演SUN公司,Java程序員,數(shù)據(jù)庫廠商這三方來模擬一下JDBC
SUN公司
/*
SUN公司負責制定這套JDBC接口。
*/
public interface JDBC{
/*
連接數(shù)據(jù)庫的方法。
*/
void getConnection();
}
數(shù)據(jù)庫廠商
數(shù)據(jù)庫廠商對SUN公司制定出的接口的實現(xiàn)類,我們稱之為驅(qū)動(Driver)
MySQL
/*
MySQL的數(shù)據(jù)庫廠家負責編寫JDBC接口的實現(xiàn)類
*/
public class MySQL implements JDBC{
public void getConnection(){
// 具體這里的代碼怎么寫,對于我們Java程序員來說沒關(guān)系
// 這段代碼涉及到mysql底層數(shù)據(jù)庫的實現(xiàn)原理。
System.out.println("連接MYSQL數(shù)據(jù)庫成功!");
}
}
Oracle數(shù)據(jù)庫廠商
/*
Oracle的數(shù)據(jù)庫廠家負責編寫JDBC接口的實現(xiàn)類
*/
public class Oracle implements JDBC{
public void getConnection(){
System.out.println("連接Oracle數(shù)據(jù)庫成功!");
}
}
Java程序員
顯然,java程序員在操作MySQL數(shù)據(jù)庫時,需要先把MySQL數(shù)據(jù)庫廠商的對JDBC的實現(xiàn)類的jar包導入工程,也就是需要先把mysql驅(qū)動導入工程,不然接口的方法沒有實現(xiàn)類,程序自然無法執(zhí)行。
同理,java程序員在操作Oracle數(shù)據(jù)庫時,也需要先導入Oracle驅(qū)動
/*
Java程序員角色。
不需要關(guān)心具體是哪個品牌的數(shù)據(jù)庫,只需要面向JDBC接口寫代碼。
面向接口編程,面向抽象編程,不要面向具體編程。
*/
import java.util.*;
public class JavaProgrammer
{
public static void main(String[] args) throws Exception{
// JDBC jdbc = new MySQL();
// JDBC jdbc = new SqlServer();
// 創(chuàng)建對象可以通過反射機制。
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String className = bundle.getString("className");
Class c = Class.forName(className);
JDBC jdbc = (JDBC)c.newInstance();
// 以下代碼都是面向接口調(diào)用方法,不需要修改
jdbc.getConnection();
}
}
三方之間的關(guān)系圖

JDBC編程六步法
第一步:注冊驅(qū)動
- 作用:告訴Java程序,即將要連接的是哪個品牌的數(shù)據(jù)庫
Driver driver = new com.mysql.cj.jdbc.Driver(); // 使用多態(tài),父類型引用指向子類型對象
DriverManager.registerDriver(driver);
上面代碼可以合并為下面一行
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
注冊驅(qū)動這里還可以再進行代碼優(yōu)化,我們就在下面JDBC工具類,DBUtils里面進行
第二步:獲取連接
- 表示JVM的進程和數(shù)據(jù)庫進程之間的通道打開了,這屬于進程之間的通信,重量級的操作,使用完之后一定要關(guān)閉通道
獲取連接的url需要遵循通信協(xié)議
- 通信協(xié)議是通信之前就提前定好的數(shù)據(jù)傳送格式
- 它包括數(shù)據(jù)包具體怎樣傳數(shù)據(jù),其格式是提前定好的
url案例
jdbc:mysql://127.0.0.1:3306/swu
swu是西南大學的英文簡寫,我自己設置的數(shù)據(jù)庫的名字
-
jdbc:mysql://-------->協(xié)議 -
127.0.0.1------------->IP地址 -
3306/mysql----------->數(shù)據(jù)庫端口號 -
swu--------------------->具體的數(shù)據(jù)庫名
String url = "jdbc:mysql://192.168.151.9:3306/bjpowernode";
String user = "root";
String password = "981127";
String connection = DriverManager.getConnection(url,user,password);
注意
不同數(shù)據(jù)庫的通信的協(xié)議不同,例如Oracle數(shù)據(jù)庫的通信協(xié)議如下:
jdbc:oracle:thin:@localhost:1521:數(shù)據(jù)庫名
第三步:獲取數(shù)據(jù)庫操作對象
- 專門執(zhí)行sql語句的對象
Statement stmt = connection.createStatement();
一般較少使用statement執(zhí)行SQL語句,因為有SQL注入問題。
我們一般使用PrepareStatement對象,之后會談到
第四步:執(zhí)行SQL語句
JDBC中的sql語句不需要提供分號結(jié)尾
String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
// 專門執(zhí)行DML語句的(insert delete update)
// 返回值是“影響數(shù)據(jù)庫中的記錄條數(shù)”
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "保存成功" : "保存失敗");
第五步:處理查詢結(jié)果集
- 只有當?shù)谒牟綀?zhí)行的是
select語句的時候,才要處理結(jié)果集
我們用ResultSet對象來接收執(zhí)行sql語句的返回值后,這個數(shù)據(jù)集是什么樣子呢?
如下圖:

這里詳細談談ResultSet的nextLine()方法,getString()方法
nextLine()方法判斷下一行是否有數(shù)據(jù),它類似于一個浮標的移動,如果下一行有數(shù)據(jù),則返回true,否則返回false
getString()方法:不管數(shù)據(jù)庫中的數(shù)據(jù)類型是什么,都以String的形式取出,同理:還有g(shù)etInt,getDouble等
當然,如果在()內(nèi)輸入字段名,則會以列的名字獲得該數(shù)據(jù),輸入單純的數(shù)字n,則取出第n列的數(shù)據(jù)
注意:
- JDBC中所有下標從1開始,不是從0開始。
- 列名稱不是表中的列名稱,是查詢結(jié)果集的列名稱
第六步:釋放資源
- 使用完資源之后一定要關(guān)閉資源。
- Java和數(shù)據(jù)庫屬于進程間的通信,開啟之后一定要關(guān)閉。
- 不關(guān)閉通道,時間一長可能出現(xiàn)問題
為了保證資源一定釋放,在finally語句塊中關(guān)閉資源,并且要遵循從小到大依次關(guān)閉
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
JDBC工具類的寫法
我們通常創(chuàng)建一個類名為DBUtils的工具類來封裝JDBC中注冊驅(qū)動,獲取連接,釋放資源部分的代碼,提高代碼復用性
import java.sql.*;
/**
* JDBC工具類,簡化JDBC編程。
*/
public class DBUtil {
/**
* 工具類中的構(gòu)造方法都是私有的。
* 因為工具類當中的方法都是靜態(tài)的,不需要new對象,直接采用類名調(diào)用。
*/
private DBUtil() {
}
// 靜態(tài)代碼塊在類加載時執(zhí)行,并且只執(zhí)行一次。
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 獲取數(shù)據(jù)庫連接對象
*
* @return 連接對象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/swu", "root", "password");
}
/**
* 關(guān)閉資源
* @param conn 連接對象
* @param ps 數(shù)據(jù)庫操作對象
* @param rs 結(jié)果集
*/
public static void close(Connection conn, Statement ps, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
ResourceBundle綁定屬性配置文件
資源配置文件準備
文件名:jdbc.properties
內(nèi)容:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://主機IP/數(shù)據(jù)庫名
user=數(shù)據(jù)庫用戶名
password=數(shù)據(jù)庫用戶密碼
綁定
使用ResourceBundle的getBundle方法,傳遞參數(shù)為資源配置文件(.properties后綴文件)名字,不包括后綴
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");