1. 前言
在前面我們學習MySQL數據庫時,都是利用圖形化客戶端工具(如:idea、datagrip),來操作數據庫的。
我們做為后端程序開發人員,通常會使用Java程序來完成對數據庫的操作。Java程序操作數據庫的技術呢,有很多啊,而最為底層、最為基礎的就是JDBC。
JDBC:(Java DataBase Connectivity),就是使用Java語言操作關系型數據庫的一套API。 【是操作數據庫最為基礎、底層的技術】
但是使用JDBC來操作數據庫,會比較繁瑣,所以現在在企業項目開發中呢,一般都會使用基于JDBC的封裝的高級框架,比如:Mybatis、MybatisPlus、Hibernate、SpringDataJPA。
而這些技術,目前的市場占有份額如下圖所示:
從上圖中,我們也可以看到,目前最為主流的就是Mybatis,其次是MybatisPlus。
所以,在我們的課程體系中呢,這兩種主流的操作數據庫的框架我們都要學習。 而我們在學習這兩個主流的框架之前,還需要學習一下操作數據庫的基礎基礎 JDBC。 然后接下來,再來學習Mybatis。 而在我們后面的課程中,我們還要學習MybatisPlus框架。 那么今天呢,我們就先來學習 JDBC 和 Mybatis。
今天課程安排:
- JDBC
- Mybatis
- SpringBoot配置文件
2. JDBC
2.1 介紹
JDBC:(Java DataBase Connectivity),就是使用Java語言操作關系型數據庫的一套API。
本質:
- sun公司官方定義的一套操作所有關系型數據庫的規范,即接口。
- 各個數據庫廠商去實現這套接口,提供數據庫驅動jar包。
- 我們可以使用這套接口(JDBC)編程,真正執行的代碼是驅動jar包中的實現類。
那有了JDBC之后,我們就可以直接在java代碼中來操作數據庫了,只需要編寫這樣一段java代碼,就可以來操作數據庫中的數據。 示例代碼如下:
2.2 查詢數據
2.2.1 需求
需求:基于JDBC實現用戶登錄功能。
本質:其本質呢,其實就是基于JDBC程序,執行如下select語句,并將查詢的結果輸出到控制臺。SQL語句:
select * from user where username = 'linchong' and password = '123456';
2.2.2 準備工作
1). 創建一個maven項目
2). 創建一個數據庫 web,并在該數據庫中創建user表
create table user(id int unsigned primary key auto_increment comment 'ID,主鍵',username varchar(20) comment '用戶名',password varchar(32) comment '密碼',name varchar(10) comment '姓名',age tinyint unsigned comment '年齡'
) comment '用戶表';insert into user(id, username, password, name, age) values (1, 'daqiao', '123456', '大喬', 22),(2, 'xiaoqiao', '123456', '小喬', 18),(3, 'diaochan', '123456', '貂蟬', 24),(4, 'lvbu', '123456', '呂布', 28),(5, 'zhaoyun', '12345678', '趙云', 27);
2.2.3 代碼實現
AI提示詞(prompt):
你是一名java開發工程師,幫我基于JDBC程序來操作數據庫,執行如下SQL語句:
select * from user where username = 'daqiao' and password = '123456'
具體的代碼為:
1). 在 pom.xml 文件中引入依賴
<dependencies><!-- MySQL JDBC driver --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.3</version><scope>test</scope></dependency>
</dependencies>
2). 在 src/main/test/java
目錄下編寫測試類,定義測試方法
public class JDBCTest {/*** 編寫JDBC程序, 查詢數據*/@Testpublic void testJdbc() throws Exception {// 獲取連接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");// 創建預編譯的PreparedStatement對象PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");// 設置參數pstmt.setString(1, "daqiao"); // 第一個問號對應的參數pstmt.setString(2, "123456"); // 第二個問號對應的參數// 執行查詢ResultSet rs = pstmt.executeQuery();// 處理結果集while (rs.next()) {int id = rs.getInt("id");String uName = rs.getString("username");String pwd = rs.getString("password");String name = rs.getString("name");int age = rs.getInt("age");System.out.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);}// 關閉資源rs.close();pstmt.close();conn.close();}}
而上述的單元測試中,我們在SQL語句中,將將 用戶名 和密碼的值都寫死了,而這兩個值應該是動態的,是將來頁面傳遞到服務端的。 那么,我們可以基于前面所講解的JUnit中的參數化測試進行單元測試,代碼改造如下:
版本1:
@Test
public void testSelect() throws Exception {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {String url = "jdbc:mysql://localhost:3306/web01";String username = "root";String password = "fu921400521";Class.forName("com.mysql.cj.jdbc.Driver");connection = DriverManager.getConnection(url, username, password);preparedStatement = connection.prepareStatement("select * from user where username = ? and password = ?");preparedStatement.setString(1, "daqiao");preparedStatement.setString(2, "123456");resultSet = preparedStatement.executeQuery();while (resultSet.next()) {User user = new User(resultSet.getInt("id"),resultSet.getString("username"),resultSet.getString("password"),resultSet.getString("name"),resultSet.getInt("age"));System.out.println(user);}} catch (SQLException se) {se.printStackTrace();} catch (Exception e) {e.printStackTrace();}finally {try {if (resultSet != null) resultSet.close();if (preparedStatement != null) preparedStatement.close();if (connection != null) connection.close();}catch (SQLException se) {se.printStackTrace();}}}
版本2:
public class JDBCTest {/*** 編寫JDBC程序, 查詢數據*/@ParameterizedTest@CsvSource({"daqiao,123456"})public void testJdbc(String _username, String _password) throws Exception {// 獲取連接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");// 創建預編譯的PreparedStatement對象PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");// 設置參數pstmt.setString(1, _username); // 第一個問號對應的參數pstmt.setString(2, _password); // 第二個問號對應的參數// 執行查詢ResultSet rs = pstmt.executeQuery();// 處理結果集while (rs.next()) {int id = rs.getInt("id");String uName = rs.getString("username");String pwd = rs.getString("password");String name = rs.getString("name");int age = rs.getInt("age");System.out.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);}// 關閉資源rs.close();pstmt.close();conn.close();}}
如果在測試時,需要傳遞一組參數,可以使用 @CsvSource
注解。
2.2.4 代碼剖析
2.2.4.1 ResultSet
ResultSet(結果集對象):封裝了DQL查詢語句查詢的結果。
- next():將光標從當前位置向前移動一行,并判斷當前行是否為有效行,返回值為boolean。
-
- true:有效行,當前行有數據
- false:無效行,當前行沒有數據
- getXxx(…):獲取數據,可以根據列的編號獲取,也可以根據列名獲取(推薦)。
結果解析步驟:
2.2.4.2 預編譯SQL
其實我們在編寫SQL語句的時候,有兩種風格:
- 靜態SQL(參數硬編碼)
conn.prepareStatement("SELECT * FROM user WHERE username = 'daqiao' AND password = '123456'");
ResultSet resultSet = pstmt.executeQuery();
這種呢,就是參數值,直接拼接在SQL語句中,參數值是寫死的。
- 預編譯SQL(參數動態傳遞)
conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");
pstmt.setString(1, "daqiao");
pstmt.setString(2, "123456");
ResultSet resultSet = pstmt.executeQuery();
這種呢,并未將參數值在SQL語句中寫死,而是使用 ? 進行占位,然后再指定每一個占位符對應的值是多少,而最終在執行SQL語句的時候,程序會將SQL語句(SELECT * FROM user WHERE username = ? AND password = ?),以及參數值("daqiao", "123456")都發送給數據庫,然后在執行的時候,會使用參數值,將?占位符替換掉。
那這種預編譯的SQL,也是在項目開發中推薦使用的SQL語句。主要的作用有兩個:
- 防止SQL注入
- 性能更高
那接下來,我們就來介紹一下這兩點。
2.2.4.2.1 SQL注入
- SQL注入:通過控制輸入來修改事先定義好的SQL語句,以達到執行代碼對服務器進行攻擊的方法。
SQL注入最典型的場景,就是用戶登錄功能。
注入演示:
1). 打開課程資料中的文件夾 資料/02. SQL注入演示
,運行其中的jar包 sql_Injection_demo-0.0.1-SNAPSHOT.jar
,進入該目錄后,執行命令:
java -jar sql_Injection_demo-0.0.1-SNAPSHOT.jar
2). 打開瀏覽器訪問 http://localhost:9090/ ,必須登錄后才能訪問到系統。我們先測試正常的用戶名和密碼
3). 接下來,我們再來測試一下錯誤的用戶名和密碼 。
我們看到,如果用戶名密碼錯誤,是不能進入到系統中進行訪問的,會提示 用戶名和密碼錯誤
。
4). 那接下來,我們就要演示一下SQL注入現象,我們可以通過控制表單輸入,來修改事先定義好的SQL語句的含義。 從而來攻擊服務器。
點擊登錄后,我們看到居然可以成功進入到系統中。
為什么會出現這種現象呢?
在進行登錄操作時,怎么樣才算登錄成功呢? 如果我們查詢到了數據,就說明用戶名密碼是對的。 如果沒有查詢到數據,就說明用戶名或密碼錯誤。
而出現上述現象,原因就是因為,我們我們編寫的SQL語句是基于字符串進行拼接的 。 我們輸入的用戶名無所謂,比如:shfhsjfhja
,而密碼呢,就是我們精心設計的,如:' or '1' = '1
。
那最終拼接的SQL語句,如下所示:
我們知道,or
連接的條件,是或的關系,兩者滿足其一就可以。 所以,雖然用戶名密碼輸入錯誤,也是可以查詢返回結果的,而只要查詢到了數據,就說明用戶名和密碼是正確的。
2.2.4.2.2 SQL注入解決
而通過預編譯SQL(select * from user where username = ? and password = ?),就可以直接解決上述SQL注入的問題。 接下來,我們再來演示一下,通過預編譯SQL是否能夠解決SQL注入問題。
1). 打開課程資料中的文件夾 資料/02. SQL注入演示
,運行其中的jar包 sql_prepared_demo-0.0.1-SNAPSHOT.jar
,進入該目錄后,執行命令:
java -jar sql_prepared_demo-0.0.1-SNAPSHOT.jar
2). 打開瀏覽器訪問 http://localhost:9090/
,必須登錄后才能訪問到系統 。我們先測試正常的用戶名和密碼
3). 那接下來,我們就要演示一下是否可以基于上述的密碼 ' or '1' = '1
,來完成SQL注入 。
通過控制臺,可以看到輸入的SQL語句,是預編譯SQL語句。
而在預編譯SQL語句中,當我們執行的時候,會把整個' or '1'='1
作為一個完整的參數,賦值給第2個問號(' or '1'='1
進行了轉義,只當做字符串使用)
那么此時再查詢時,就查詢不到對應的數據了,登錄失敗。
注意:在以后的項目開發中,我們使用的基本全部都是預編譯SQL語句。
2.2.4.2.3 性能更高
2.3 增刪改數據
2.3.1 需求
- 需求:基于JDBC程序,執行如下update語句。
- SQL:
update user set password = '123456', gender = 2 where id = 1;
2.3.2 代碼實現
AI提示詞(prompt):
你是一名java開發工程師,幫我基于JDBC程序來操作數據庫,執行如下SQL語句:
update user set password = '123456', gender = 2 where id = 1;
代碼實現如下:
@ParameterizedTest
@CsvSource({"1,123456,25"})
public void testUpdate(int userId, String newPassword, int newAge) throws Exception {// 建立數據庫連接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");// SQL 更新語句String sql = "UPDATE user SET password = ?, age = ? WHERE id = ?";// 創建預編譯的PreparedStatement對象PreparedStatement pstmt = conn.prepareStatement(sql);// 設置參數pstmt.setString(1, newPassword); // 第一個問號對應的參數pstmt.setInt(2, newAge); // 第二個問號對應的參數pstmt.setInt(3, userId); // 第三個問號對應的參數// 執行更新int rowsUpdated = pstmt.executeUpdate();// 輸出結果System.out.println(rowsUpdated + " row(s) updated.");// 關閉資源pstmt.close();conn.close();
}
- JDBC程序執行DML語句:int rowsUpdated = pstmt.executeUpdate(); //返回值是影響的記錄數
- JDBC程序執行DQL語句:ResultSet resultSet = pstmt.executeQuery(); //返回值是查詢結果集