資料排序:選擇排序和氣泡排序
JDBC
1. JDBC 概述
1.1 客戶端操作資料庫的方式
- 1) 方式1: 使用第三方客戶端來訪問 MySQL:SQLyog
- 2) 方式2: 使用命令列3) 我們今天要學習的是通過 Java程式 來訪問 MySQL 資料庫
1.2 什麼是JDBC
JDBC(Java Data Base Connectivity) 是 Java 訪問資料庫的標準規範.是一種用於執行SQL語句的Java API,可以為
多種關係資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成。是Java訪問資料庫的標準規範.
1.3 JDBC 原理
JDBC是介面,驅動是介面的實現,沒有驅動將無法完成資料庫連線,從而不能操作資料庫!每個資料庫廠商都需
要提供自己的驅動,用來連線自己公司的資料庫,也就是說驅動一般都由資料庫生成廠商提供。
總結:
JDBC就是由sun公司定義的一套操作所有關係型資料庫的規則(介面),而資料庫廠商需要實現這套介面,提供資料庫
驅動jar包, 我們可以使用這套介面程式設計,真正執行的程式碼是對應驅動包中的實現類。
2. JDBC 開發
2.1 資料準備
-- 建立 jdbc_user表 CREATE TABLE jdbc_user ( id INT PRIMARY KEY AUTO_INCREMENT , username VARCHAR(50), PASSWORD VARCHAR(50), birthday DATE ); -- 新增資料 INSERT INTO jdbc_user (username, PASSWORD,birthday)VALUES('admin1', '123','1991/12/24'), ('admin2','123','1995/12/24'), ('test1', '123','1998/12/24'), ('test2', '123','2000/12/24');
2.2 MySql驅動包
1. 將MySQL驅動包新增到jar包庫資料夾中,Myjar資料夾,用於存放當前專案需要的所有jar包
2. 在 idea中 配置jar包庫的位置
3. 建立一個新的專案jdbc_task01, 配置jar包庫
2.3 API使用: 1.註冊驅動
- JDBC規範定義驅動介面: java.sql.Driver
- MySql驅動包提供了實現類: com.mysql.jdbc.Driver
1) 程式碼示例
public class JDBCDemo01 { public static void main(String[] args) throws ClassNotFoundException { //1.註冊驅動 // forName 方法執行將類進行初始化 Class.forName("com.mysql.jdbc.Driver"); } }
2) 為什麼這樣可以註冊驅動?
我們知道 Class類的forName方法 ,可以將一個類初始化, 現在我們一起Driver類的 看一下原始碼
// Driver類是MySql提供的資料庫驅動類, 實現了JDBC的Driver介面 java.sql.Driver public class Driver extends NonRegisteringDriver implements java.sql.Driver { // 空參構造 public Driver() throws SQLException {} //靜態程式碼塊,Class類的 forName()方法將Driver類 載入到記憶體, static程式碼塊會自動執行 static { try { /* DriverManager 驅動管理類 registerDriver(new Driver) 註冊驅動的方法 註冊資料庫驅動 */ DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
}
注:
- JDBC3 開始,目前已經普遍使用的版本。可以不用註冊驅動而直接使用。 Class.forName 這句話可以省略。
2.4 API使用: 2.獲得連線
- Connection 介面,代表一個連線物件 ,具體的實現類由資料庫的廠商實現
- 使用 DriverManager類的靜態方法,getConnection可以獲取資料庫的連線
1) getConnection方法 3個 連線引數說明
2) 對URL的詳細說明
jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
- JDBC規定url的格式由三部分組成,每個部分中間使用冒號分隔。
- 第一部分是協議 jdbc,這是固定的;
- 第二部分是子協議,就是資料庫名稱,連線mysql資料庫,第二部分當然是mysql了;
- 第三部分是由資料庫廠商規定的,我們需要了解每個資料庫廠商的要求,mysql的第三部分分別由資料庫伺服器的IP地址(localhost)、埠號(3306),以及要使用的 資料庫名稱 組成。
3) 程式碼示例
public class JDBCDemo02 { public static void main(String[] args) throws Exception { //1.註冊驅動 Class.forName("com.mysql.jdbc.Driver"); //2.獲取連線 url,使用者名稱, 密碼 String url = "jdbc:mysql://localhost:3306/db4"; Connection con = DriverManager.getConnection(url, "root", "123456"); //com.mysql.jdbc.JDBC4Connection@2e3fc542 System.out.println(con); } }
2.5 API 使用: 3.獲取語句執行平臺
- Connection介面中的方法說明
- Statement createStatement() 建立 SQL語句執行物件
程式碼示例
public class JDBCDemo03 { public static void main(String[] args) throws Exception { //1.註冊驅動 Class.forName("com.mysql.jdbc.Driver"); //2.獲取連線 url,使用者名稱, 密碼 String url = "jdbc:mysql://localhost:3306/db4"; Connection con = DriverManager.getConnection(url, "root", "123456"); //3.獲取 Statement物件 Statement statement = con.createStatement(); //4.執行建立表操作 String sql = "create table test01(id int, name varchar(20),age int);"; //5.增刪改操作 使用executeUpdate,增加一張表 int i = statement.executeUpdate(sql); //6.返回值是受影響的函式 System.out.println(i); //7.關閉流 statement.close(); con.close(); } }
2.6 API 使用: 4.處理結果集
- 只有在進行查詢操作的時候, 才會處理結果集
程式碼示例
public class JDBCDemo04 { public static void main(String[] args) throws SQLException { //1.註冊驅動 可以省略 //2.獲取連線 String url = "jdbc:mysql://localhost:3306/db4"; Connection con = DriverManager.getConnection(url, "root", "123456"); //3.獲取 Statement物件 Statement statement = con.createStatement(); String sql = "select * from jdbc_user"; //執行查詢操作,返回的是一個 ResultSet 結果物件 ResultSet resultSet = statement.executeQuery(sql); //4.處理結果集 resultSet } }
2.6.1 ResultSet介面
- 作用:封裝資料庫查詢的結果集,對結果集進行遍歷,取出每一條記錄。
程式碼示例
public class JDBCDemo04 { public static void main(String[] args) throws SQLException { //1.註冊驅動 可以省略 //2.獲取連線 String url = "jdbc:mysql://localhost:3306/db4"; Connection con = DriverManager.getConnection(url, "root", "123456"); //3.獲取 Statement物件 Statement statement = con.createStatement(); String sql = "select * from jdbc_user"; //執行查詢操作,返回的是一個 ResultSet 結果物件 ResultSet resultSet = statement.executeQuery(sql); //4.處理結果集 // //next 方法判斷是否還有下一條資料 // boolean next = resultSet.next(); // System.out.println(next); // // //getXXX 方法獲取資料 兩種方式 // int id = resultSet.getInt("id");//列名 // System.out.println(id); // // int anInt = resultSet.getInt(1);//列號 // System.out.println(anInt); //使用while迴圈 while(resultSet.next()){ //獲取id int id = resultSet.getInt("id"); //獲取姓名 String username = resultSet.getString("username"); //獲取生日 Date birthday = resultSet.getDate("birthday"); System.out.println(id + " = " +username + " : " + birthday); } //關閉連線 resultSet.close(); statement.close(); con.close(); } }
2.7 API 使用: 5.釋放資源
- 1) 需要釋放的物件:ResultSet 結果集,Statement 語句,Connection 連線
- 2) 釋放原則:先開的後關,後開的先關。ResultSet ==> Statement ==> Connection
- 3) 放在哪個程式碼塊中:finally 塊
- 與IO流一樣,使用後的東西都需要關閉!關閉的順序是先開後關, 先得到的後關閉,後得到的先關閉
程式碼示例
public class JDBCDemo05 { public static void main(String[] args) { Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { //1.註冊驅動(省略) //2.獲取連線 String url = "jdbc:mysql://localhost:3306/db4"; connection = DriverManager.getConnection(url, "root", "123456"); //3.獲取 Statement物件 statement = connection.createStatement(); String sql = "select * from jdbc_user"; resultSet = statement.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } finally{ /** * 開啟順序: connection ==> statement => resultSet * 關閉順序: resultSet ==> statement ==> connection */ try { connection.close(); resultSet.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
2.8 步驟總結
- 1. 獲取驅動(可以省略)
- 2. 獲取連線
- 3. 獲取Statement物件
- 4. 處理結果集(只在查詢時處理)
- 5. 釋放資源
3. JDBC實現增刪改查
3.1 JDBC工具類
- 什麼時候自己建立工具類?
- 如果一個功能經常要用到,我們建議把這個功能做成一個工具類,可以在不同的地方重用。
- “獲得資料庫連線”操作,將在以後的增刪改查所有功能中都存在,可以封裝工具類JDBCUtils。提供獲取連線物件的方法,從而達到程式碼的重複利用。
- 工具類包含的內容
- 1) 可以把幾個字串定義成常量:使用者名稱,密碼,URL,驅動類
- 2) 得到資料庫的連線:getConnection()
- 3) 關閉所有開啟的資源:
程式碼示例
/** * JDBC 工具類 */ public class JDBCUtils { //1. 定義字串常量, 記錄獲取連線所需要的資訊 public static final String DRIVERNAME = "com.mysql.jdbc.Driver"; public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8"; public static final String USER = "root"; public static final String PASSWORD = "123456"; //2. 靜態程式碼塊, 隨著類的載入而載入 static{ try { //註冊驅動 Class.forName(DRIVERNAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
//3.獲取連線的靜態方法 public static Connection getConnection(){ try { //獲取連線物件 Connection connection = DriverManager.getConnection(URL, USER, PASSWORD); //返回連線物件 return connection; } catch (SQLException e) { e.printStackTrace(); return null; } }
//關閉資源的方法 public static void close(Connection con, Statement st){ if(con != null && st != null){ try { st.close(); con.close(); } catch (SQLException e) { e.printStackTrace(); } } }
public static void close(Connection con, Statement st, ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } close(con,st); } }
3.2 DML操作
3.2.1 插入記錄
解決插入中文亂碼問題.
jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8 characterEncoding=UTF-8 指定字元的編碼、解碼格式。
程式碼示例
/** * 插入資料 * @throws SQLException */ @Test public void testInsert() throws SQLException { //1.通過工具類獲取連線 Connection connection = JDBCUtils.getConnection(); //2.獲取Statement Statement statement = connection.createStatement(); //2.1 編寫Sql String sql = "insert into jdbc_user values(null,'張百萬','123','2020/1/1')"; //2.2 執行Sql int i = statement.executeUpdate(sql); System.out.println(i); //3.關閉流 JDBCUtils.close(connection,statement); }
3.2.2 更新記錄
- 根據ID 需改使用者名稱稱
/** * 修改 id 為1 的使用者名稱為 廣坤 */ @Test public void testUpdate() throws SQLException { Connection connection = JDBCUtils.getConnection(); Statement statement = connection.createStatement(); String sql = "update jdbc_user set username = '廣坤' where id = 1"; statement.executeUpdate(sql); JDBCUtils.close(connection,statement); }
3.2.3 刪除記錄
- 刪除id為 3 和 4 的記錄
/** * 刪除id 為 3 和 4的記錄 * @throws SQLException */ @Test public void testDelete() throws SQLException { Connection connection = JDBCUtils.getConnection(); Statement statement = connection.createStatement(); statement.executeUpdate("delete from jdbc_user where id in(3,4)"); JDBCUtils.close(connection,statement); }
3.3 DQL操作
3.3.1 查詢姓名為張百萬的一條記錄
public class TestJDBC02 { public static void main(String[] args) throws SQLException { //1.獲取連線物件 Connection connection = JDBCUtils.getConnection(); //2.獲取Statement物件 Statement statement = connection.createStatement(); String sql = "SELECT * FROM jdbc_user WHERE username = '張百萬';"; ResultSet resultSet = statement.executeQuery(sql); //3.處理結果集 while(resultSet.next()){ //通過列名 獲取欄位資訊 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String birthday = resultSet.getString("birthday"); System.out.println(id+" "+username+" " + password +" " + birthday); } //4.釋放資源 JDBCUtils.close(connection,statement,resultSet); } }
4. SQL注入問題
4.1 Sql注入演示
1) 向jdbc_user表中 插入兩條資料
# 插入2條資料 INSERT INTO jdbc_user VALUES(NULL,'jack','123456','2020/2/24'); INSERT INTO jdbc_user VALUES(NULL,'tom','123456','2020/2/24');
2) SQL注入演示
# SQL注入演示 -- 填寫一個錯誤的密碼 SELECT * FROM jdbc_user WHERE username = 'tom' AND PASSWORD = '123' OR '1' = '1';
如果這是一個登陸操作,那麼使用者就登陸成功了.顯然這不是我們想要看到的結果
4.2 sql注入案例:使用者登陸
- 需求
- 使用者在控制檯上輸入使用者名稱和密碼, 然後使用 Statement 字串拼接的方式 實現使用者的登入。
- 步驟
- 1) 得到使用者從控制檯上輸入的使用者名稱和密碼來查詢資料庫
- 2) 寫一個登入的方法
- a) 通過工具類得到連線
- b) 建立語句物件,使用拼接字串的方式生成 SQL 語句
- c) 查詢資料庫,如果有記錄則表示登入成功,否則登入失敗
- d) 釋放資源
-
Sql注入方式: 123' or '1'=’1
程式碼示例
public class TestLogin01 { /** * 使用者登入案例 * 使用 Statement字串拼接的方式完成查詢 * @param args */ public static void main(String[] args) throws SQLException { //1.獲取連線 Connection connection = JDBCUtils.getConnection(); //2.獲取Statement Statement statement = connection.createStatement(); //3.獲取使用者輸入的使用者名稱和密碼 Scanner sc = new Scanner(System.in); System.out.println("請輸入使用者名稱: "); String name = sc.nextLine(); System.out.println("請輸入密碼: "); String pass = sc.nextLine(); System.out.println(pass); //4.拼接Sql,執行查詢 String sql = "select * from jdbc_user " + "where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'"; System.out.println(sql); ResultSet resultSet = statement.executeQuery(sql); //5.處理結果集,判斷結果集是否為空 if(resultSet.next()){ System.out.println("登入成功! 歡迎您: " + name); }else { System.out.println("登入失敗!"); } //釋放資源 JDBCUtils.close(connection,statement,resultSet); } }
4.3 問題分析
1) 什麼是SQL注入?
我們讓使用者輸入的密碼和 SQL 語句進行字串拼接。使用者輸入的內容作為了 SQL 語句語法的一部分,改變了 原有
SQL 真正的意義,以上問題稱為 SQL 注入 .
2) 如何實現的注入
- 根據使用者輸入的資料,拼接處的字串
-
select * from jdbc_user where username = 'abc' and password = 'abc' or '1'='1' name='abc' and password='abc' 為假 '1'='1' 真 相當於 select * from user where true=true; 查詢了所有記錄
3) 如何解決
要解決 SQL 注入就不能讓使用者輸入的密碼和我們的 SQL 語句進 行簡單的字串拼接。
5. 預處理物件
5.1 PreparedStatement 介面介紹
- PreparedStatement 是 Statement 介面的子介面,繼承於父介面中所有的方法。它是一個預編譯的 SQL 語句物件.
- 預編譯: 是指SQL 語句被預編譯,並存儲在 PreparedStatement 物件中。然後可以使用此物件多次高效地執行該語句。
5.2 PreparedStatement 特點
- 因為有預先編譯的功能,提高 SQL 的執行效率。
- 可以有效的防止 SQL 注入的問題,安全性更高
5.3 獲取PreparedStatement物件
- 通過Connection建立PreparedStatement物件
5.4 PreparedStatement介面常用方法
5.5 使用PreparedStatement的步驟
1) 編寫 SQL 語句,未知內容使用?佔位:
"SELECT * FROM jdbc_user WHERE username=? AND password=?";
2) 獲得 PreparedStatement 物件 3) 設定實際引數:setXxx( 佔位符的位置, 真實的值) 4) 執行引數化 SQL 語句 5)
關閉資源
5.6 使用PreparedStatement完成登入案例
- 使用 PreparedStatement 預處理物件,可以有效的避免SQL注入
- 步驟:
- 1.獲取資料庫連線物件
- 2.編寫SQL 使用? 佔位符方式3.獲取預處理物件 (預編譯物件會將Sql傳送給資料庫 進
- 行預編譯)
- 4.提示使用者輸入使用者名稱 & 密碼
- 5.設定實際引數:setXxx(佔位符的位置, 真實的值)
- 6.執行查詢獲取結果集
- 7.判斷是否查詢到資料
- 8.關閉資源
public class TestLogin02 { /** * 使用預編譯物件 PrepareStatement 完成登入案例 * @param args * @throws SQLException */ public static void main(String[] args) throws SQLException { //1.獲取連線 Connection connection = JDBCUtils.getConnection(); //2.獲取Statement Statement statement = connection.createStatement(); //3.獲取使用者輸入的使用者名稱和密碼 Scanner sc = new Scanner(System.in); System.out.println("請輸入使用者名稱: "); String name = sc.nextLine(); System.out.println("請輸入密碼: "); String pass = sc.nextLine(); System.out.println(pass); //4.獲取 PrepareStatement 預編譯物件 //4.1 編寫SQL 使用 ? 佔位符方式 String sql = "select * from jdbc_user where username = ? and password = ?"; PreparedStatement ps = connection.prepareStatement(sql); //4.2 設定佔位符引數 ps.setString(1,name); ps.setString(2,pass); //5. 執行查詢 處理結果集 ResultSet resultSet = ps.executeQuery(); if(resultSet.next()){ System.out.println("登入成功! 歡迎您: " + name); }else{ System.out.println("登入失敗!"); } //6.釋放資源 JDBCUtils.close(connection,statement,resultSet); } }
5.7 PreparedStatement的執行原理
- 分別使用 Statement物件 和 PreparedStatement物件進行插入操作
程式碼示例
public class TestPS { public static void main(String[] args) throws SQLException { Connection con = JDBCUtils.getConnection(); //獲取 Sql語句執行物件 Statement st = con.createStatement(); //插入兩條資料 st.executeUpdate("insert into jdbc_user values(null,'張三','123','1992/12/26')"); st.executeUpdate("insert into jdbc_user values(null,'李四','123','1992/12/26')"); //獲取預處理物件 PreparedStatement ps = con.prepareStatement("insert into jdbc_user values(?,?,?,?)"); //第一條數 設定佔位符對應的引數 ps.setString(1,null); ps.setString(2,"長海"); ps.setString(3,"qwer"); ps.setString(4,"1990/1/10"); //執行插入 ps.executeUpdate(); //第二條資料 ps.setString(1,null); ps.setString(2,"小斌"); ps.setString(3,"1122"); ps.setString(4,"1990/1/10"); //執行插入 ps.executeUpdate(); //釋放資源 st.close(); ps.close(); con.close(); } }
5.8 Statement 與 PreparedStatement的區別?
- 1. Statement用於執行靜態SQL語句,在執行時,必須指定一個事先準備好的SQL語句。
- 2. PrepareStatement是預編譯的SQL語句物件,語句中可以包含動態引數“?”,在執行時可以為“?”動態設定引數值。
- 3. PrepareStatement可以減少編譯次數提高資料庫效能。
6. JDBC 控制事務
- 之前我們是使用 MySQL 的命令來操作事務。接下來我們使用 JDBC 來操作銀行轉賬的事務。
6.1 資料準備
-- 建立賬戶表 CREATE TABLE account( -- 主鍵 id INT PRIMARY KEY AUTO_INCREMENT, -- 姓名 NAME VARCHAR(10), -- 轉賬金額 money DOUBLE ); -- 新增兩個使用者 INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);
6.2 事務相關API
我們使用 Connection中的方法實現事務管理
6.3 開發步驟
- 1. 獲取連線
- 2. 開啟事務
- 3. 獲取到 PreparedStatement , 執行兩次更新操作
- 4. 正常情況下提交事務
- 5. 出現異常回滾事務
- 6. 最後關閉資源
6.4 程式碼示例
public class JDBCTransaction { //JDBC 操作事務 public static void main(String[] args) { Connection con = null; PreparedStatement ps = null; try { //1. 獲取連線 con = JDBCUtils.getConnection(); //2. 開啟事務 con.setAutoCommit(false); //3. 獲取到 PreparedStatement 執行兩次更新操作 //3.1 tom 賬戶 -500 ps = con.prepareStatement("update account set money = money - ? where name = ? "); ps.setDouble(1,500.0); ps.setString(2,"tom"); ps.executeUpdate(); //模擬tom轉賬後 出現異常 System.out.println(1 / 0); //3.2 jack 賬戶 +500 ps = con.prepareStatement("update account set money = money + ? where name = ? "); ps.setDouble(1,500.0); ps.setString(2,"jack"); ps.executeUpdate(); //4. 正常情況下提交事務 con.commit(); System.out.println("轉賬成功!"); } catch (SQLException e) { e.printStackTrace(); try { //5. 出現異常回滾事務 con.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { //6. 最後關閉資源 JDBCUtils.close(con,ps); } } }