前言
- 大二上學(xué)期跟著學(xué)校里面的老師學(xué)JavaSE的時候?qū)懥艘黄浅@腏DBC的文章JDBC捺疼,那時候自認(rèn)為自己對JDBC的認(rèn)識已經(jīng)很高了丈挟,認(rèn)為它很簡單,現(xiàn)在再回頭看撵孤,發(fā)現(xiàn)自己那時候完全是井底之蛙摔寨,所以我覺得得再寫一篇文章深入談?wù)凧DBC去枷,算是給自己的黑歷史抹白吧。
- 我之前寫的那篇jdbc文章是复,使用
statement
操作sql
語句會存在SQL注入
問題删顶,之后會再寫一篇文件談?wù)勥@個問題 - 鑒于之前寫了關(guān)于MySQL事務(wù)機(jī)制的文章,之后淑廊,我也會談?wù)凧DBC事務(wù)機(jī)制的使用
JDBC本質(zhì)
JDBC逗余,本質(zhì)上就是一套接口,是SUN公司制定的一套接口(interface
)季惩,包名為:java.sql.*
(這個軟件包下有很多接口)
為什么面向接口編程
我們先來看看什么是面向接口編程:
接口都有調(diào)用者和實(shí)現(xiàn)者录粱,面向接口調(diào)用、面向接口寫實(shí)現(xiàn)類画拾,這都屬于面向接口編程啥繁。
為什么要面向接口編程?
- 解耦合:
- 降低程序的耦合度
- 提高程序的擴(kuò)展力青抛。
多態(tài)機(jī)制就是非常典型的:面向抽象編程旗闽。(不要面向具體編程)
思考
現(xiàn)在思考:為什么SUN公司制定一套JDBC接口呢?
- 因?yàn)槊恳粋€數(shù)據(jù)庫的底層實(shí)現(xiàn)原理都不一樣蜜另。
- Oracle數(shù)據(jù)庫有自己的原理适室。
- MySQL數(shù)據(jù)庫也有自己的原理。
- MS SqlServer數(shù)據(jù)庫也有自己的原理蚕钦。
每一個數(shù)據(jù)庫產(chǎn)品都有自己獨(dú)特的實(shí)現(xiàn)原理亭病,如果我們不寫一套接口,那么我們這些程序員對同一套Java程序嘶居,需要準(zhǔn)備幾套面向不同數(shù)據(jù)庫的Java代碼,程序員會累死在重復(fù)且無意義的工作上。
而面向接口編程可以實(shí)現(xiàn)一個規(guī)范邮屁,從而讓我們java程序員寫一套java代碼整袁,適配不同的數(shù)據(jù)庫,達(dá)到解放程序員的效果
三方模擬JDBC本質(zhì)
我們扮演SUN公司佑吝,Java程序員坐昙,數(shù)據(jù)庫廠商這三方來模擬一下JDBC
SUN公司
/*
SUN公司負(fù)責(zé)制定這套JDBC接口。
*/
public interface JDBC{
/*
連接數(shù)據(jù)庫的方法芋忿。
*/
void getConnection();
}
數(shù)據(jù)庫廠商
數(shù)據(jù)庫廠商對SUN公司制定出的接口的實(shí)現(xiàn)類,我們稱之為驅(qū)動(Driver)
MySQL
/*
MySQL的數(shù)據(jù)庫廠家負(fù)責(zé)編寫JDBC接口的實(shí)現(xiàn)類
*/
public class MySQL implements JDBC{
public void getConnection(){
// 具體這里的代碼怎么寫,對于我們Java程序員來說沒關(guān)系
// 這段代碼涉及到mysql底層數(shù)據(jù)庫的實(shí)現(xiàn)原理灸芳。
System.out.println("連接MYSQL數(shù)據(jù)庫成功期升!");
}
}
Oracle數(shù)據(jù)庫廠商
/*
Oracle的數(shù)據(jù)庫廠家負(fù)責(zé)編寫JDBC接口的實(shí)現(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的實(shí)現(xiàn)類的jar包導(dǎo)入工程,也就是需要先把mysql驅(qū)動導(dǎo)入工程薪铜,不然接口的方法沒有實(shí)現(xiàn)類众弓,程序自然無法執(zhí)行。
同理隔箍,java程序員在操作Oracle數(shù)據(jù)庫時谓娃,也需要先導(dǎo)入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)建對象可以通過反射機(jī)制。
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ū)動這里還可以再進(jìn)行代碼優(yōu)化涩搓,我們就在下面JDBC工具類污秆,DBUtils
里面進(jìn)行
第二步:獲取連接
- 表示JVM的進(jìn)程和數(shù)據(jù)庫進(jìn)程之間的通道打開了,這屬于進(jìn)程之間的通信昧甘,重量級的操作良拼,使用完之后一定要關(guān)閉通道
獲取連接的url需要遵循通信協(xié)議
- 通信協(xié)議是通信之前就提前定好的數(shù)據(jù)傳送格式
- 它包括數(shù)據(jù)包具體怎樣傳數(shù)據(jù),其格式是提前定好的
url案例
jdbc:mysql://127.0.0.1:3306/swu
swu是西南大學(xué)的英文簡寫充边,我自己設(shè)置的數(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語句常侦,因?yàn)橛蠸QL注入問題。
我們一般使用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é)果集
- 只有當(dāng)?shù)谒牟綀?zhí)行的是
select
語句的時候聋亡,才要處理結(jié)果集
我們用ResultSet對象來接收執(zhí)行sql語句的返回值后,這個數(shù)據(jù)集是什么樣子呢际乘?
如下圖:
這里詳細(xì)談?wù)?em>ResultSet的nextLine()
方法坡倔,getString()
方法
nextLine()
方法判斷下一行是否有數(shù)據(jù),它類似于一個浮標(biāo)的移動脖含,如果下一行有數(shù)據(jù)罪塔,則返回true
,否則返回false
getString()
方法:不管數(shù)據(jù)庫中的數(shù)據(jù)類型是什么养葵,都以String
的形式取出征堪,同理:還有g(shù)etInt,getDouble等
當(dāng)然港柜,如果在()內(nèi)輸入字段名请契,則會以列的名字獲得該數(shù)據(jù),輸入單純的數(shù)字n夏醉,則取出第n列的數(shù)據(jù)
注意:
- JDBC中所有下標(biāo)從1開始爽锥,不是從0開始。
- 列名稱不是表中的列名稱畔柔,是查詢結(jié)果集的列名稱
第六步:釋放資源
- 使用完資源之后一定要關(guān)閉資源氯夷。
- Java和數(shù)據(jù)庫屬于進(jìn)程間的通信,開啟之后一定要關(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ū)動踩蔚,獲取連接,釋放資源部分的代碼枚粘,提高代碼復(fù)用性
import java.sql.*;
/**
* JDBC工具類馅闽,簡化JDBC編程。
*/
public class DBUtil {
/**
* 工具類中的構(gòu)造方法都是私有的馍迄。
* 因?yàn)楣ぞ哳惍?dāng)中的方法都是靜態(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綁定屬性配置文件
資源配置文件準(zhǔn)備
文件名:jdbc.properties
內(nèi)容:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://主機(jī)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");