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