數(shù)據(jù)庫技術(shù)四:JDBC,預(yù)處理對象,JDBC事務(wù)控制

JDBC概述

  • 什么是JDBC

JDBC (Java Data Base Connectivity) 是 Java 訪問數(shù)據(jù)庫的標準規(guī)范。是一種用于執(zhí)行 SQL 語句的 Java API,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用 Java 語言編寫的類和接口組成。是 Java 訪問數(shù)據(jù)庫的標準規(guī)范。

  • JDBC原理

JDBC 是接口,驅(qū)動是接口的實現(xiàn),沒有驅(qū)動將無法完成數(shù)據(jù)庫連接,從而不能操作數(shù)據(jù)庫。每個數(shù)據(jù)庫廠商都需要提供自己的驅(qū)動,用來連接自己公司的數(shù)據(jù)庫,也就是說驅(qū)動一般都由數(shù)據(jù)庫生產(chǎn)廠商提供。

JDBC 就是由 sun 公司定義的一套操作所有關(guān)系型數(shù)據(jù)庫的規(guī)則(接口),而數(shù)據(jù)庫廠商需要實現(xiàn)這套接口,提供數(shù)據(jù)庫驅(qū)動 jar 包,我們可以使用這套接口編程,真正執(zhí)行的代碼是對應(yīng)驅(qū)動包中的實現(xiàn)類。

JDBC開發(fā)

  • 數(shù)據(jù)準備

在 MySQL 中準備好以下數(shù)據(jù)


-- 創(chuàng)建 jdbc_user表
CREATE TABLE jdbc_user (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50),
  PASSWORD VARCHAR(50),
  birthday DATE
);
 
-- 添加數(shù)據(jù)
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');
  • MySql驅(qū)動包

首先將 MySQL 驅(qū)動包添加到 jar 包庫文件夾 myJar 中,它用于存放當前項目需要的所有 jar 包。然后在 IDEA 的項目中配置 jar 包庫的位置。最后創(chuàng)建一個新的模塊 jdbc_task01 并添加 jar 包庫的依賴。

步驟分析:
1 . 獲取驅(qū)動 (可以省略)
2 . 獲取連接
3 . 獲取 Statement 對象
4 . 處理結(jié)果集 (只在查詢時處理)
5 . 釋放資源

1. 注冊驅(qū)動
??JDBC規(guī)范定義驅(qū)動接口: java.sql.Driver
??MySql驅(qū)動包提供了實現(xiàn)類: com.mysql.jdbc.Driver

從 JDBC 3 開始,目前已經(jīng)普遍使用的版本,可以不用注冊驅(qū)動而直接使用。

使用反射方法 Class.forName 加載 Driver 類的時候會自動執(zhí)行 Driver 類的靜態(tài)代碼塊里面的注冊驅(qū)動的代碼。

public class JDBCDemo01 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 注冊驅(qū)動 forName 方法執(zhí)行將類進行初始化
        Class.forName("com.mysql.jdbc.Driver");
    }
}

2. 獲得連接
解決插入中文亂碼問題:characterEncoding=UTF-8 指定字符的編碼、解碼格式。

public class JDBCDemo02 {
    public static void main(String[] args) throws Exception {

        // 注冊驅(qū)動
        Class.forName("com.mysql.jdbc.Driver");

        // 獲取連接 url, 用戶名, 密碼
        String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
        Connection con = DriverManager.getConnection(url, "root", "123456");
    }
}

3. 獲取語句執(zhí)行平臺
??Statement createStatement(); -- 創(chuàng)建 SQL 語句執(zhí)行對象。
??int executeUpdate(String sql); -- 執(zhí)行增刪改語句,返回 int 類型,代表受影響的行數(shù)。

public class JDBCDemo03 {
    public static void main(String[] args) throws Exception {
        // 注冊驅(qū)動
        Class.forName("com.mysql.jdbc.Driver");

        // 獲取連接 url,用戶名, 密碼
        String url = "jdbc:mysql://localhost:3306/db4";
        Connection con = DriverManager.getConnection(url, "root", "123456");

        // 獲取 Statement 對象
        Statement statement = con.createStatement();

        // 執(zhí)行創(chuàng)建表操作
        String sql = "create table test01(id int, name varchar(20), age int);";

        // 增刪改操作
        int i = statement.executeUpdate(sql);

        // 返回值是受影響的函數(shù)
        System.out.println(i);

        // 關(guān)閉流
        statement.close();
        con.close();
    }
}

4. 處理結(jié)果集
ResultSet executeQuery(String sql); -- 執(zhí)行查詢語句,返回 ResultSet 結(jié)果集對象。

只有在進行查詢操作的時候,才會處理結(jié)果集。

ResultSet 接口作用:封裝數(shù)據(jù)庫查詢的結(jié)果集,對結(jié)果集進行遍歷,取出每一條記錄。

public class JDBCDemo04 {
    public static void main(String[] args) throws SQLException {
        // 注冊驅(qū)動可以省略

        // 獲取連接
        String url = "jdbc:mysql://localhost:3306/db4";
        Connection con = DriverManager.getConnection(url, "root", "123456");

        // 獲取 Statement對象
        Statement statement = con.createStatement();
        String sql = "select * from jdbc_user";

        // 執(zhí)行查詢操作, 返回的是一個 ResultSet 結(jié)果對象
        ResultSet resultSet = statement.executeQuery(sql);

        // 處理結(jié)果集 resultSet
        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);
        }

        //關(guān)閉連接
        resultSet.close();
        statement.close();
        con.close();

    }
}

5. 釋放資源
釋放原則:先開的后關(guān),后開的先關(guān)。

public static void main(String[] args)  {

    Connection connection = null;
    Statement statement = null;
    ResultSet resultSet = null;

    try {
        // 注冊驅(qū)動(省略)
        // 獲取連接
        String url = "jdbc:mysql://localhost:3306/db4";
        connection = DriverManager.getConnection(url, "root", "123456");

        // 獲取 Statement對象
        statement = connection.createStatement();

        String sql = "select * from jdbc_user";
        resultSet = statement.executeQuery(sql);

    } catch (SQLException e) {

        e.printStackTrace();

    } finally {
        /**
         * 關(guān)閉的順序是先開后關(guān), 先得到的后關(guān)閉,后得到的先關(guān)閉
         */
        try {
            connection.close();
            resultSet.close();
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

JDBC實現(xiàn)增刪改查

  • JDBC工具類

如果一個功能經(jīng)常要用到,建議把這個功能做成一個工具類,可以在不同的地方重用。

“獲得數(shù)據(jù)庫連接”操作,將在以后的增刪改查所有功能中都存在,可以封裝工具類 JDBCUtils:1. 把幾個字符串定義成常量:用戶名,密碼,URL,驅(qū)動類;2. 得到數(shù)據(jù)庫的連接:getConnection();3. 關(guān)閉所有打開的資源。

/**
 * JDBC 工具類
 */
public class JDBCUtils {
    // 定義字符串常量, 記錄獲取連接所需要的信息
    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";

    // 靜態(tài)代碼塊, 隨著類的加載而加載
    static{
        try {
            //注冊驅(qū)動
            Class.forName(DRIVERNAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 獲取連接的靜態(tài)方法
    public static Connection getConnection(){
        try {
            //獲取連接對象
            Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
            //返回連接對象
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    //關(guān)閉資源的方法

    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);
    }
}

插入記錄

/**
 * 插入數(shù)據(jù)
 * @throws SQLException
 */
@Test
public void testInsert() throws SQLException {
    // 通過工具類獲取連接
    Connection connection = JDBCUtils.getConnection();

    // 獲取 Statement
    Statement statement = connection.createStatement();

    // 編寫 SQL
    String sql = "insert into jdbc_user values(null,'布萊爾','123','2020/1/1')";

    // 執(zhí)行 SQL
    int i = statement.executeUpdate(sql);
    System.out.println(i);

    // 關(guān)閉流
    JDBCUtils.close(connection,statement);
}

更新記錄

/**
 * 修改 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);
}

刪除記錄

/**
 * 刪除 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);
}

查詢記錄

/**
 * 查詢姓名為張三的一條記錄
 * @throws SQLException
 */
@Test
public void testDelete() throws SQLException {
    // 獲取連接對象
    Connection connection = JDBCUtils.getConnection();

    // 獲取Statement對象
    Statement statement = connection.createStatement();

    String  sql = "SELECT * FROM jdbc_user WHERE username = '張三';";
    ResultSet resultSet = statement.executeQuery(sql);

    // 處理結(jié)果集
    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);
    }

    // 釋放資源
    JDBCUtils.close(connection,statement,resultSet);
}

SQL注入問題

  • 什么是SQL注入?

SQL 注入:比如用戶輸入的密碼和 SQL 語句進行字符串拼接時,用戶輸入的內(nèi)容作為了 SQL 語句語法的一部分,改變了原有 SQL 真正的意義。

  • Example

在語句 select * from jdbc_user where username = 'abc' and password = 'abc' 后面加上 or '1'='1',那么無論 username 和 password 的值時什么,都會查詢了所有記錄,然后就會成功登錄。

public class TestLogin01 {
    /**
     * 用戶登錄案例
     * 使用 Statement 字符串拼接的方式完成查詢
     * @param args
     */
    public static void main(String[] args) throws SQLException {

        // 獲取連接
        Connection connection = JDBCUtils.getConnection();

        // 獲取 Statement對象
        Statement statement = connection.createStatement();

        // 獲取用戶輸入的用戶名和密碼
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入用戶名: ");
        String name = sc.nextLine();

        System.out.println("請輸入密碼: ");
        String pass = sc.nextLine();
        System.out.println(pass);

        // 拼接 SQL,執(zhí)行查詢
        String sql = "select * from jdbc_user " + "where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'";

        System.out.println(sql);
        ResultSet resultSet = statement.executeQuery(sql);

        // 處理結(jié)果集,判斷結(jié)果集是否為空
        if(resultSet.next()){

            System.out.println("登錄成功! 歡迎您: " + name);
        }else {

            System.out.println("登錄失敗!");
        }

        // 釋放資源
        JDBCUtils.close(connection, statement, resultSet);
    }
}
sql注入問題.png
  • 如何解決

要解決 SQL 注入就不能讓用戶輸入的密碼和 SQL 語句進行簡單的字符串拼接。



預(yù)處理對象

  • PreparedStatement 接口

預(yù)處理對象:因為有預(yù)先編譯的功能,可以提高 SQL 的執(zhí)行效率,還可以有效的防止 SQL 注入的問題,安全性更高。

PreparedStatement 接口是 Statement 接口的子接口,繼承于父接口中所有的方法。它是一個預(yù)編譯的 SQL 語句對象。

預(yù)編譯:是指 SQL 語句被預(yù)編譯,并存儲在 PreparedStatement 對象中。然后可以使用此對象多次高效地執(zhí)行該語句。

public class TestLogin02 {
    /**
     * 使用預(yù)編譯對象 PrepareStatement 完成登錄案例
     * @param args
     * @throws SQLException
     */
    public static void main(String[] args) throws SQLException {
        // 獲取連接
        Connection connection = JDBCUtils.getConnection();
 
        // 獲取用戶輸入的用戶名和密碼
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入用戶名: ");
        String name = sc.nextLine();
 
        System.out.println("請輸入密碼: ");
        String pass = sc.nextLine();
        System.out.println(pass);
 
      /*
      * 1) 編寫 SQL 語句,未知內(nèi)容使用?占位,
      *  2) 獲得 PreparedStatement 對象 
      *  3) 設(shè)置實際參數(shù):setXxx( 占位符的位置, 真實的值) 
      *  4) 執(zhí)行參數(shù)化 SQL 語句
      *  5)關(guān)閉資源
      *
      */
        // 編寫 SQL 使用 ? 占位符方式
        String sql = "select * from jdbc_user where username = ? and password = ?";
        // 1. 獲取 PrepareStatement 預(yù)編譯對象
        PreparedStatement ps = connection.prepareStatement(sql);
 
        // 2. 設(shè)置占位符參數(shù)
        ps.setString(1, name);
        ps.setString(2, pass);
 
        // 3. 執(zhí)行查詢并處理結(jié)果集
        ResultSet resultSet = ps.executeQuery();
        if(resultSet.next()){
            System.out.println("登錄成功,歡迎您: " + name);
        }else{
            System.out.println("登錄失敗!");
        }
 
        // 釋放資源
        JDBCUtils.close(connection, ps, resultSet);
    }
}
  • Statement 與 PreparedStatement的區(qū)別?

Statement 用于執(zhí)行靜態(tài) SQL 語句,在執(zhí)行時,必須指定一個事先準備好的SQL語句。

PrepareStatement 是預(yù)編譯的 SQL 語句對象,語句中可以包含動態(tài)參數(shù) “?”,在執(zhí)行時可以為 “?” 動態(tài)設(shè)置參數(shù)值。

PrepareStatement 可以減少編譯次數(shù)提高數(shù)據(jù)庫性能。


Statement 與 PreparedStatement的區(qū)別.png

JDBC 控制事務(wù)

  • 數(shù)據(jù)準備

在 MySQL 中準備好以下數(shù)據(jù)

-- 創(chuàng)建賬戶表
CREATE TABLE account(
    -- 主鍵
    id INT PRIMARY KEY AUTO_INCREMENT,
    -- 姓名
    NAME VARCHAR(10),
    -- 轉(zhuǎn)賬金額
    money DOUBLE
);
 
-- 添加兩個用戶
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);

步驟

  1. 獲取連接
  2. 開啟事務(wù) setAutoCommit(false)
  3. 獲取到 PreparedStatement,執(zhí)行兩次更新操作
  4. 正常情況下提交事務(wù) commit()
  5. 出現(xiàn)異常回滾事務(wù) rollback()
  6. 最后關(guān)閉資源
public class JDBCTransaction {
    public static void main(String[] args) {
        Connection con = null;
        PreparedStatement ps = null;

        try {
            // 獲取連接
            con = JDBCUtils.getConnection();

            // 開啟事務(wù)
            con.setAutoCommit(false);

            // 獲取到 PreparedStatement 執(zhí)行兩次更新操作
            // tom 賬戶減去 500
            ps = con.prepareStatement("update account set money = money - ? where name = ? ");
            ps.setDouble(1,500.0);
            ps.setString(2,"tom");
            ps.executeUpdate();

            // 模擬 tom 轉(zhuǎn)賬后出現(xiàn)異常
            System.out.println(1 / 0);

            // jack 賬戶增加 500
            ps = con.prepareStatement("update account set money = money + ? where name = ? ");
            ps.setDouble(1, 500.0);
            ps.setString(2, "jack");
            ps.executeUpdate();

            // 正常情況下提交事務(wù)
            con.commit();
            System.out.println("轉(zhuǎn)賬成功");

        } catch (SQLException e) {
            e.printStackTrace();
            try {
                // 出現(xiàn)異?;貪L事務(wù)
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            // 最后關(guān)閉資源
            JDBCUtils.close(con,ps);
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容