天天看點

JDBC-2(CRUD)

3.PreparedStatement實作CRUD

JDBC-2(CRUD)

3.1 操作和通路資料庫

  • 資料庫連接配接被用于向資料庫伺服器發送指令和SQL語句,接受資料庫伺服器傳回的結果。(一個資料庫連接配接就是也給Socket連接配接)
  • 在 java.sql 包中有 3 個接口分别定義了對資料庫的調用的不同方式:
    • Statement:用于執行靜态 SQL 語句并傳回它所生成結果的對象。
    • PrepatedStatement:SQL 語句被預編譯并存儲在此對象中,可以使用此對象多次高效!地執行該語句。
    • CallableStatement:用于執行 SQL 存儲過程
JDBC-2(CRUD)

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對應資料類型轉換表

JDBC-2(CRUD)

使用步驟:

  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()
JDBC-2(CRUD)

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):訓示是否自動為指定列進行編号,這樣這些列仍然是隻讀的。
JDBC-2(CRUD)

查詢操作

  • 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

查詢操作總結:

JDBC-2(CRUD)

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

點選檢視代碼

//泛型方法,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中關閉,保證及時其他代碼出現異常,資源也一定能被關閉。