JDBC实战(一)JDBC概述
文章目錄
- JDBC概述
- 1、什么是JDBC?
- 2、簡(jiǎn)單的小Demo入門(mén)JDBC
- 2.1、使用JDBC連接MySQL數(shù)據(jù)庫(kù)
- 2.2、優(yōu)化第一個(gè)Demo
- 2.3、 三種注冊(cè)JDBC驅(qū)動(dòng)的方式
- 2.4、使用JDBC實(shí)現(xiàn)增刪改查(CRUD)
- 2.5、了解JDBC中的SQL注入問(wèn)題
JDBC概述
1、什么是JDBC?
什么是JDBC?JDBC是Java DataBase Connectivity的縮寫(xiě),它是Java程序訪問(wèn)數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)接口。
使用Java程序訪問(wèn)數(shù)據(jù)庫(kù)時(shí),Java代碼并不是直接通過(guò)TCP連接去訪問(wèn)數(shù)據(jù)庫(kù),而是通過(guò)JDBC接口來(lái)訪問(wèn),而JDBC接口則通過(guò)JDBC驅(qū)動(dòng)來(lái)實(shí)現(xiàn)真正對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)。
例如,我們?cè)贘ava代碼中如果要訪問(wèn)MySQL,那么必須編寫(xiě)代碼操作JDBC接口。注意到JDBC接口是Java標(biāo)準(zhǔn)庫(kù)自帶的,所以可以直接編譯。而具體的JDBC驅(qū)動(dòng)是由數(shù)據(jù)庫(kù)廠商提供的,例如,MySQL的JDBC驅(qū)動(dòng)由Oracle提供。因此,訪問(wèn)某個(gè)具體的數(shù)據(jù)庫(kù),我們只需要引入該廠商提供的JDBC驅(qū)動(dòng),就可以通過(guò)JDBC接口來(lái)訪問(wèn),這樣保證了Java程序編寫(xiě)的是一套數(shù)據(jù)庫(kù)訪問(wèn)代碼,卻可以訪問(wèn)各種不同的數(shù)據(jù)庫(kù),因?yàn)樗麄兌继峁┝藰?biāo)準(zhǔn)的JDBC驅(qū)動(dòng)。
JDBC架構(gòu)
JDBC API支持用于數(shù)據(jù)庫(kù)訪問(wèn)的兩層和三層處理模型,但通常,JDBC體系結(jié)構(gòu)由兩層組成:
- JDBC API:提供應(yīng)用程序到JDBC管理器連接。
- JDBC驅(qū)動(dòng)程序API:支持JDBC管理器到驅(qū)動(dòng)程序連接。
JDBC API使用驅(qū)動(dòng)程序管理器并指定數(shù)據(jù)庫(kù)的驅(qū)動(dòng)程序來(lái)提供與異構(gòu)數(shù)據(jù)庫(kù)的透明連接。
JDBC驅(qū)動(dòng)程序管理器確保使用正確的驅(qū)動(dòng)程序來(lái)訪問(wèn)每個(gè)數(shù)據(jù)源。 驅(qū)動(dòng)程序管理器能夠支持連接到多個(gè)異構(gòu)數(shù)據(jù)庫(kù)的多個(gè)并發(fā)驅(qū)動(dòng)程序。
2、簡(jiǎn)單的小Demo入門(mén)JDBC
2.1、使用JDBC連接MySQL數(shù)據(jù)庫(kù)
連接JDBC需要的步驟為:
- 1、注冊(cè)驅(qū)動(dòng)(url,driver)
- 2、連接數(shù)據(jù)庫(kù)(connection,statement)
- 3、創(chuàng)建sql命令、執(zhí)行sql語(yǔ)句
- 4、將得到的數(shù)據(jù)進(jìn)行相關(guān)操作
- 5、關(guān)閉資源
運(yùn)行結(jié)果:
注意
MySQL 5.5.45+, 5.6.26+ and 5.7.6+版本默認(rèn)要求建立SSL連接
mysql8.0和之前版本的區(qū)別,首先驅(qū)動(dòng)換了,不是com.mysql.jdbc.Driver而是’com.mysql.cj.jdbc.Driver’,
此外mysql8.0是不需要建立ssl連接的,你需要顯示關(guān)閉。最后你需要設(shè)置時(shí)區(qū),比如設(shè)置CST。
實(shí)例: "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"
2.2、優(yōu)化第一個(gè)Demo
第一個(gè)例子里面,很多代碼是冗雜的,看起來(lái)很不友好。我們需要優(yōu)化。
新建一個(gè)工具類(lèi)如下:
全是靜態(tài)方法的工具類(lèi)
此時(shí)代碼優(yōu)化為:
package driver;import java.sql.*;/*** 學(xué)習(xí)如何用jdbc連接數(shù)據(jù)庫(kù)完成操作。,第二版本使用工具類(lèi)靜態(tài)方法優(yōu)化。*/public class Test_Driver2 {public static void main(String[]args) {Connection connection= null;Statement statement = null;ResultSet res = null;try{System.out.println("connect to database....");connection = JDBCUtils.getConnect();System.out.println("create statement...");statement = connection.createStatement();//3、選擇sql命令操作數(shù)據(jù)庫(kù)中的數(shù)據(jù)String sql;sql="select * from test_user";//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集res = statement.executeQuery(sql);//獲取結(jié)果集//5、從結(jié)果集里面獲取數(shù)據(jù)while(res.next()){int id = res.getInt("id");String name = res.getString("name");System.out.println(id+":"+name);}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils.free(connection,statement,res);}} }基于單例設(shè)計(jì)模式的優(yōu)化工具類(lèi)
package driver;import java.sql.*;/*** 前面使用的是靜態(tài)方法優(yōu)化的jdbc驅(qū)動(dòng)連接數(shù)據(jù)庫(kù)。* 下面使用單例設(shè)計(jì)模式,同時(shí)考慮以下線程安全問(wèn)題。*/public final class JDBCUtils1 {private String user = "root";private String password = "1234";//URL格式,每個(gè)數(shù)據(jù)庫(kù)都有自己的格式。一般為://JDBC:子協(xié)議:子名稱(chēng)://主機(jī)地址:端口/數(shù)據(jù)庫(kù)?其他設(shè)置//mysql沒(méi)有子名稱(chēng),如果是主機(jī)可以寫(xiě)為"JDBC:mysql:///test"//注意,mysql8.0需要加上設(shè)置時(shí)區(qū)。private String DB_URL = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";private static JDBCUtils1 JB=null;//使用懶漢式private JDBCUtils1(){}//構(gòu)造器私有public static JDBCUtils1 getInstance(){//懶漢式if(JB==null){synchronized (JDBCUtils1.class){if(JB==null){//防止多線程導(dǎo)致的問(wèn)題JB = new JDBCUtils1();//最先執(zhí)行靜態(tài)代碼塊}}}return JB;}static{//使用class.forName()方法一般用于靜態(tài)代碼塊,而且該方法注冊(cè)驅(qū)動(dòng)不依賴(lài)具體的類(lèi)庫(kù)try {//forName進(jìn)行類(lèi)的加載時(shí)優(yōu)先加載靜態(tài)代碼塊。Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}public Connection getConnect() throws SQLException {//異常應(yīng)該拋出return DriverManager.getConnection(DB_URL, user, password);}public void free(Connection conn, Statement st, ResultSet res) {try {if (res != null) //原則1:晚點(diǎn)連接早點(diǎn)釋放。原則2:先創(chuàng)建的后釋放res.close();} catch (SQLException e) {e.printStackTrace();} finally {if (st != null)try {st.close();} catch (SQLException e) {e.printStackTrace();} finally {if (conn != null)try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}} }2.3、 三種注冊(cè)JDBC驅(qū)動(dòng)的方式
-
Class.forName(“com.mysql.jdbc.Driver”);
-
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
-
System.setProperty(“jdbc.drivers”,”com.mysql.jdbc.Driver”);
第一種
通過(guò)Class類(lèi)方法加載對(duì)應(yīng)數(shù)據(jù)庫(kù)驅(qū)動(dòng)類(lèi)
利用了Driver源碼里面的靜態(tài)代碼塊構(gòu)建類(lèi)實(shí)例:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {//靜態(tài)代碼塊try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}} }第一種方法好處:通過(guò)Class把驅(qū)動(dòng)類(lèi)Driver先裝載到j(luò)ava的虛擬機(jī)中,加載過(guò)程中利用靜態(tài)代碼塊注冊(cè)驅(qū)動(dòng),好處在于能夠在編譯時(shí)不依賴(lài)于特定的JDBC Driver庫(kù),也就是減少了項(xiàng)目代碼的依賴(lài)性,而且也很容易改造成從配置文件讀取JDBC配置,從而可以在運(yùn)行時(shí)動(dòng)態(tài)更換數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)。
第二種方法
System.setProperty("JDBC:driver","com.mysql.cj.jdbc.Driver"); //2、連接數(shù)據(jù)庫(kù),創(chuàng)建connection對(duì)象和statement對(duì)象 System.out.println("connect to database...."); connection = DriverManager.getConnection(URL,user,password);通過(guò)系統(tǒng)的屬性設(shè)置System.setProperty(“jdbc.driver”,”com.mysql.jdbc.Driver”);
雖然也能夠在編譯時(shí)不依賴(lài)于特定的JDBC Driver庫(kù),也就是減少了項(xiàng)目代碼的依賴(lài)性,但是方法參數(shù)設(shè)置相對(duì)來(lái)說(shuō)較為復(fù)雜,特點(diǎn)是它可以設(shè)置多個(gè)驅(qū)動(dòng),所以在加載單個(gè)驅(qū)動(dòng)時(shí),一般采用第一種方法(Class.forName(…))。
第三種方法
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //2、連接數(shù)據(jù)庫(kù),創(chuàng)建connection對(duì)象和statement對(duì)象 System.out.println("connect to database...."); connection = DriverManager.getConnection(URL,user,password);看起來(lái)比較直觀的一種方式,注冊(cè)相應(yīng)的db的jdbc驅(qū)動(dòng),在編譯時(shí)需要導(dǎo)入對(duì)應(yīng)的驅(qū)動(dòng)包。它內(nèi)部有一個(gè)vector<driver>來(lái)存儲(chǔ)這些注冊(cè)的驅(qū)動(dòng)。要用的時(shí)候一個(gè)一個(gè)取出。這里創(chuàng)建單個(gè)driver,則Driver類(lèi)的靜態(tài)代碼塊會(huì)最先執(zhí)行,那么此時(shí)new Driver的時(shí)候就會(huì)注冊(cè)一次,然后外層registerDriver()方法又會(huì)注冊(cè)一次,所以注冊(cè)了兩次驅(qū)動(dòng)。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {//靜態(tài)代碼塊try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}} }它一定要有jdbc的驅(qū)動(dòng)類(lèi)才可以通過(guò)編譯,這樣做十分依賴(lài)Jar包,一旦jar包找不到,編譯時(shí)期就會(huì)報(bào)錯(cuò),并且為程序換數(shù)據(jù)庫(kù)會(huì)帶來(lái)麻煩,并且如上所說(shuō)加載了兩次驅(qū)動(dòng),雖然這并不影響我們程序,但是這樣做實(shí)在是沒(méi)有必要,還會(huì)影響程序的運(yùn)行。
小結(jié)
推薦使用第一種方法。Class.forName(驅(qū)動(dòng));
實(shí)際上去掉驅(qū)動(dòng)注冊(cè)的這三種方法,一樣可以運(yùn)行程序。
如圖,三種方式都注釋掉
運(yùn)行結(jié)果:
使用DEBUG模式,發(fā)現(xiàn)
一樣會(huì)去注冊(cè)Drive
2.4、使用JDBC實(shí)現(xiàn)增刪改查(CRUD)
1、Create()
用到的方法statement.executeUpdate(sql)
2、Read()
public static void read(){try{System.out.println("connect to database....");connection = JDBCUtils1.getInstance().getConnect();System.out.println("create statement...");statement = connection.createStatement();String sql;sql="select id,name from test_user where id>4";//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集res = statement.executeQuery(sql);//獲取結(jié)果集while(res.next()){int id = res.getInt("id");String name = res.getString("name");System.out.println(id+":"+name);}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils1.getInstance().free(connection,statement,res);}}3、Update()
public static void update(){try{System.out.println("connect to database....");connection = JDBCUtils1.getInstance().getConnect();System.out.println("create statement...");statement = connection.createStatement();String sql;sql="update test_user set name='王二蛋' where id>4 ";//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集int i = statement.executeUpdate(sql);//獲取影響的記錄條數(shù)System.out.println("i="+i);} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils1.getInstance().free(connection,statement,res);}}4、Delete()
public static void delete(){try{System.out.println("connect to database....");connection = JDBCUtils1.getInstance().getConnect();System.out.println("create statement...");statement = connection.createStatement();String sql;sql="delete from test_user where name='王二蛋'";//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集int i = statement.executeUpdate(sql);//獲取影響的記錄條數(shù)System.out.println("i="+i);} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils1.getInstance().free(connection,statement,res);}}2.5、了解JDBC中的SQL注入問(wèn)題
package CRUD_test;import driver.JDBCUtils1;import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;/*** 了解SQL注入問(wèn)題*/public class SQL_injection {private static Connection connection= null;private static Statement statement = null;private static ResultSet res = null;public static void main(String[]args){String name="小肥仔";read(name);}private static void read(String s) {try{System.out.println("connect to database....");connection = JDBCUtils1.getInstance().getConnect();System.out.println("create statement...");statement = connection.createStatement();String sql;sql="select id,name from test_user where name="+"'"+s+"'";System.out.println(sql);//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集res = statement.executeQuery(sql);//獲取結(jié)果集while(res.next()){int id = res.getInt("id");String name = res.getString("name");System.out.println(id+":"+name);}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils1.getInstance().free(connection,statement,res);}} }
修改:
運(yùn)行程序,結(jié)果:
并沒(méi)有名字叫'or 1 or '的記錄,卻查出了4條。發(fā)生了SQL注入問(wèn)題,原因就是輸入的' or 1 or'干擾了命令的匹配。SQL語(yǔ)句里面or是一個(gè)關(guān)鍵字,1表示結(jié)果為真,也就是全都符合。與實(shí)際上我們想要實(shí)現(xiàn)的功能不符合。原因:我們沒(méi)有明確statement的使用場(chǎng)景。
| Statement | 用于對(duì)數(shù)據(jù)庫(kù)進(jìn)行通用訪問(wèn),在運(yùn)行時(shí)使用靜態(tài)SQL語(yǔ)句時(shí)很有用。 Statement接口不能接受參數(shù)。 |
| PreparedStatement | 當(dāng)計(jì)劃要多次使用SQL語(yǔ)句時(shí)使用。PreparedStatement接口在運(yùn)行時(shí)接受輸入?yún)?shù)。 |
| CallableStatement | 當(dāng)想要訪問(wèn)數(shù)據(jù)庫(kù)存儲(chǔ)過(guò)程時(shí)使用。CallableStatement接口也可以接受運(yùn)行時(shí)輸入?yún)?shù)。 |
所以我們可以使用PreparedStatement來(lái)改進(jìn)程序:
public static void main(String[]args){String name="' or 1 or '";read(name);}private static void read(String s) {try{System.out.println("connect to database....");connection = JDBCUtils1.getInstance().getConnect();System.out.println("create statement...");String sql;sql="select id,name from test_user where name=?";//使用?號(hào)表示未知參數(shù)System.out.println(sql);//4、執(zhí)行sql語(yǔ)句,獲取結(jié)果集preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1,s);//將第一個(gè)問(wèn)號(hào)參數(shù)設(shè)置為sres= preparedStatement.executeQuery();//執(zhí)行,不用sql,如果填入sql,則之前的設(shè)置失效while(res.next()){int id = res.getInt("id");String name = res.getString("name");System.out.println(id+":"+name);}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils1.getInstance().free(connection,preparedStatement,res);}}一樣的輸入,卻沒(méi)有報(bào)錯(cuò)了。
注意:res= preparedStatement.executeQuery(sql);//執(zhí)行,不用sql,如果填入sql,則之前的設(shè)置失效
對(duì)比Statement和PreparedStatement
SQL注入問(wèn)題以往的防御方式
以前對(duì)付這種漏洞的方式主要有三種:
-
字符串檢測(cè):限定內(nèi)容只能由英文、數(shù)字等常規(guī)字符,如果檢查到用戶輸入有特殊字符,直接拒絕。但缺點(diǎn)是,系統(tǒng)中不可避免地會(huì)有些內(nèi)容包含特殊字符,這時(shí)候總不能拒絕入庫(kù)。
-
字符串替換:把危險(xiǎn)字符替換成其他字符,缺點(diǎn)是危險(xiǎn)字符可能有很多,一一枚舉替換相當(dāng)麻煩,也可能有漏網(wǎng)之魚(yú)。
-
存儲(chǔ)過(guò)程:把參數(shù)傳到存儲(chǔ)過(guò)程進(jìn)行處理,但并不是所有數(shù)據(jù)庫(kù)都支持存儲(chǔ)過(guò)程。如果存儲(chǔ)過(guò)程中執(zhí)行的命令也是通過(guò)拼接字符串出來(lái)的,還是會(huì)有漏洞。
現(xiàn)在的辦法就是參數(shù)化查詢(xún)
近年來(lái),自從參數(shù)化查詢(xún)出現(xiàn)后,SQL注入漏洞已成明日黃花。
參數(shù)化查詢(xún)(Parameterized Query 或 Parameterized Statement)是訪問(wèn)數(shù)據(jù)庫(kù)時(shí),在需要填入數(shù)值或數(shù)據(jù)的地方,使用參數(shù) (Parameter) 來(lái)給值。
在使用參數(shù)化查詢(xún)的情況下,數(shù)據(jù)庫(kù)服務(wù)器不會(huì)將參數(shù)的內(nèi)容視為SQL指令的一部份來(lái)處理,而是在數(shù)據(jù)庫(kù)完成SQL指令的編譯后,才套用參數(shù)運(yùn)行,因此就算參數(shù)中含有指令,也不會(huì)被數(shù)據(jù)庫(kù)運(yùn)行。Access、SQL Server、MySQL、SQLite等常用數(shù)據(jù)庫(kù)都支持參數(shù)化查詢(xún)。
總結(jié)
以上是生活随笔為你收集整理的JDBC实战(一)JDBC概述的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java数字不等于_java – 仅使用
- 下一篇: python安装caffe_Caffe安