JDBC概述(1)--獲取Connection、使用PreparedStatement操作和訪問Mysql

語言: CN / TW / HK

theme: awesome-green highlight: an-old-hope


第1章:JDBC概述

來源:https://www.bilibili.com/video/BV1eJ411c7rf?p=24&spm_id_from=333.1007.top_right_bar_window_history.content.click

1.1 資料的持久化

持久化(persistence):把資料儲存到可掉電式儲存裝置中以供之後使用。大多數情況下,特別是企業級應用,資料持久化意味著將記憶體中的資料儲存到硬碟上加以”固化”,而持久化的實現過程大多通過各種關係資料庫來完成。

持久化的主要應用是將記憶體中的資料儲存在關係型資料庫中,當然也可以儲存在磁碟檔案、XML資料檔案中。

image.png

1.2 Java中的資料儲存技術

  • 在Java中,資料庫存取技術可分為如下幾類:

    • JDBC直接訪問資料庫
    • JDO (Java Data Object )技術
    • 第三方O/R工具,如Hibernate, Mybatis 等
  • JDBC是java訪問資料庫的基石,JDO、Hibernate、MyBatis等只是更好的封裝了JDBC。

1.3 JDBC介紹

  • JDBC(Java Database Connectivity)是一個獨立於特定資料庫管理系統、通用的SQL資料庫存取和操作的公共介面(一組API),定義了用來訪問資料庫的標準Java類庫,(java.sql,javax.sql)使用這些類庫可以以一種標準的方法、方便地訪問資料庫資源。

  • JDBC為訪問不同的資料庫提供了一種統一的途徑,為開發者遮蔽了一些細節問題。

  • JDBC的目標是使Java程式設計師使用JDBC可以連線任何提供了JDBC驅動程式的資料庫系統,這樣就使得程式設計師無需對特定的資料庫系統的特點有過多的瞭解,從而大大簡化和加快了開發過程。

如果沒有JDBC,那麼Java程式訪問資料庫時是這樣的:

image.png

有了JDBC,Java程式訪問資料庫時是這樣的:

image.png

總結如下:

image.png

1.4 JDBC體系結構

  • JDBC介面(API)包括兩個層次:
    • 面向應用的API:Java API,抽象介面,供應用程式開發人員使用(連線資料庫,執行SQL語句,獲得結果)。
    • 面向資料庫的API:Java Driver API,供開發商開發資料庫驅動程式用。

JDBC是sun公司提供一套用於資料庫操作的介面,java程式設計師只需要面向這套介面程式設計即可。

不同的資料庫廠商,需要針對這套介面,提供不同實現。不同的實現的集合,即為不同資料庫的驅動。 ————面向介面程式設計

1.5 JDBC程式編寫步驟

image.png

補充:ODBC(Open Database Connectivity,開放式資料庫連線),是微軟在Windows平臺下推出的。使用者在程式中只需要呼叫ODBC API,由 ODBC 驅動程式將呼叫轉換成為對特定的資料庫的呼叫請求。

第2章:獲取資料庫連線

2.1 要素一:Driver介面實現類

2.1.1 Driver介面介紹

  • java.sql.Driver 介面是所有 JDBC 驅動程式需要實現的介面。這個介面是提供給資料庫廠商使用的,不同資料庫廠商提供不同的實現

  • 在程式中不需要直接去訪問實現了 Driver 介面的類,而是由驅動程式管理器類(java.sql.DriverManager)去呼叫這些Driver實現。

    • Oracle的驅動:oracle.jdbc.driver.OracleDriver
    • mySql5.7的驅動: com.mysql.jdbc.Driver
    • mySql8的驅動:com.mysql.cj.jdbc.Driver

將上述jar包拷貝到Java工程的一個目錄中,習慣上新建一個lib資料夾。這裡演示的是新建的普通java工程,並且使用的Mysql8.0的驅動的jar包,當然,也可以直接建立Maven工程匯入相關Mysql相關依賴即可。

image.png

然後在驅動jar上右鍵-->Build Path-->Add to Build Path(idea為add library)

注意:如果是Dynamic Web Project(動態的web專案)話,則是把驅動jar放到WebContent(有的開發工具叫WebRoot)目錄中的WEB-INF目錄中的lib目錄下即可

image.png

2.1.2 載入與註冊JDBC驅動

  • 載入驅動:載入 JDBC 驅動需呼叫 Class 類的靜態方法 forName(),向其傳遞要載入的 JDBC 驅動的類名

    • Class.forName(“com.mysql.jdbc.Driver”);
  • 註冊驅動:DriverManager 類是驅動程式管理器類,負責管理驅動程式

    • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)來註冊驅動
    • 通常不用顯式呼叫 DriverManager 類的 registerDriver() 方法來註冊驅動程式類的例項,因為 Driver 介面的驅動程式類都包含了靜態程式碼塊,在這個靜態程式碼塊中,會呼叫 DriverManager.registerDriver() 方法來註冊自身的一個例項。下圖是MySQL的Driver實現類的原始碼:

image.png

2.2 要素二:URL

  • JDBC URL 用於標識一個被註冊的驅動程式,驅動程式管理器通過這個 URL 選擇正確的驅動程式,從而建立到資料庫的連線。

  • JDBC URL的標準由三部分組成,各部分間用冒號分隔。

    • jdbc:子協議:子名稱
    • 協議:JDBC URL中的協議總是jdbc
    • 子協議:子協議用於標識一個數據庫驅動程式
    • 子名稱:一種標識資料庫的方法。子名稱可以依不同的子協議而變化,用子名稱的目的是為了定位資料庫提供足夠的資訊。包含主機名(對應服務端的ip地址),埠號,資料庫名
  • 舉例:

image.png

  • 幾種常用資料庫的 JDBC URL

    • MySQL的連線URL編寫方式:

      • jdbc:mysql://主機名稱:mysql服務埠號/資料庫名稱?引數=值&引數=值
      • jdbc:mysql://localhost:3306/atguigu
      • jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC程式與伺服器端的字符集不一致,會導致亂碼,那麼可以通過引數指定伺服器端的字符集)
      • jdbc:mysql://localhost:3306/atguigu?user=root&password=123456

2.3 要素三:使用者名稱和密碼

  • user,password可以用“屬性名=屬性值”方式告訴資料庫
  • 可以呼叫 DriverManager 類的 getConnection() 方法建立到資料庫的連線

2.4 資料庫連線方式舉例

2.4.0 準備工作

1、利用navicat匯入sql指令碼到mysql資料庫

image.png

2、建立普通java工程或者Maven工程進行演示:idea

3、如果是普通java工程,則工程目錄下新建libs包放mysql驅動的jar包

4、如果是Maven工程,匯入依賴到POM檔案即可:

xml //以Mysql8為例:Mysql5.7的可以去Maven倉庫中自己找 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>

5、如下是Mysql5.7和Mysql8.0版本的驅動

image.png

6、Mysql5.7的JDBC配置

```properties

最好加個字首

user=root password=XXXXXXXXX url=jdbc:mysql://localhost:3306/test driverClass=com.mysql.jdbc.Driver ```

7、Mysql8.0的JDBC配置

properties jdbc.driver=com.mysql.cj.jdbc.Driver #注意:5.7之後為cj.... jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false jdbc.username=root jdbc.password=XXXXXXXXX

2.4.1 連線方式一

Mysql5.7演示:

```java @Test public void testConnection1() { try { //1.提供java.sql.Driver介面實現類的物件 Driver driver = null; driver = new com.mysql.jdbc.Driver();

    //2.提供url,指明具體操作的資料
    String url = "jdbc:mysql://localhost:3306/test";

    //3.提供Properties的物件,指明使用者名稱和密碼
    Properties info = new Properties();
    info.setProperty("user", "root");
    info.setProperty("password", "abc123");

    //4.呼叫driver的connect(),獲取連線
    Connection conn = driver.connect(url, info);
    System.out.println(conn);
} catch (SQLException e) {
    e.printStackTrace();
}

} ```

說明:上述程式碼中顯式出現了第三方資料庫的API

mysql8.0演示

```java package com.lemon.connection;

import org.junit.Test;

import java.sql.Connection; import java.sql.Driver; import java.sql.SQLException; import java.util.Properties;

//jdbc.driver=com.mysql.cj.jdbc.Driver //jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false

public class ConnectionTest { @Test public void testConnection1() { try {

        //1.提供java.sql.Driver介面實現類的物件:mysql的驅動
        Driver driver = null;
        driver = new com.mysql.cj.jdbc.Driver();

        //2.提供url,指明具體操作的資料:這裡連線的是我們建立的test資料庫
        String url = "jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false";

        //3.提供Properties的物件,指明使用者名稱和密碼進行封裝
        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "##ajt19980607");

        //4.呼叫driver的connect(),獲取mysql資料庫連線connection
        Connection conn = driver.connect(url, info);
        System.out.println(conn);//[email protected]
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

} ```

2.4.2 連線方式二

```java //mysql5.7,8.0同樣 @Test public void testConnection2() { try { //1.例項化Driver String className = "com.mysql.jdbc.Driver"; Class clazz = Class.forName(className); Driver driver = (Driver) clazz.newInstance();

    //2.提供url,指明具體操作的資料
    String url = "jdbc:mysql://localhost:3306/test";

    //3.提供Properties的物件,指明使用者名稱和密碼
    Properties info = new Properties();
    info.setProperty("user", "root");
    info.setProperty("password", "abc123");

    //4.呼叫driver的connect(),獲取連線
    Connection conn = driver.connect(url, info);
    System.out.println(conn);//[email protected]

} catch (Exception e) {
    e.printStackTrace();
}

} ```

說明:相較於方式一,這裡使用反射例項化Driver,不在程式碼中體現第三方資料庫的API。體現了面向介面程式設計思想。

2.4.3 連線方式三

```java @Test public void testConnection3() { try { //1.資料庫連線的4個基本要素: String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "abc123"; String driverName = "com.mysql.jdbc.Driver";

    //2.例項化Driver
    Class clazz = Class.forName(driverName);
    Driver driver = (Driver) clazz.newInstance();
    //3.註冊驅動
    DriverManager.registerDriver(driver);
    //4.獲取連線
    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn)//com.mysql.cj.jdbc.ConnectionIm[email protected]
} catch (Exception e) {
    e.printStackTrace();
}

} ```

說明:使用DriverManager實現資料庫的連線。體會獲取連線必要的4個基本要素。

2.4.4 連線方式四

```java @Test public void testConnection4() { try { //1.資料庫連線的4個基本要素: String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "abc123"; String driverName = "com.mysql.jdbc.Driver";

    //2.載入驅動 (①例項化Driver ②註冊驅動)
    Class.forName(driverName);


    //Driver driver = (Driver) clazz.newInstance();
    //3.註冊驅動
    //DriverManager.registerDriver(driver);
    /*
        可以註釋掉上述程式碼的原因,是因為在mysql的Driver類中宣告有:
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }

         */


    //3.獲取連線
    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn);
} catch (Exception e) {
    e.printStackTrace();
}

} ```

說明:不必顯式的註冊驅動了。因為在DriverManager的原始碼中已經存在靜態程式碼塊,實現了驅動的註冊。

2.4.5 連線方式五(最終版)

```java @Test public void testConnection5() throws Exception { //1.載入配置檔案 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is);

//2.讀取配置資訊
String user = pros.getProperty("jdbc.user");
String password = pros.getProperty("jdbc.password");
String url = pros.getProperty("jdbc.url");
String driverClass = pros.getProperty("jdbc.driverClass");

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

//4.獲取連線
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);

} ```

其中,配置檔案宣告在工程的src目錄下:【jdbc.properties】

```properties

5.7

user=root password=XXXXXXXXX url=jdbc:mysql://localhost:3306/test driverClass=com.mysql.jdbc.Driver

8.0

jdbc.driverClass=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false jdbc.user=root jdbc.password=XXXXXXXXX ```

說明:使用配置檔案的方式儲存配置資訊,在程式碼中載入配置檔案

使用配置檔案的好處:

  • 實現了程式碼和資料的分離,如果需要修改配置資訊,直接在配置檔案中修改,不需要深入程式碼
  • 如果修改了配置資訊,省去重新編譯的過程。

第3章:使用PreparedStatement實現CRUD操作

3.1 操作和訪問資料庫

  • 資料庫連線被用於向資料庫伺服器傳送命令和 SQL 語句,並接受資料庫伺服器返回的結果。其實一個數據庫連線就是一個Socket連線。

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

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

image.png

3.2 使用Statement操作資料表的弊端

  • 通過呼叫 Connection 物件的 createStatement() 方法建立該物件。該物件用於執行靜態的 SQL 語句,並且返回執行結果。

  • Statement 介面中定義了下列方法用於執行 SQL 語句:

    sql int excuteUpdate(String sql):執行更新操作INSERT、UPDATE、DELETE ResultSet executeQuery(String sql):執行查詢操作SELECT

  • 但是使用Statement操作資料表存在弊端:

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

  • 對於 Java 而言,要防範 SQL 注入,只要用 PreparedStatement(從Statement擴充套件而來) 取代 Statement 就可以了。

  • 程式碼演示:

```java public class StatementTest {

// 使用Statement的弊端:需要拼寫sql語句,並且存在SQL注入的問題
@Test
public void testLogin() {
    Scanner scan = new Scanner(System.in);

    System.out.print("使用者名稱:");
    String userName = scan.nextLine();
    System.out.print("密碼:");
    String password = scan.nextLine();

    // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
    String sql = "SELECT user,password FROM user_table WHERE user = '" + userName + "' AND password = '" + password
            + "'";
    User user = get(sql, User.class);
    if (user != null) {
        System.out.println("登陸成功!");
    } else {
        System.out.println("使用者名稱或密碼錯誤!");
    }
}

// 使用Statement實現對資料表的查詢操作
public <T> T get(String sql, Class<T> clazz) {
    T t = null;

    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        // 1.載入配置檔案
        InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(is);

        // 2.讀取配置資訊
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

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

        // 4.獲取連線
        conn = DriverManager.getConnection(url, user, password);

        st = conn.createStatement();

        rs = st.executeQuery(sql);

        // 獲取結果集的元資料
        ResultSetMetaData rsmd = rs.getMetaData();

        // 獲取結果集的列數
        int columnCount = rsmd.getColumnCount();

        if (rs.next()) {

            t = clazz.newInstance();

            for (int i = 0; i < columnCount; i++) {
                // //1. 獲取列的名稱
                // String columnName = rsmd.getColumnName(i+1);

                // 1. 獲取列的別名
                String columnName = rsmd.getColumnLabel(i + 1);

                // 2. 根據列名獲取對應資料表中的資料
                Object columnVal = rs.getObject(columnName);

                // 3. 將資料表中得到的資料,封裝進物件
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(t, columnVal);
            }
            return t;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 關閉資源
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    return null;
}

} ```

綜上:

image.png

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 注入

  • 還可以插入BLOB型別的欄位

3.3.3 Java與SQL對應資料型別轉換表

| Java型別 | SQL型別 | | ------------------ | ------------------------ | | boolean | BIT | | byte | TINYINT | | short | SMALLINT | | int | INTEGER | | long | BIGINT | | String | CHAR,VARCHAR,LONGVARCHAR | | byte array | BINARY , VAR BINARY | | java.sql.Date | DATE | | java.sql.Time | TIME | | java.sql.Timestamp | TIMESTAMP |

3.3.4 ORM關係對映

ORM關係對映:

  • 一個數據表對應一個java類
  • 表中一條記錄對應一個java物件
  • 表中一個欄位對應一個java屬性

最好表中的每個欄位名和Java類中屬性名一致,否則必須給表中欄位新增別名,別名為java類中屬性欄位名

表中欄位的屬性和Java中屬性型別轉換如上表所示

3.3.5 使用PreparedStatement實現增、刪、改操作

以customer表為例,id為主鍵且自增

image.png

以向customers表中增加一條記錄為例:

```java package com.lemon.connection;

import org.junit.Test;

import java.io.InputStream; import java.sql.*; import java.text.SimpleDateFormat; import java.util.Properties;

/* * @author lemon * @create 2022-01-27 19:53 * TO:一把青梅換了酒錢 / public class PreparedStatementTest { @Test public void testInsert(){

    Connection conn = null;
    PreparedStatement ps = null;

    try {
        //1.載入配置檔案
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(is);

        //2.讀取配置資訊
        String user = pros.getProperty("jdbc.user");
        String password = pros.getProperty("jdbc.password");
        String url = pros.getProperty("jdbc.url");
        String driverClass = pros.getProperty("jdbc.driverClass");

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

        //4.獲取連線
        conn = DriverManager.getConnection(url,user,password);

        //5、新增sql語句
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        //預編譯sql語句,返回PreparedStatement的例項
        ps = conn.prepareStatement(sql);
        //填充佔位符
        ps.setString(1, "孫悟空");
        ps.setString(2, "[email protected]");

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        java.util.Date date = sdf.parse("2008-01-01");
        ps.setDate(3, new Date(date.getTime()));

        //6、執行sql語句
        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //7、關閉資源
        try {
            if(ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn != null){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

}

```

檢視資料庫結果:

image.png

3.3.6 封裝:JDBCUtils工具類(V1)

```java public class JDBCUtils { //獲取連線 public static Connection getConnection() throws Exception{ //1.載入配置檔案 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is);

    //2.讀取配置資訊
    String user = pros.getProperty("jdbc.user");
    String password = pros.getProperty("jdbc.password");
    String url = pros.getProperty("jdbc.url");
    String driverClass = pros.getProperty("jdbc.driverClass");

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

    //4.獲取連線
    Connection conn = DriverManager.getConnection(url,user,password);
    return conn;

}

//關閉連線
public static void closeResource(Connection conn, PreparedStatement ps){
    try {
        if(ps != null) {
            ps.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    try {
        if(conn != null){
            conn.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

} ```

3.3.7 通用的增、刪、改操作

```java //通用的增、刪、改操作(體現一:增、刪、改 ; 體現二:針對於不同的表) public void update(String sql,Object ... args){ //可變形參 Connection conn = null; PreparedStatement ps = null; try { //1.獲取資料庫的連線 conn = JDBCUtils.getConnection(); //2.獲取PreparedStatement的例項 (或:預編譯sql語句) ps = conn.prepareStatement(sql); //3.填充佔位符 for(int i = 0;i < args.length;i++){ ps.setObject(i + 1, args[i]); } //4.執行sql語句 ps.execute(); } catch (Exception e) { e.printStackTrace(); }finally{ //5.關閉資源 JDBCUtils.closeResource(conn, ps);

}

} ```

驗證:刪除customers表中id為1的記錄

java @Test public void testUpdate(){ String sql = "delete from customers where id = ?"; //這裡注意表名如果是關鍵字要加著重號`` update(sql,1); }

image.png

3.3.8 使用PreparedStatement實現查詢操作-返回一個物件

查詢id=3的記錄,並封裝為javaBean

```java public class CustomerForQuery {

@Test
public void testQuery1(){

    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        // 1.獲取資料庫連線
        conn = JDBCUtils.getConnection();

        // 2.預編譯sql語句,得到PreparedStatement物件
        String sql = "select id,name,email,birth from customers where id = ?";
        ps = conn.prepareStatement(sql);
        ps.setObject(1, 3);


        //3.執行executeQuery(),得到結果集:ResultSet:這個結果集不包含列名
        rs = ps.executeQuery();

        //4、處理結果集
        if (rs.next()) { //判斷結果集的下一條是否有資料,類比next,true即指標下移
            // 獲取列值
            int id = rs.getInt(1);
            String name = rs.getString(2);
            String email = rs.getString(3);
            Date birth = rs.getDate(4);

            //將資料封裝為一個javabean
            Customer cus = new Customer(id,name,email,birth);
            System.out.println(cus);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 7.關閉資源
        JDBCUtils.closeResource(conn, ps, rs);//需要過載一下
    }
}

} ```

Customer類

```java package com.lemon.bean;

import java.util.Date;

/* * @author lemon * @create 2022-01-27 21:32 * TO:一把青梅換了酒錢 / public class Customer { //ORM關係對映 //注意和資料庫中欄位名對應,否則sql語句使用別名 private int id; private String name; private String email; private Date birth;

public Customer() {
}

public Customer(int id, String name, String email, Date birth) {
    this.id = id;
    this.name = name;
    this.email = email;
    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 String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public Date getBirth() {
    return birth;
}

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

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

}

```

結果:

image.png

3.3.9 針對customers表的通用查詢操作--返回一條記錄

主要是if和while的區別

```java public Customer queryForCustomers(String sql, Object... args){

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    // 1.獲取資料庫連線
    conn = JDBCUtils.getConnection();

    // 2.預編譯sql語句,得到PreparedStatement物件
    ps = conn.prepareStatement(sql);
    for (int i = 0; i < args.length; i++) {
        ps.setObject(i + 1, args[i]);
    }

    //3.執行executeQuery(),得到結果集:ResultSet:這個結果集不包含列名
    rs = ps.executeQuery();

    // 4.得到結果集的元資料
    ResultSetMetaData rsmd = rs.getMetaData();
    //得到列數,獲取列的值,就得知道有多少個列
    int columnCount = rsmd.getColumnCount();

    //5、處理結果集
    if (rs.next()) { //判斷結果集的下一條是否有資料,類比next,true即指標下移

        Customer cus = new Customer();//使用空參構造器建立物件,
        for (int i = 0; i < columnCount; i++) {// 遍歷每一個列

            // 獲取列值
            Object columnVal = rs.getObject(i + 1);
            // 獲取列的別名:列的別名,如果沒有設定別名則使用類的屬性名充當
            //也可以使用getColumnName,但是該方法不能獲取別名,別名設定為欄位屬性名
            String columnLabel = rsmd.getColumnLabel(i + 1);

            // 6.2使用反射,給物件的相應屬性賦值,列名(準確來說是別名)就是java中的屬性名,
            Field field = Customer.class.getDeclaredField(columnLabel);//clazz=Customer.class
            field.setAccessible(true);
            field.set(cus, columnVal);
        }
        return cus;
    }

} catch (Exception e) {
    e.printStackTrace();
} finally {
    // 7.關閉資源
    JDBCUtils.closeResource(conn, ps, rs);
}
return null;

} ```

測試:

```java @Test public void testQuery(){ String sql = "select id,name,email,birth from customers where id = ?"; //欄位起別名 Customer customer = queryForCustomers(sql,3); System.out.println(customer);

} ```

結果:

image.png

3.3.10 通用的針對於不同表的查詢:返回一個物件

```java // 通用的針對於不同表的查詢:返回一個物件 (version 1.0) public T getInstance(Class clazz, String sql, Object... args) {

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    // 1.獲取資料庫連線
    conn = JDBCUtils.getConnection();

    // 2.預編譯sql語句,得到PreparedStatement物件
    ps = conn.prepareStatement(sql);

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

    // 4.執行executeQuery(),得到結果集:ResultSet
    rs = ps.executeQuery();

    // 5.得到結果集的元資料:ResultSetMetaData
    ResultSetMetaData rsmd = rs.getMetaData();

    // 6.1通過ResultSetMetaData得到columnCount,columnLabel;通過ResultSet得到列值
    int columnCount = rsmd.getColumnCount();
    if (rs.next()) { //判斷結果集的下一條是否有資料,類比next,true即指標下移
        T t = clazz.newInstance();//這裡演示的是統一的造物件,查得結果才造物件
        for (int i = 0; i < columnCount; i++) {// 遍歷每一個列

            // 獲取列值
            Object columnVal = rs.getObject(i + 1);
            // 獲取列的別名:列的別名,使用類的屬性名充當,沒有別名,則預設屬性名,同getColumnName
            String columnLabel = rsmd.getColumnLabel(i + 1);

            // 6.2使用反射,給物件的相應屬性賦值  列名(準確來說是別名)就是java中的屬性名,
            Field field = clazz.getDeclaredField(columnLabel);//clazz=Customer.class
            field.setAccessible(true);
            field.set(t, columnVal);

        }

        return t;

    }
} catch (Exception e) {

    e.printStackTrace();
} finally {
    // 7.關閉資源
    JDBCUtils.closeResource(conn, ps, rs); 構成方法的過載
}

return null;

} ```

說明:使用PreparedStatement實現的查詢操作可以替換Statement實現的查詢操作,解決Statement拼串和SQL注入問題。

3.3.11 通用的針對於不同表的查詢:返回多條記錄

```java public List getForList(Class clazz, String sql, Object... args) {

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    // 1.獲取資料庫連線
    conn = JDBCUtils.getConnection();

    // 2.預編譯sql語句,得到PreparedStatement物件
    ps = conn.prepareStatement(sql);

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

    // 4.執行executeQuery(),得到結果集:ResultSet
    rs = ps.executeQuery();

    // 5.得到結果集的元資料:ResultSetMetaData
    ResultSetMetaData rsmd = rs.getMetaData();

    // 6.1通過ResultSetMetaData得到columnCount,columnLabel;通過ResultSet得到列值
    int columnCount = rsmd.getColumnCount();

    //建立集合物件
    Arraylist<T> List = new ArrayList<T>();
    while (rs.next()) { //判斷結果集的下一條是否有資料,類比next,true即指標下移
        T t = clazz.newInstance();//這裡演示的是統一的造物件,查得結果才造物件
        for (int i = 0; i < columnCount; i++) {// 遍歷每一個列

            // 獲取列值
            Object columnVal = rs.getObject(i + 1);
            // 獲取列的別名:列的別名,使用類的屬性名充當
            String columnLabel = rsmd.getColumnLabel(i + 1);
            getColumnname:獲取列名
            // 6.2使用反射,給物件的相應屬性賦值  列名(準確來說是別名)就是java中的屬性名,
            Field field = clazz.getDeclaredField(columnLabel);//clazz=Customer.class
            field.setAccessible(true);
            field.set(t, columnVal);

        }

        list.add(t);

    }
    return list;
} catch (Exception e) {

    e.printStackTrace();
} finally {
    // 7.關閉資源
    JDBCUtils.closeResource(conn, ps, rs); 構成方法的過載
}

return null;

} ```

測試

java @Test public void testQuery(){     String sql = "select id,name,email,birth from customers where id < ?"; //欄位起別名     list<Customer> list = getInstance(Customer.class,sql,12);     list.forEach(System::out::println) }

3.4 ResultSet與ResultSetMetaData

3.4.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.png

3.4.2 ResultSetMetaData

  • 可用於獲取關於 ResultSet 物件中列的型別和屬性資訊的物件
  • ResultSetMetaData meta = rs.getMetaData();

java getColumnName(int column):獲取指定列的名稱 getColumnLabel(int column):獲取指定列的別名 getColumnCount():返回當前 ResultSet 物件中的列數。 getColumnTypeName(int column):檢索指定列的資料庫特定的型別名稱。 getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字元為單位。 isNullable(int column):指示指定列中的值是否可以為 null。 isAutoIncrement(int column):指示是否自動為指定列進行編號,這樣這些列仍然是隻讀的。

image.png

問題1:得到結果集後, 如何知道該結果集中有哪些列 ? 列名是什麼?

​ 需要使用一個描述 ResultSet 的物件, 即 ResultSetMetaData

問題2:關於ResultSetMetaData

  1. 如何獲取 ResultSetMetaData: 呼叫 ResultSet 的 getMetaData() 方法即可
  2. 獲取 ResultSet 中有多少列:呼叫 ResultSetMetaData 的 getColumnCount() 方法
  3. 獲取 ResultSet 每一列的列的別名是什麼:呼叫 ResultSetMetaData 的getColumnLabel() 方法

image.png

3.5 資源的釋放

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

3.6 JDBC API小結

  • 兩種思想

    • 面向介面程式設計的思想
    • ORM思想(object relational mapping)
      • 一個數據表對應一個java類
      • 表中的一條記錄對應java類的一個物件
      • 表中的一個欄位對應java類的一個屬性

    sql是需要結合欄位名和表的屬性名來寫。注意起別名。

  • 兩種技術

    • JDBC結果集的元資料:ResultSetMetaData
      • 獲取列數:getColumnCount()
      • 獲取列的別名:getColumnLabel()
    • 通過反射,建立指定類的物件,獲取指定的屬性並賦值

    PreparedStatement可以操作Blog的資料,而Statement做不到,並且還可以實現更加高效的批量操作