8.x版本JDBC數據庫連接技術

一、前言

1.1課程需要哪些前置技術

技術 版本 備註
idea 2022.2 最新版本
jdk 1.8
mysql-jdbc驅動 8.0.27 8.0.25+
druid 1.1.21
mysql 8.0.25
  • 前置技術
    • 需要軟件
      • mysql軟件安裝(8+版本)
      • mysql可視化工具安裝
      • idea工具安裝(推薦2022版本)
    • SQL語句
      • 掌握數據庫連接命令
      • 掌握基本的DDL,DQL,DML等命令
      • 掌握數據庫事務概念
    • Java基礎語法
      • 多態機制
      • 基本容器使用(集合和數組等)
      • 泛型
      • 反射等技術

1.2 課程學習路線設計

學 悟 行

image-20230106111832154

二、全新JDBC技術概述

2.1 jdbc技術概念和理解

  • jdbc技術理解

    image-20230106111649011

    image-20230106111636653

  • jdbc概念總結

    1. jdbc是(Java Database Connectivity)單詞的縮寫,翻譯爲java連接數據庫
    2. jdbc是java程序連接數據庫的技術統稱
    3. jdbc由java語言的規範(接口)和各個數據庫廠商的實現驅動(jar)組成
    4. jdbc是一種典型的面向接口編程
    5. jdbc優勢
      1. 只需要學習jdbc規範接口的方法,即可操作所有的數據庫軟件
      2. 項目中期切換數據庫軟件,只需要更換對應的數據庫驅動jar包,不需要更改代碼

2.2 jdbc核心api和使用路線

  • jdbc技術組成

    1. jdk下jdbc規範接口, 存儲在java.sql和javax.sql包中的api

      爲了項目代碼的可移植性,可維護性,SUN公司從最初就制定了Java程序連接各種數據庫的統一接口規範。這樣的話,不管是連接哪一種DBMS軟件,Java代碼可以保持一致性。

    2. 各個數據庫廠商提供的驅動jar包

      因爲各個數據庫廠商的DBMS軟件各有不同,那麼內部如何通過sql實現增、刪、改、查等管理數據,只有這個數據庫廠商自己更清楚,因此把接口規範的實現交給各個數據庫廠商自己實現。

      jar包是什麼?

      java程序打成的一種壓縮包格式,你可以將這些jar包引入你的項目中,然後你可以使用這個java程序中類和方法以及屬性了!

  • 涉及具體核心類和接口

    • DriverManager
      1. 將第三方數據庫廠商的實現驅動jar註冊到程序中
      2. 可以根據數據庫連接信息獲取connection
    • Connection
      • 和數據庫建立的連接,在連接對象上,可以多次執行數據庫curd動作
      • 可以獲取statement和 preparedstatement,callablestatement對象
    • Statement | PreparedStatement | CallableStatement
      • 具體發送SQL語句到數據庫管理軟件的對象
      • 不同發送方式稍有不同! **preparedstatement **使用爲重點!
    • Result
      • 面向對象思維的產物(抽象成數據庫的查詢結果表)
      • 存儲DQL查詢數據庫結果的對象
      • 需要我們進行解析,獲取具體的數據庫數據
  • jdbc api使用路線

    image-20230106111746624

2.3爲什麼選擇全新 8+版本mysql-jdbc驅動?

  • 支持8.0+版本mysql數據管理軟件
    • mysql軟件知名版本迭代時間
版本號 迭代時間 大小
mysql-8.0.25 4月 30, 2021 435.7M
mysql-5.7.25 1月 10, 2019 387.7M
mysql-5.5.30 9月 19, 2012 201.5M
  • mysql 8.x版本數據庫性能提升介紹

    性能提升級。官方表示MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。

    MySQL 8.0 在讀/寫工作負載、IO 密集型工作負載、以及高競爭工作負載時相比MySQL5.7有更好的性能。

  • 支持java jdbc規範 4.2+版本新特性

    • java jdbc規範驅動版本和更新時間

      Year JDBC Version JSR Specification JDK Implementation

      2017 JDBC 4.3 JSR 221 Java SE 9

      **2014 JDBC 4.2 JSR 221 ** Java SE 8

      2011 JDBC 4.1 JSR 221 Java SE 7

      2006 JDBC 4.0 JSR 221 Java SE 6

      2001 JDBC 3.0 JSR 54 JDK 1.4

      1999 JDBC 2.1 JDK 1.2

      1997 JDBC 1.2 JDK 1.1

    • jdbc規範版本更新內容(瞭解)

**JDBC 4.3 中引入的主要新功能包括:**
添加了對分片的支持
添加了 java.sql.連接生成器接口
添加了 java.sql.ShardigKey 接口
添加了 java.sql.分片密鑰生成器接口
添加了.sql.XA 連接生成器接口
添加了 javax.sql.池連接生成器接口

**JDBC 4.2 中引入的主要新功能包括:**
添加了對引用光標的支持
添加了 java.sql.驅動程序操作接口
添加了.sql.SQLType 接口
添加 java.sql.JDBCType 枚舉
一些 JDBC 接口更改

**JDBC 4.1 中引入的主要新功能包括:**
添加了對“使用資源試用”語句的支持
增強的日期值和時間戳值
從 Java 對象到 JDBC 類型的其他映射
一些 JDBC 接口更改

**JDBC 4.0 中引入的主要新功能包括:**
自動加載爪哇.sql.驅動程序
數據類型支持
國家字符集轉換支持
支持

由於 JDBC 4.3 API 是向後兼容的,因此將 Java SE 9 或更高版本與 JDBC 4.2、4.1、4.0 
或 3.0 驅動程序一起使用沒有問題,只要您不使用 JDBC 4.3 API 中引入的新方法或類。
  • 支持 jdk1.8版本語法變更新特性

    Connector/J 8.0是專門爲在Java 8平臺上運行而創建的。

    衆所周知,Java8與早期的Java版本高度兼容,

    但還是存在少量不兼容性,所以,驅動技術版本,儘量選擇支持jdk 8.0+!

  • 支持全新的驅動api,增加自動時區選擇和默認utf-8編碼格式等配置

三、全新JDBC核心API

3.1 引入mysql-jdbc驅動jar

  1. 驅動jar版本選擇

    我們選擇版本 8.0.27版本

mysql版本 推薦驅動版本 備註
mysql 5.5.x 5.0.x com.mysql.jdbc.Driver
mysql 5.7.x 5.1.x com.mysql.jdbc.Driver
msyql 8.x 8.0.x 建議: 8.0.25+省略時區設置com.mysql.cj.jdbc.Driver
  1. java工程導入依賴

    1. 項目創建lib文件夾

    2. 導入驅動依賴jar包

    3. jar包右鍵-添加爲項目依賴

      img

3.2 jdbc基本使用步驟分析(6步)

  1. 註冊驅動
  2. 獲取連接
  3. 創建發送sql語句對象
  4. 發送sql語句,並獲取返回結果
  5. 結果集解析
  6. 資源關閉

3.3基於statement演示查詢

  • 準備數據庫數據
CREATE DATABASE atguigu;

USE atguigu;

CREATE TABLE t_user(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用戶主鍵',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
   PASSWORD VARCHAR(64) NOT NULL COMMENT '密碼',
   nickname VARCHAR(20) NOT NULL COMMENT '暱稱');
   
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
  ('root','123456','經理'),('admin','666666','管理員');
  • 查詢目標

    查詢全部用戶信息,進行控制檯輸出

    img

  • 基於statement實現查詢(演示步驟)

/**
 * Description: 利用jdbc技術,完成用戶數據查詢工作
 *
 * TODO: 步驟總結 (6步)
 *    1. 註冊驅動
 *    2. 獲取連接
 *    3. 創建statement
 *    4. 發送SQL語句,並獲取結果
 *    5. 結果集解析
 *    6. 關閉資源
 */
public class JdbcBasePart {

    public static void main(String[] args) throws SQLException {

        //1.註冊驅動
        /**
         * TODO: 注意
         *   Driver -> com.mysql.cj.jdbc.Driver
         */
        DriverManager.registerDriver(new Driver());

        //2.獲取連接
        /**
         * TODO: 注意
         *   面向接口編程
         *   java.sql 接口 = 實現類
         *   connection 使用java.sql.Connection接口接收
         */
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu",
                "root",
                "root");

        //3.創建小車
        Statement statement = connection.createStatement();

        //4.發送SQL語句
        String sql = "select id,account,password,nickname from t_user ;";
        ResultSet resultSet =  statement.executeQuery(sql);

        //5.結果集解析
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String account = resultSet.getString("account");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            System.out.println(id+"::"+account+"::"+password+"::"+nickname);
        }

        //6.關閉資源  【先開後關】
        resultSet.close();
        statement.close();
        connection.close();

    }

}

3.4基於statement方式問題

  • 本案例目標

    • 明確jdbc流程和詳細講解使用(註冊驅動,獲取連接,發送語句,結果解析)
    • 發現問題,引出preparedstatement
  • 準備數據庫數據

    上個案例相同的數據庫

CREATE DATABASE atguigu;

USE atguigu;

CREATE TABLE t_user(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用戶主鍵',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
   PASSWORD VARCHAR(64) NOT NULL COMMENT '密碼',
   nickname VARCHAR(20) NOT NULL COMMENT '暱稱');
   
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
  ('root','123456','經理'),('admin','666666','管理員');
  • 演示目標

    模擬登錄,控制檯輸入賬號和密碼,判斷是否登陸成功成功!

    img

  • 基於statement實現模擬登錄

/**
 * Description: 輸入賬號密碼,模擬用戶登錄!
 */
public class JdbcStatementLoginPart {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        //1.輸入賬號和密碼
        Scanner scanner = new Scanner(System.in);
        String account = scanner.nextLine();
        String password = scanner.nextLine();
        scanner.close();

        //2.jdbc的查詢使用
        // 註冊驅動
        // DriverManager.registerDriver(new Driver());
        // 問題:註冊兩次驅動
        // 1. DriverManager.registerDriver() 方法本身會註冊一次
        // 2. Driver.static{ DriverManager.registerDriver(new Driver())} 靜態代碼塊也會註冊一次
        // 解決:只想註冊一次驅動
        // 只觸發靜態代碼塊即可!Driver
        /**
         * 類加載: java文件 -> 編譯 -> 【 class字節碼文件 -->  類加載 --> jvm虛擬中  --> Class對象】
         * 類加載具體步驟:  加載 【class文件轉成對象加載到虛擬機中】->
         *                連接 【驗證(檢查類文件) -> 準備 (靜態變量賦默認值) -> 解析 (調用靜態代碼塊) 】 ->
         *                初始化 -> (賦真實值)
         * 以下7種方式會觸發類加載:
         *    1. new關鍵字
         *    2. 調用靜態屬性
         *    3. 調用靜態方法
         *    4. 接口 包含1.8 新特性 default關鍵字
         *    5. 反射 【Class.forName() 類名.class】
         *    6. 子類調用會觸發父類的靜態代碼塊
         *    7. 觸發類的入口方法main
         */
        //註冊一次驅動
        Class.forName("com.mysql.cj.jdbc.Driver");



        /**
         * 重寫: 爲了子類擴展父類的方法!父類也間接的規範了子類方法的參數和返回!
         * 重載: 重載一般應用在第三方的工具類上,爲了方便用戶多種方式傳遞參數形式!簡化形式!
         */
        /**
         * 三個參數:
         *    String URL: 連接數據庫地址
         *    String user: 連接數據庫用戶名
         *    String password: 連接數據庫用戶對應的密碼
         * 數據庫URL語法:
         *    JDBC:
         *        ip port
         *        jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 數據庫名
         *        jdbc:mysql://localhost:3306/day01
         *        192.168.33.45
         *        jdbc:mysql://192.168.33.45/3306/day01
         *        當前電腦的省略寫法! 注意:本機和端口3306
         *        jdbc:mysql://localhost:3306/day01 = jdbc:mysql:///day01
         *
         * 兩個參數:
         *     String URL : 寫法還是jdbc的路徑寫法!
         *     Properties : 就是一個參數封裝容器!至少要包含 user / password key!存儲連接賬號信息!
         *
         * 一個參數:
         *    String URL: URl可以攜帶目標地址,可以通過?分割,在後面key=value&key=value形式傳遞參數
         *                jdbc:mysql:///day01?user=root&password=123456
         * 擴展路徑參數(瞭解):
         *    serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
         *
         */
        //獲取連接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

        //固定方法固定劑
        //創建statement
        Statement statement = connection.createStatement();

        //執行SQL語句 [動態SQL語句,需要字符串拼接]
        String sql = "select * from t_user where account = '"+account+"' and password = '"+password+"' ;";


        /**
         *  ResultSet 結果集對象 = executeQuery(DQL語句)
         *  int       響應行數  = executeUpdate(非DQL語句)
         */
        ResultSet resultSet = statement.executeQuery(sql);


        //ResultSet == 小海豚  你必須有面向對象的思維:Java是面向對象編程的語言 OOP!
        /**
         *
         * TODO:1.需要理解ResultSet的數據結構和小海豚查詢出來的是一樣,需要在腦子裏構建結果表!
         * TODO:2.有一個光標指向的操作數據行,默認指向第一行的上邊!我們需要移動光標,指向行,在獲取列即可!
         *        boolean = next()
         *              false: 沒有數據,也不移動了!
         *              true:  有更多行,並且移動到下一行!
         *       推薦:推薦使用if 或者 while循環,嵌套next方法,循環和判斷體內獲取數據!
         *       if(next()){獲取列的數據!} ||  while(next()){獲取列的數據!}
         *
         *TODO:3.獲取當前行列的數據!
         *         get類型(int columnIndex | String columnLabel)
         *        列名獲取  //lable 如果沒有別名,等於列名, 有別名label就是別名,他就是查詢結果的標識!
         *        列的角標  //從左到右 從1開始! 數據庫全是從1開始!
         */

        //進行結果集對象解析
        if (resultSet.next()){
            //只要向下移動,就是有數據 就是登錄成功!
            System.out.println("登錄成功!");
        }else{
            System.out.println("登錄失敗!");
        }

        //關閉資源
        resultSet.close();
        statement.close();
        connection.close();
    }

}
  • 存在問題

    1. SQL語句需要字符串拼接,比較麻煩

    2. 只能拼接字符串類型,其他的數據庫類型無法處理

    3. 可能發生注入攻擊

      動態值充當了SQL語句結構,影響了原有的查詢結果!

3.5 基於preparedStatement方式優化

利用preparedStatement解決上述案例注入攻擊SQL語句拼接問題! (重點掌握)

/**
 * @Author 趙偉風
 * Description: 使用預編譯Statement解決注入攻擊問題
 */
public class JdbcPreparedStatementLoginPart {


    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        //1.輸入賬號和密碼
        Scanner scanner = new Scanner(System.in);
        String account = scanner.nextLine();
        String password = scanner.nextLine();
        scanner.close();

        //2.jdbc的查詢使用
        //註冊驅動
        Class.forName("com.mysql.cj.jdbc.Driver");

        //獲取連接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

        //創建preparedStatement
        //connection.createStatement();
        //TODO 需要傳入SQL語句結構
        //TODO 要的是SQL語句結構,動態值的部分使用 ? ,  佔位符!
        //TODO ?  不能加 '?'  ? 只能替代值,不能替代關鍵字和容器名
        String sql = "select * from t_user where account = ? and password = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //佔位符賦值
        //給佔位符賦值! 從左到右,從1開始!
        /**
         *  int 佔位符的下角標
         *  object 佔位符的值
         */
        preparedStatement.setObject(2,password);
        preparedStatement.setObject(1,account);

        //這哥們內部完成SQL語句拼接!
        //執行SQL語句即可
        ResultSet resultSet = preparedStatement.executeQuery();
        //preparedStatement.executeUpdate()

        //進行結果集對象解析
        if (resultSet.next()){
            //只要向下移動,就是有數據 就是登錄成功!
            System.out.println("登錄成功!");
        }else{
            System.out.println("登錄失敗!");
        }

        //關閉資源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

}

img

3.6基於preparedStatement演示curd

  • 數據庫數據插入
/**
 * 插入一條用戶數據!
 * 賬號: test
 * 密碼: test
 * 暱稱: 測試
 */
@Test
public void testInsert() throws Exception{

    //註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");

    //獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

    //TODO: 切記, ? 只能代替 值!!!!!  不能代替關鍵字 特殊符號 容器名
    String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //佔位符賦值
    preparedStatement.setString(1, "test");
    preparedStatement.setString(2, "test");
    preparedStatement.setString(3, "測試");

    //發送SQL語句
    int rows = preparedStatement.executeUpdate();

    //輸出結果
    System.out.println(rows);

    //關閉資源close
    preparedStatement.close();
    connection.close();
}
  • 數據庫數據修改
/**
 * 修改一條用戶數據!
 * 修改賬號: test的用戶,將nickname改爲tomcat
 */
@Test
public void testUpdate() throws Exception{

    //註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");

    //獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

    //TODO: 切記, ? 只能代替 值!!!!!  不能代替關鍵字 特殊符號 容器名
    String sql = "update t_user set nickname = ? where account = ? ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //佔位符賦值
    preparedStatement.setString(1, "tomcat");
    preparedStatement.setString(2, "test");

    //發送SQL語句
    int rows = preparedStatement.executeUpdate();

    //輸出結果
    System.out.println(rows);

    //關閉資源close
    preparedStatement.close();
    connection.close();
}
  • 數據庫數據刪除
/**
 * 刪除一條用戶數據!
 * 根據賬號: test
 */
@Test
public void testDelete() throws Exception{

    //註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");

    //獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

    //TODO: 切記, ? 只能代替 值!!!!!  不能代替關鍵字 特殊符號 容器名
    String sql = "delete from t_user where account = ? ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //佔位符賦值
    preparedStatement.setString(1, "test");

    //發送SQL語句
    int rows = preparedStatement.executeUpdate();

    //輸出結果
    System.out.println(rows);

    //關閉資源close
    preparedStatement.close();
    connection.close();
}
  • 數據庫數據查詢
/**
 * 查詢全部數據!
 *   將數據存到List<Map>中
 *   map -> 對應一行數據
 *      map key -> 數據庫列名或者別名
 *      map value -> 數據庫列的值
 * TODO: 思路分析
 *    1.先創建一個List<Map>集合
 *    2.遍歷resultSet對象的行數據
 *    3.將每一行數據存儲到一個map對象中!
 *    4.將對象存到List<Map>中
 *    5.最終返回
 *
 * TODO:
 *    初體驗,結果存儲!
 *    學習獲取結果表頭信息(列名和數量等信息)
 */
@Test
public void testQueryMap() throws Exception{

    //註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");

    //獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

    //TODO: 切記, ? 只能代替 值!!!!!  不能代替關鍵字 特殊符號 容器名
    String sql = "select id,account,password,nickname from t_user ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //佔位符賦值 本次沒有佔位符,省略

    //發送查詢語句
    ResultSet resultSet = preparedStatement.executeQuery();

    //創建一個集合
    List<Map> mapList = new ArrayList<>();

    //獲取列信息對象
    ResultSetMetaData metaData = resultSet.getMetaData();
    int columnCount = metaData.getColumnCount();
    while (resultSet.next()) {
        Map map = new HashMap();
        for (int i = 1; i <= columnCount; i++) {
            map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
        }
        mapList.add(map);
    }

    System.out.println(mapList);

    //關閉資源close
    preparedStatement.close();
    connection.close();
    resultSet.close();
}

3.7 preparedStatement使用方式總結

  • 使用步驟總結
//1.註冊驅動

//2.獲取連接

//3.編寫SQL語句

//4.創建preparedstatement並且傳入SQL語句結構

//5.佔位符賦值

//6.發送SQL語句,並且獲取結果 

//7.結果集解析

//8.關閉資源
  • 使用API總結
//1.註冊驅動
方案1: 調用靜態方法,但是會註冊兩次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射觸發
Class.forName("com.mysql.cj.jdbc.Driver");

//2.獲取連接

Connection connection = DriverManager.getConnection();

3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=賬號&password=密碼 )

//3.創建statement

//靜態
Statement statement = connection.createStatement();
//預編譯
PreparedStatement preparedstatement = connection.preparedStatement(sql語句結構);

//4.佔位符賦值

preparedstatement.setObject(?的位置 從左到右 從1開始,值)

//5.發送sql語句獲取結果

int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL

//6.查詢結果集解析

//移動光標指向行數據 next();  if(next())  while(next())
//獲取列的數據即可   get類型(int 列的下角標 從1開始 | int 列的label (別名或者列名))
//獲取列的信息   getMetadata(); ResultsetMetaData對象 包含的就是列的信息
                getColumnCount(); | getCloumnLebal(index)
//7.關閉資源
close(); 

四、全新JDBC擴展提升

4.1 自增長主鍵回顯實現

  • 功能需求

    1. java程序獲取插入數據時mysql維護自增長維護的主鍵id值,這就是主鍵回顯
    2. 作用: 在多表關聯插入數據時,一般主表的主鍵都是自動生成的,所以在插入數據之前無法知道這條數據的主鍵,但是從表需要在插入數據之前就綁定主表的主鍵,這是可以使用主鍵回顯技術:
  • 功能實現

    繼續沿用之前的表數據

/**
 * 返回插入的主鍵!
 * 主鍵:數據庫幫助維護的自增長的整數主鍵!
 * @throws Exception
 */
@Test
public void  returnPrimaryKey() throws Exception{

    //1.註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
    //3.編寫SQL語句結構
    String sql = "insert into t_user (account,password,nickname) values (?,?,?);";
    //4.創建預編譯的statement,傳入SQL語句結構
    /**
     * TODO: 第二個參數填入 1 | Statement.RETURN_GENERATED_KEYS
     *       告訴statement攜帶回數據庫生成的主鍵!
     */
    PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    //5.佔位符賦值
    statement.setObject(1,"towgog");
    statement.setObject(2,"123456");
    statement.setObject(3,"二狗子");
    //6.執行SQL語句 【注意:不需要傳入SQL語句】 DML
    int i = statement.executeUpdate();
    //7.結果集解析
    System.out.println("i = " + i);

    //一行一列的數據!裏面就裝主鍵值!
    ResultSet resultSet = statement.getGeneratedKeys();
    resultSet.next();
    int anInt = resultSet.getInt(1);
    System.out.println("anInt = " + anInt);


    //8.釋放資源
    statement.close();
    connection.close();
}

4.2 批量數據插入性能提升

  • 功能需求
    1. 批量數據插入優化
    2. 提升大量數據插入效率
  • 功能實現
/**
 *
 * 批量細節:
 *    1.url?rewriteBatchedStatements=true
 *    2.insert 語句必須使用 values
 *    3.語句後面不能添加分號;
 *    4.語句不能直接執行,每次需要裝貨  addBatch() 最後 executeBatch();
 *
 * 批量插入優化!
 * @throws Exception
 */
@Test
public void  batchInsertYH() throws Exception{

    //1.註冊驅動
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.獲取連接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true",
            "root","root");
    //3.編寫SQL語句結構
    String sql = "insert into t_user (account,password,nickname) values (?,?,?)";
    //4.創建預編譯的statement,傳入SQL語句結構
    /**
     * TODO: 第二個參數填入 1 | Statement.RETURN_GENERATED_KEYS
     *       告訴statement攜帶回數據庫生成的主鍵!
     */
    long start = System.currentTimeMillis();
    PreparedStatement statement = connection.prepareStatement(sql);
    for (int i = 0; i < 10000; i++) {

        //5.佔位符賦值
        statement.setObject(1,"ergouzi"+i);
        statement.setObject(2,"lvdandan");
        statement.setObject(3,"驢蛋蛋"+i);
        //6.裝車
        statement.addBatch();
    }

    //發車! 批量操作!
    statement.executeBatch();

    long end = System.currentTimeMillis();

    System.out.println("消耗時間:"+(end - start));


    //7.結果集解析

    //8.釋放資源
    connection.close();
}

4.3 jdbc中數據庫事務實現

  • 章節目標

    使用jdbc代碼,添加數據庫事務動作!

    開啓事務

    事務提交 / 事務回滾

  • 事務概念回顧

// 事務概念
   數據庫事務就是一種SQL語句執行的緩存機制,不會單條執行完畢就更新數據庫數據,最終根據緩存內的多條語句執行結果統一判定!
   一個事務內所有語句都成功及事務成功,我們可以觸發commit提交事務來結束事務,更新數據!
   一個事務內任意一條語句失敗,及事務失敗,我們可以觸發rollback回滾結束事務,
   數據回到事務之前狀態!
   
   舉個例子: 
           臨近高考,你好喫懶做,偶爾還瞎花錢,父母也只會說'你等着!',待到高考完畢!
           成績600+,翻篇,慶祝!
           成績200+,翻舊賬,男女混合雙打!
           
//優勢
   允許我們在失敗情況下,數據迴歸到業務之前的狀態! 
   
//場景
   **一個業務****涉及****多條修改****數據庫語句!**
   例如: 經典的轉賬案例,轉賬業務(加錢和減錢)   
         批量刪除(涉及多個刪除)
         批量添加(涉及多個插入)     
         
// 事務特性
  1. 原子性(Atomicity)原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。 

  2. 一致性(Consistency)事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。

  3. 隔離性(Isolation)事務的隔離性是指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。

  4. 持久性(Durability)持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其他操作和數據庫故障不應該對其有任何影響

// 事務類型
  
  自動提交 : 每條語句自動存儲一個事務中,執行成功自動提交,執行失敗自動回滾! (MySQL)
  手動提交:  手動開啓事務,添加語句,手動提交或者手動回滾即可!
  
// sql開啓事務方式
   針對自動提交: 關閉自動提交即可,多條語句添加以後,最終手動提交或者回滾! (推薦)
     
      SET autocommit = off; //關閉當前連接自動事務提交方式
      # 只有當前連接有效
      # 編寫SQL語句即可
      SQL
      SQL
      SQL
      #手動提交或者回滾 【結束當前的事務】
      COMMIT / ROLLBACK ;  
     
   手動開啓事務: 開啓事務代碼,添加SQL語句,事務提交或者事務回滾! (不推薦)

// 呼應jdbc技術
 
  try{
    connection.setAutoCommit(false); //關閉自動提交了
    
    //注意,只要當前connection對象,進行數據庫操作,都不會自動提交事務
    //數據庫動作!
    //statement - 單一的數據庫動作 c u r d 
    
    connection.commit();
  }catch(Execption e){
    connection.rollback();
  }
  • 數據庫表數據
-- 繼續在atguigu的庫中創建銀行表
CREATE TABLE t_bank(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '賬號主鍵',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
   money  INT UNSIGNED COMMENT '金額,不能爲負值') ;
   
INSERT INTO t_bank(account,money) VALUES
  ('ergouzi',1000),('lvdandan',1000);
  • 代碼結構設計

    img

  • jdbc事務實現

    • 測試類
/**
 * Description: 測試類
 */
public class BankTest {

    @Test
    public void testBank() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("ergouzi", "lvdandan",
                500);
    }

}
- BankService
/**
 * Description: bank表業務類,添加轉賬業務
 */
public class BankService {


    /**
     * 轉賬業務方法
     * @param addAccount  加錢賬號
     * @param subAccount  減錢賬號
     * @param money  金額
     */
    public void transfer(String addAccount,String subAccount, int money) throws ClassNotFoundException, SQLException {

        System.out.println("addAccount = " + addAccount + ", subAccount = " + subAccount + ", money = " + money);

        //註冊驅動
        Class.forName("com.mysql.cj.jdbc.Driver");

        //獲取連接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

        int flag = 0;

        //利用try代碼塊,調用dao
        try {
            //開啓事務(關閉事務自動提交)
            connection.setAutoCommit(false);

            BankDao bankDao = new BankDao();
            //調用加錢 和 減錢
            bankDao.addMoney(addAccount,money,connection);
            System.out.println("--------------");
            bankDao.subMoney(subAccount,money,connection);
            flag = 1;
            //不報錯,提交事務
            connection.commit();
        }catch (Exception e){

            //報錯回滾事務
            connection.rollback();
            throw e;
        }finally {
            connection.close();
        }

        if (flag == 1){
            System.out.println("轉賬成功!");
        }else{
            System.out.println("轉賬失敗!");
        }
    }

}
- BankDao
/**
 * Description: 數據庫訪問dao類
 */
public class BankDao {

    /**
     * 加錢方法
     * @param account
     * @param money
     * @param connection 業務傳遞的connection和減錢是同一個! 纔可以在一個事務中!
     * @return 影響行數
     */
    public int addMoney(String account, int money,Connection connection) throws ClassNotFoundException, SQLException {


        String sql = "update t_bank set money = money + ? where account = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //佔位符賦值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //發送SQL語句
        int rows = preparedStatement.executeUpdate();

        //輸出結果
        System.out.println("加錢執行完畢!");

        //關閉資源close
        preparedStatement.close();

        return rows;
    }

    /**
     * 減錢方法
     * @param account
     * @param money
     * @param connection 業務傳遞的connection和加錢是同一個! 纔可以在一個事務中!
     * @return 影響行數
     */
    public int subMoney(String account, int money,Connection connection) throws ClassNotFoundException, SQLException {

        String sql = "update t_bank set money = money - ? where account = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //佔位符賦值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //發送SQL語句
        int rows = preparedStatement.executeUpdate();

        //輸出結果
        System.out.println("減錢執行完畢!");

        //關閉資源close
        preparedStatement.close();

        return rows;
    }
}

五、國貨之光Druid連接池技術使用

5.1 連接性能消耗問題分析

​ **貪污和浪費是極大的犯罪 **

- 毛爺爺

connection可以複用! (年紀輕輕你就範了 浪費連接的罪啊!)

5.2 數據庫連接池作用

總結缺點:
(1)不使用數據庫連接池,每次都通過DriverManager獲取新連接,用完直接拋棄斷開,連接的利用率太低,太浪費。
(2)對於數據庫服務器來說,壓力太大了。我們數據庫服務器和Java程序對連接數也無法控制,很容易導致數據庫服務器崩潰。

我們就希望能管理連接。

  • 我們可以建立一個連接池,這個池中可以容納一定數量的連接對象,一開始,我們可以先替用戶先創建好一些連接對象,等用戶要拿連接對象時,就直接從池中拿,不用新建了,這樣也可以節省時間。然後用戶用完後,放回去,別人可以接着用。
  • 可以提高連接的使用率。當池中的現有的連接都用完了,那麼連接池可以向服務器申請新的連接放到池中。
  • 直到池中的連接達到“最大連接數”,就不能在申請新的連接了,如果沒有拿到連接的用戶只能等待。

5.3 市面常見連接池產品和對比

JDBC 的數據庫連接池使用 javax.sql.DataSource接口進行規範,所有的第三方連接池都實現此接口,自行添加具體實現!也就是說, 所有連接池獲取連接的和回收連接方法都一樣,不同的只有性能和擴展功能!

  • DBCP 是Apache提供的數據庫連接池,速度相對c3p0較快,但因自身存在BUG
  • C3P0 是一個開源組織提供的一個數據庫連接池,速度相對較慢,穩定性還可以
  • Proxool 是sourceforge下的一個開源項目數據庫連接池,有監控連接池狀態的功能,穩定性較c3p0差一點
  • Druid 是阿里提供的數據庫連接池,據說是集DBCP 、C3P0 、Proxool 優點於一身的數據庫連接池,妥妥國貨之光!!!!

img

img

5.4國貨之光druid連接池使用

記得導入druid工具類jar

  • 硬編碼方式(瞭解,不推薦)
/**
 * 創建druid連接池對象,使用硬編碼進行核心參數設置!
 *   必須參數: 賬號
 *             密碼
 *             url
 *             driverClass
 *   非必須參數:
 *           初始化個數
 *           最大數量等等  不推薦設置
 */
@Test
public void druidHard() throws SQLException {

   DruidDataSource dataSource = new DruidDataSource();

   //設置四個必須參數
   dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
   dataSource.setUsername("root");
   dataSource.setPassword("root");
   dataSource.setUrl("jdbc:mysql:///day01");

   //獲取連接
   Connection connection = dataSource.getConnection();
   // JDBC的步驟
   //回收連接
   connection.close();
}
  • 軟編碼方式

    • 外部配置

      存放在src/druid.properties

# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
- druid聲明代碼
/**
 * 不直接在java代碼編寫配置文件!
 * 利用工廠模式,傳入配置文件對象,創建連接池!
 * @throws Exception
 */
@Test
public void druidSoft() throws Exception {
    Properties properties = new Properties();
    InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
    properties.load(ips);
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
}
  • druid配置(瞭解)
配置 缺省 說明
name 配置這個屬性的意義在於,如果存在多個數據源,監控的時候可以通過名字來區分開來。 如果沒有配置,將會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl 連接數據庫的url,不同數據庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 連接數據庫的用戶名
password 連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細看這裏:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName 根據url自動識別 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然後選擇相應的driverClassName(建議配置下)
initialSize 0 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時
maxActive 8 最大連接池數量
maxIdle 8 已經不再使用,配置了也沒效果
minIdle 最小連接池數量
maxWait 獲取連接時最大等待時間,單位毫秒。配置了maxWait之後,缺省啓用公平鎖,併發效率會有所下降,如果需要可以通過配置useUnfairLock屬性爲true使用非公平鎖。
poolPreparedStatements false 是否緩存preparedStatement,也就是PSCache。PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。
maxOpenPreparedStatements -1 要啓用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改爲true。在Druid中,不會存在Oracle下PSCache佔用內存過多的問題,可以把這個數值配置大一些,比如說100
validationQuery 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。
testOnBorrow true 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnReturn false 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能
testWhileIdle false 建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
timeBetweenEvictionRunsMillis 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
numTestsPerEvictionRun 不再使用,一個DruidDataSource只支持一個EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理連接初始化的時候執行的sql
exceptionSorter 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄連接
filters 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall
proxyFilters 類型是List,如果同時配置了filters和proxyFilters,是組合關係,並非替換關係

六、全新JDBC使用優化以及工具類封裝

6.1 jdbc工具類封裝v1.0

我們封裝一個工具類,內部包含連接池對象,同時對外提供連接的方法和回收連接的方法!

外部配置文件

位置: src/druid.properties

# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

工具類代碼

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCToolsVersion1 {
    private static DataSource ds;
    static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();//這麼寫,不能保證同一個線程,兩次getConnection()得到的是同一個Connection對象
                            //如果不能保證是同一個連接對象,就無法保證事務的管理
    }

    public static void free(Connection conn) throws SQLException {
        conn.setAutoCommit(true);
        conn.close();//還給連接池
    }
}

6.2 jdbc工具類封裝v.2.0

優化工具類v1.0版本,考慮事務的情況下!如何一個線程的不同方法獲取同一個連接!

ThreadLocal的介紹:

JDK 1.2的版本中就提供java.lang.ThreadLocal,爲解決多線程程序的併發問題提供了一種新的思路。
使用這個工具類可以很簡潔地編寫出優美的多線程程序。通常用來在在多線程中管理共享數據庫連接、Session等

ThreadLocal用於保存某個線程共享變量,原因是在Java中,每一個線程對象中都有一個ThreadLocalMap<ThreadLocal, Object>,其key就是一個ThreadLocal,而Object即爲該線程的共享變量。而這個map是通過ThreadLocal的set和get方法操作的。對於同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變量,而不會影響其他線程的變量。

1、ThreadLocal對象.get: 獲取ThreadLocal中當前線程共享變量的值。

2、ThreadLocal對象.set: 設置ThreadLocal中當前線程共享變量的值。

3、ThreadLocal對象.remove: 移除ThreadLocal中當前線程共享變量的值。

v2.0版本工具類

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
這個工具類的作用就是用來給所有的SQL操作提供“連接”,和釋放連接。
這裏使用ThreadLocal的目的是爲了讓同一個線程,在多個地方getConnection得到的是同一個連接。
這裏使用DataSource的目的是爲了(1)限制服務器的連接的上限(2)連接的重用性等
 */
public class JDBCTools {
    private static DataSource ds;
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
         Connection connection = tl.get();
         if(connection  == null){//當前線程還沒有拿過連接,就給它從數據庫連接池拿一個
             connection = ds.getConnection();
             tl.set(connection);
         }
         return connection;
    }

    public static void free() throws SQLException {
        Connection connection = tl.get();
        if(connection != null){
            tl.remove();
            connection.setAutoCommit(true);//避免還給數據庫連接池的連接不是自動提交模式(建議)
            connection.close();
        }
    }
}

**注意: **修改轉賬業務,使用此工具類

6.3 高級應用層封裝BaseDao

基本上每一個數據表都應該有一個對應的DAO接口及其實現類,發現對所有表的操作(增、刪、改、查)代碼重複度很高,所以可以抽取公共代碼,給這些DAO的實現類可以抽取一個公共的父類,我們稱爲BaseDao

public abstract class BaseDao {
    /*
    通用的增、刪、改的方法
    String sql:sql
    Object... args:給sql中的?設置的值列表,可以是0~n
     */
    protected int update(String sql,Object... args) throws SQLException {
//        創建PreparedStatement對象,對sql預編譯
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //設置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
            }
        }

        //執行sql
        int len = ps.executeUpdate();
        ps.close();
        //這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
        //沒有開啓事務的話,直接回收關閉即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }

    /*
    通用的查詢多個Javabean對象的方法,例如:多個員工對象,多個部門對象等
    這裏的clazz接收的是T類型的Class對象,
    如果查詢員工信息,clazz代表Employee.class,
    如果查詢部門信息,clazz代表Department.class,
     */
    protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
        //        創建PreparedStatement對象,對sql預編譯
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //設置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        獲取結果集的元數據對象。
        元數據對象中有該結果集一共有幾列、列名稱是什麼等信息
         */
         ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//獲取結果集列數

        //遍歷結果集ResultSet,把查詢結果中的一條一條記錄,變成一個一個T 對象,放到list中。
        while(res.next()){
            //循環一次代表有一行,代表有一個T對象
            T t = clazz.newInstance();//要求這個類型必須有公共的無參構造

            //把這條記錄的每一個單元格的值取出來,設置到t對象對應的屬性中。
            for(int i=1; i<=columnCount; i++){
                //for循環一次,代表取某一行的1個單元格的值
                Object value = res.getObject(i);

                //這個值應該是t對象的某個屬性值
                //獲取該屬性對應的Field對象
//                String columnName = metaData.getColumnName(i);//獲取第i列的字段名
                String columnName = metaData.getColumnLabel(i);//獲取第i列的字段名或字段的別名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//這麼做可以操作private的屬性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
        //沒有開啓事務的話,直接回收關閉即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }

    protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if(list == null || list.size() == 0){
            return null;
        }
        return list.get(0);
    }
}

七、基於CMS項目JDBC實戰練習

7.1 cms項目介紹和導入

  • 項目介紹

    利用JavaSE技術,進行控制檯輸出的客戶管理系統! 主要功能讓包含客戶展示,客戶刪除,客戶添加,客戶修改,退出系統!

    添加客戶

    img

    修改客戶

    img

    展示客戶列表

    img

    刪除客戶

    img

    退出系統

    img

  • 項目導入

    1. 打開項目

      img

    2. 配置jdk

      img

      img

7.2基於cms項目添加數據庫相關配置

  • 準備數據庫腳本
-- 員工表

CREATE TABLE t_customer(
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客戶主鍵',
  NAME VARCHAR(20)  COMMENT '客戶名稱',
  gender VARCHAR(4) COMMENT '客戶性別',
  age INT  COMMENT '客戶年齡',
  salary DOUBLE(8,1) COMMENT '客戶工資',
  phone VARCHAR(11) COMMENT '客戶電話')
  • 添加配置文件

    位置: src下, druid.properties

# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
  • 導入jdbcv2.0工具類
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
這個工具類的作用就是用來給所有的SQL操作提供“連接”,和釋放連接。
這裏使用ThreadLocal的目的是爲了讓同一個線程,在多個地方getConnection得到的是同一個連接。
這裏使用DataSource的目的是爲了(1)限制服務器的連接的上限(2)連接的重用性等
 */
public class JDBCTools {
    private static DataSource ds;
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
         Connection connection = tl.get();
         if(connection  == null){//當前線程還沒有拿過連接,就給它從數據庫連接池拿一個
             connection = ds.getConnection();
             tl.set(connection);
         }
         return connection;
    }

    public static void free() throws SQLException {
        Connection connection = tl.get();
        if(connection != null){
            tl.remove();
            connection.setAutoCommit(true);//避免還給數據庫連接池的連接不是自動提交模式(建議)
            connection.close();
        }
    }
}
  • 導入baseDao工具類
public abstract class BaseDao {
    /*
    通用的增、刪、改的方法
    String sql:sql
    Object... args:給sql中的?設置的值列表,可以是0~n
     */
    protected int update(String sql,Object... args) throws SQLException {
//        創建PreparedStatement對象,對sql預編譯
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //設置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
            }
        }

        //執行sql
        int len = ps.executeUpdate();
        ps.close();
        //這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
        //沒有開啓事務的話,直接回收關閉即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }

    /*
    通用的查詢多個Javabean對象的方法,例如:多個員工對象,多個部門對象等
    這裏的clazz接收的是T類型的Class對象,
    如果查詢員工信息,clazz代表Employee.class,
    如果查詢部門信息,clazz代表Department.class,
     */
    protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
        //        創建PreparedStatement對象,對sql預編譯
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //設置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        獲取結果集的元數據對象。
        元數據對象中有該結果集一共有幾列、列名稱是什麼等信息
         */
         ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//獲取結果集列數

        //遍歷結果集ResultSet,把查詢結果中的一條一條記錄,變成一個一個T 對象,放到list中。
        while(res.next()){
            //循環一次代表有一行,代表有一個T對象
            T t = clazz.newInstance();//要求這個類型必須有公共的無參構造

            //把這條記錄的每一個單元格的值取出來,設置到t對象對應的屬性中。
            for(int i=1; i<=columnCount; i++){
                //for循環一次,代表取某一行的1個單元格的值
                Object value = res.getObject(i);

                //這個值應該是t對象的某個屬性值
                //獲取該屬性對應的Field對象
//                String columnName = metaData.getColumnName(i);//獲取第i列的字段名
                String columnName = metaData.getColumnLabel(i);//獲取第i列的字段名或字段的別名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//這麼做可以操作private的屬性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
        //沒有開啓事務的話,直接回收關閉即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }

    protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if(list == null || list.size() == 0){
            return null;
        }
        return list.get(0);
    }
}

7.3 基於cms項目實戰

  • customerService
package com.atguigu.cms.service;

import com.atguigu.cms.dao.CustomerDao;
import com.atguigu.cms.javabean.Customer;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 這是一個具有管理功能的功能類. 內部數據不允許外部隨意修改, 具有更好的封裝性.
 */
public class CustomerService {


    private CustomerDao customerDao = new CustomerDao();

    /**
     * 用途:返回所有客戶對象
     * 返回:集合
     */
    public List<Customer> getList() {

        try {
            return customerDao.queryList();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }



    /**
     * 用途:添加新客戶
     * 參數:customer指定要添加的客戶對象
     */
    public void addCustomer(Customer customer)  {
        try {
            customerDao.insertCustomer(customer);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用途:返回指定id的客戶對象記錄
     * 參數: id 就是要獲取的客戶的id號.
     * 返回:封裝了客戶信息的Customer對象
     */
    public Customer getCustomer(int id) {

        try {
            return customerDao.queryById(id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 修改指定id號的客戶對象的信息
     * @param id 客戶id
     * @param cust 對象
     * @return 修改成功返回true, false表明指定id的客戶未找到
     */
    public boolean modifyCustomer(int id, Customer cust)  {
        int rows = 0;
        try {
            rows = customerDao.updateCustomer(cust);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return rows > 0;
    }

    /**
     * 用途:刪除指定id號的的客戶對象記錄
     * 參數: id 要刪除的客戶的id號
     * 返回:刪除成功返回true;false表示沒有找到
     */
    public boolean removeCustomer(int id) {
        int rows = 0;
        try {
            rows = customerDao.deleteCustomer(id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return rows > 0;
    }

}
  • customerDao
package com.atguigu.cms.dao;

import com.atguigu.cms.javabean.Customer;
import com.atguigu.cms.utils.BaseDao;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Description: 客戶進行數據庫操作的類
 */
public class CustomerDao extends BaseDao {


    public List<Customer> queryList() throws Exception {
        ArrayList<Customer> list = query(Customer.class, "select * from t_customer");
        return list;
    }

    public void insertCustomer(Customer customer) throws SQLException {
       int rows = update("insert into t_customer(name,gender,age,salary,phone) values (?,?,?,?,?)",
                customer.getName(), customer.getGender(),customer.getAge(),customer.getSalary(),customer.getPhone());
    }

    public Customer queryById(int id) throws Exception {
        Customer customer = queryBean(Customer.class, "select * from t_customer where id = ?", id);
        return customer;
    }

    public int deleteCustomer(int id) throws SQLException {
        return update("delete from t_customer where id =?", id);
    }

    public int updateCustomer(Customer cust) throws SQLException {
        return update("update t_customer set name = ? , gender = ? , age = ? ," +
                "salary = ? , phone = ? where id = ? ;", cust.getName(), cust.getGender(),cust.getAge(), cust.getSalary(), cust.getPhone(), cust.getId());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章