JDBC-2(CRUD)

3.PreparedStatement實現CRUD

image

3.1 操作和訪問資料庫

  • 資料庫連接被用於向資料庫伺服器發送命令和SQL語句,接受資料庫伺服器返回的結果。(一個資料庫連接就是也給Socket連接)

  • 在 java.sql 包中有 3 個介面分別定義了對資料庫的調用的不同方式:

    • Statement:用於執行靜態 SQL 語句並返回它所生成結果的對象。
    • PrepatedStatement:SQL 語句被預編譯並存儲在此對象中,可以使用此對象多次高效!地執行該語句。
    • CallableStatement:用於執行 SQL 存儲過程

image

3.2 Statement操作數據表的弊端

  1. 存在拼串操作,繁瑣
  2. 存在SQL注入問題 SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1'
    • SQL注入是利用某些系統沒有對用戶輸入的數據進行充分檢查,而在用戶輸入數據中注入非法的SQL語句段或命令,從而利用系統的SQL引擎完成惡意行為的做法。

3.3 PreparedStatement的使用

3.3.1 PreparedStatement介紹

  • 可以通過調用 Connection 對象的 preparedStatement(String sql) 方法獲取 PreparedStatement 對象

  • PreparedStatement 介面是 Statement 的子介面,它表示一條預編譯過的 SQL 語句

  • PreparedStatement 對象所代表的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXxx() 方法來設置這些參數. setXxx() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值

3.3.2 PreparedStatement vs Statement

  • 程式碼的可讀性和可維護性。

  • PreparedStatement 能最大可能提高性能:

    • DBServer會對預編譯語句提供性能優化。因為預編譯語句有可能被重複調用,所以語句在被DBServer的編譯器編譯後的執行程式碼被快取下來,那麼下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行程式碼中就會得到執行。
    • 在statement語句中,即使是相同操作但因為數據內容不一樣,所以整個語句本身不能匹配,沒有快取語句的意義.事實是沒有資料庫會對普通語句編譯後的執行程式碼快取。這樣每執行一次都要對傳入的語句編譯一次。
    • (語法檢查,語義檢查,翻譯成二進位命令,快取)
  • PreparedStatement 可以防止 SQL 注入

3.3.3 Java與SQL對應數據類型轉換表

image

使用步驟:

  1. 建立資料庫連接
  2. 預編譯sql語句,返回PreparedStatement實例
  3. 填充佔位符(注意: 與資料庫交互的API起始值為1)
  4. 執行sql操作
  5. 關閉資源(connection preparedStatement)

3.3.4 增刪改

點擊查看程式碼
    @Test //向t_account中添加一條記錄
    public void Update1() throws Exception{
        //1.獲取配置文件基本資訊
        InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties prop = new Properties();
        prop.load(inputStream);

        String url = prop.getProperty("url");
        String driverClass = prop.getProperty("driverClass");
        String user = prop.getProperty("user");
        String password = prop.getProperty("password");

        //2.載入驅動
        Class.forName(driverClass);

        //3,獲取連接
        Connection connection = DriverManager.getConnection(url, user, password);

        //4.update,預編譯sql語句,返回PreparedStatement實例
        String sql = "insert into t_account(username, money) values(?,?)";//?為佔位符
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.填充佔位符(與資料庫交互的API起始值為1)
        preparedStatement.setString(1, "Mike");
        preparedStatement.setInt(2, 1000000);

        //6.執行sql操作
        preparedStatement.execute();

        //7.資源關閉(連接和preparedStatement都要關)
        preparedStatement.close();
        connection.close();
    }

進階:封裝connection與close的實現

點擊查看封裝類JDBCUtils
public class JDBCUtils {
    public static Connection getConnection() throws Exception{
        //1.獲取配置文件基本資訊
        InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties prop = new Properties();
        prop.load(inputStream);

        String url = prop.getProperty("url");
        String driverClass = prop.getProperty("driverClass");
        String user = prop.getProperty("user");
        String password = prop.getProperty("password");

        //2.載入驅動
        Class.forName(driverClass);

        //3,獲取連接
        Connection connection = DriverManager.getConnection(url, user, password);

        return connection;
    }

    public static void closeResource(Connection connection, PreparedStatement preparedStatement){
        try{
            if(connection != null) connection.close();
            if(preparedStatement != null) preparedStatement.close();
        } catch (SQLException e){
            e.printStackTrace();
        }

    }
}
點擊查看實現類
    @Test
    public void Update2() throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {

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

            //2.預編譯sql語句,返回preparedStatement實例
            String sql = "UPDATE t_account\n" +
                    "SET money = ?\n" +
                    "WHERE username = ?;";
            preparedStatement = connection.prepareStatement(sql);


            //可選 填充佔位符
            preparedStatement.setInt(1, 200000000);
            preparedStatement.setString(2, "Jingd");

            //3執行sql操作
            preparedStatement.execute();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            //4.資源關閉
            JDBCUtils.closeResource(connection, preparedStatement);
        }
    }

進階:通用增刪改操作(函數傳參為sql語句與佔位符)

點擊查看程式碼
    public void allUpdate(String sql, Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            //1.建立連接
            connection = JDBCUtils.getConnection();

            //2.預編譯sql語句,返回preparedStatement實例
            preparedStatement = connection.prepareStatement(sql);

            //3.(可選) 填充佔位符
            for (int i = 0; i << args.length; i++) {
                //注意! 易錯!
                preparedStatement.setObject(i + 1, args[i]);
            }

            //4.執行
            preparedStatement.execute();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            //5.關閉資源
            JDBCUtils.closeResource(connection, preparedStatement);
        }
    }

註:若表中有關鍵詞名與sql語句名重名,可用著重號“表示獨特性。


3.3.5 查

1-ResultSet概述

  • 查詢需要調用PreparedStatement 的 executeQuery() 方法,查詢結果是一個ResultSet 對象

  • ResultSet 對象以邏輯表格的形式封裝了執行資料庫操作的結果集,ResultSet 介面由資料庫廠商提供實現

  • ResultSet 返回的實際上就是一張數據表。有一個指針指向數據表的第一條記錄的前面。

  • ResultSet 對象維護了一個指向當前數據行的游標,初始的時候,游標在第一行之前,可以通過 ResultSet 對象的 next() 方法移動到下一行。調用 next()方法檢測下一行是否有效。若有效,該方法返回 true,且指針下移。相當於Iterator對象的 hasNext() 和 next() 方法的結合體。

  • 當指針指向一行時, 可以通過調用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。

    • 例如: getInt(1), getString(“name”)
    • 注意:Java與資料庫交互涉及到的相關Java API中的索引都從1開始。
  • ResultSet 介面的常用方法:

    • boolean next()

    • getString()

image

2-ResultSetMetData

  • 可用於獲取關於 ResultSet 對象中列的類型和屬性資訊的對象

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):獲取指定列的名稱

    • getColumnLabel(int column):獲取指定列的別名

    • getColumnCount():返回當前 ResultSet 對象中的列數。

    • getColumnTypeName(int column):檢索指定列的資料庫特定的類型名稱。

    • getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字元為單位。

    • isNullable(int column):指示指定列中的值是否可以為 null。

    • isAutoIncrement(int column):指示是否自動為指定列進行編號,這樣這些列仍然是只讀的。

image


查詢操作

  • R與CUD的主要區別在於執行與處理結果,查詢結果用FormatClass類
點擊查看程式碼
    @Test 
    public void selectTest1(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //1.建立資料庫連接
            connection = JDBCUtils.getConnection();

            //2.預編譯sql語句,返回preparedStatement實例
            String sql = "SELECT *\n" +
                    "FROM t_account\n" +
                    "WHERE id IN(?,?);";
            preparedStatement = connection.prepareStatement(sql);

            //3.填充佔位符
            preparedStatement.setObject(1, 1);
            preparedStatement.setObject(2, 2);

            //4.執行(區別於CRD的主要),並返回結果集
            resultSet = preparedStatement.executeQuery();

            //5.處理結果集****
            //next方法:判斷結果集下一條是否有數據,若有返回true且指針下移;否則返回false
            while(resultSet.next()){
                //獲取當前數據的各個欄位值
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                int money = resultSet.getInt(3);
                Date date = resultSet.getDate(4);

                //1.sout 2.Object[]
                //3.將數據封裝為一個對象(推薦)
                FormatClass formatClass = new FormatClass(id, name, money, date);
                System.out.println(formatClass);

            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
    }
點擊查看ORM類
package Jing.bean;

import java.sql.Date;

//ORM編程思想(object relational mapping)
//一個數據對應一個java類
//表中的一條記錄對應java類的一個對象
//表中的一個欄位對應java類的一個屬性
public class FormatClass {

    private int id;
    private String name;
    private int money;
    private Date birth;

    public FormatClass(int id, String name, int money, Date birth) {
        this.id = id;
        this.name = name;
        this.money = money;
        this.birth = birth;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    @Override
    public String toString() {
        return "FormatClass{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                ", birth=" + birth +
                '}';
    }
}

進階-通用查找操作(反射實現)

點擊查看程式碼
    //針對查詢的通用操作
    public List<FormatClass> selectAll(String sql, Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //1.獲取資料庫鏈接
            connection = JDBCUtils.getConnection();

            //2.預編譯sql語句,返回preparedStatement實例
            preparedStatement = connection.prepareStatement(sql);

            //3.填充佔位符
            for(int i = 0; i < args.length; i++){ //>
                preparedStatement.setObject(i + 1, args[i]);
            }

            //4.執行-返回結果集
            resultSet = preparedStatement.executeQuery();

            //獲取結果集的元數據(修飾現有數據的數據)
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int colCount = resultSetMetaData.getColumnCount(); //獲取結果集的列數

            //5.處理結果集
            List<FormatClass> list = new ArrayList<>();
            while(resultSet.next()){
                FormatClass formatClass = new FormatClass();
                //處理一行結果集中數據的每一列
                for(int i = 1; i <= colCount; i++){  //>
                    //列值
                    Object colVal = resultSet.getObject(i);
                    //列名
                  //String colName = resultSetMetaData.getColumnName(i);

                    String colName = resultSetMetaData.getColumnLabel(i);
                    //給format對象指定的colName屬性賦值為colValue - 反射
                    Field field = FormatClass.class.getDeclaredField(colName);//獲取指定屬性名
                    field.setAccessible(true);//設置為可訪問
                    field.set(formatClass, colVal);
                }
//                int id = resultSet.getInt(1);
//                String username = resultSet.getString(2);
//                int money = resultSet.getInt(3);
//                Date date = resultSet.getDate(4);
                list.add(formatClass);
            }
            return list;

        } catch (Exception e){
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
        return null;
    }

  1. ResultSetMetaData獲取結果集元數據 -> 獲取資料庫結果集列名與結果集列數
    • 結果集ResultSet對應數據
    • 結果集元數據ResultSetMetaData對應修飾(列名、列數)
      • getColumnName() 獲取結果集列名 (不推薦使用,缺乏普適性)
      • getColumnLabel() 獲取列的別名 (沒有起別名時就是類的列名)
      • 針對錶的欄位名與類的屬性名不相同情況時,需使用類的屬性名來命名欄位別名
  2. 使用反射獲取指定屬性、將私有屬性設置為可訪問並填充值
    1. 獲取屬性
    2. 設置屬性為可訪問true
    3. 填充屬性列值set

查詢操作總結:

image


進階-針對不同表的通用查詢操作

點擊查看程式碼
    //泛型方法,clazz為對應返回類
    public <T> List<T> getInstance(Class<T> clazz, String sql, Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //1.建立連接
            connection = JDBCUtils.getConnection();

            //2.預編譯sql語句獲取preparedStatement對象
            preparedStatement = connection.prepareStatement(sql);

            //3.填充佔位符
            for(int i = 0; i < args.length; i++){
                preparedStatement.setObject(i + 1, args[i]);
            }

            //4.獲取結果集與結果集元數據
            resultSet = preparedStatement.executeQuery();
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int countVal = resultSetMetaData.getColumnCount();

            //5.數據處理
            List<T> list = new ArrayList<>();
            while(resultSet.next()){
                T t = clazz.newInstance();
                for(int i = 0; i < countVal; i++){
                    //獲取查詢結果
                    Object Val = resultSet.getObject(i + 1);

                    //獲取列名
                    String valName = resultSetMetaData.getColumnLabel(i + 1);

                    //反射注入
                    Field field = t.getClass().getDeclaredField(valName);
                    field.setAccessible(true); //設置私有屬性可訪問
                    field.set(t, Val);
                }
                list.add(t);
            }

            return list;
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
        return null;
    }
  • 使用泛型方法處理泛型類並返回對應泛型集合(傳參時將類的類型傳入xxx.class)

3.3.6 資源釋放

  • 釋放ResultSet, Statement,Connection。
  • 資料庫連接(Connection)是非常稀有的資源,用完後必須馬上釋放,如果Connection不能及時正確的關閉將導致系統宕機。Connection的使用原則是盡量晚創建,盡量早的釋放。
  • 可以在finally中關閉,保證及時其他程式碼出現異常,資源也一定能被關閉。
Tags: