1. 程式人生 > 其它 >JDBC入門教程

JDBC入門教程

技術標籤:selfLearningmysqljavadbcp

JDBC 概念集

概述

Java Database Connectivity (Java 語言連線資料庫)

JDBC 的本質是SUN司指定的一條介面(interface)
java.sql.*

面向介面呼叫、面向介面寫實現類,都屬於面向介面程式設計。

為什麼要面向介面程式設計?

解耦合:降低程式的耦合度,提高程式的擴充套件力。
​ 多型機制就是非常典型的面向抽象(介面)程式設計。(不要面向具體程式設計)

為什麼SUN制定套JDBC介面?

​ 因為每一個數據庫的底層實現原理不一樣。
​ Oracle、MySQL、MS SqlServer 等等等都有自己的原理。

​ 每一個數據庫產品都有自己獨特嗯實現原理。

JDBC開發前的準備工作

先從官網下載對應的驅動 jar 包,然後將其配置到環境變數 classpath 中
使用 IDEA 工具時不需要配置以上環境變數。
IDEA 有自己的配置方式。

JDBC程式設計六步概述★

  1. 註冊驅動:告訴 Java 程式即將要連線的是哪個品牌的資料庫
  2. 獲取連線:表示 JVM 的程序和資料庫程序之間的通道打開了,這屬於程序之間的通訊,重量級的。使用完只後一定要關閉通道。
  3. 獲取資料庫操作物件(專門執行 sql 語句的物件)
  4. 執行 SQL 語句:DQL、DML …
  5. 處理查詢結果:只有當第四步執行的是 select 語句的時候,才有這第五步
  6. 釋放資源:使用完資源之後,一定要關閉資源。Java 和資料庫屬於程序間的通訊,開起之後一定要關閉。

JDBC 基礎

註冊驅動與獲取連線

連結:如何在idea中使用JDBC?

演示

註冊驅動

// 多型,父型別引用指向子型別物件
Driver driver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);

####獲取連線

Connection connection = null;
try...catch
String url= "jdbc:mysql://127.0.0.1:3306/mysql?serverTimezone=UTC"
; //“mysql”是資料庫的名字,“?”後面的條件,建議也加上?useSSL=false // String url = "jdbc:mysql://114.55.106.121:3306/learning"; String user = "------"; String password = "------"; connection = DriverManager.getConnection(url,user,password); // 連線成功!連線物件 = [email protected] connection.close();

URL:統一資源定位符(網路中某個資源的絕對路徑)。URL包括:協議、IP、PORT、資源名

例如:http://182.61.200.7:80/ind ex.html

  1. http:// 通訊協議
  2. 182.61.200.7 伺服器的IP地址
  3. 80 伺服器上軟體的埠
  4. index.html 伺服器上的某個資源名

什麼是通訊協議?有什麼用?
通訊協議是通訊之前就提前定好的資料傳輸格式
資料包具體怎麼傳輸資料,格式是提前定好的

可能遇見的報錯

時區問題

The server time zone value '�й���׼ʱ��' is unrecognize.

原因:
使用了Mysql Connector/J 6.x以上的版本,然後就報了時區的錯誤

解決辦法:

​ 在配置url的時候不能簡單寫成:jdbc:mysql://localhost:3306/dbName

​ 而是要寫成 :jdbc:mysql://localhost:3306/dbName?serverTimezone=UTC

Deprecated 問題

Loading class com.mysql.jdbc.Driver'.`` This is deprecated.

原因:
提示資訊表明資料庫驅動com.mysql.jdbc.Driver’已經被棄用了、應當使用新的驅動com.mysql.cj.jdbc.Driver’

解決辦法:
.com.mysql.jdbc.Driver修改為.com.mysql.jc.jdbc.Driver

執行 SQL 語句與釋放資源

執行SQL語句

  1. 獲取資料庫操作物件
    Statement:專門用來執行 SQL 語句的物件

    Statement stmt = null;
    stmt = connection.createStatement();
    stmt.close();
    
  2. 執行 SQL 語句

    Statement**.excuteUpdate(String sql)** :專門執行 DML 語句,返回值為“資料庫中受影響的條數”

    String sql = "insert into employees(employee_id,last_name) values(99,'Cai')";
    int count = stmt.executeUpdate(sql);
    

注意: JDBC 中的 SQL 語句不提供分號結尾,否則容易報錯

關閉資源

為了保證資源一定釋放,因此將其放在finally語句中,並且依照從小到大分別對其try…catch

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

注意事項

  1. 【註冊、連線、獲取物件、執行語句、處理語句】都可以一齊放在 SQLException的異常處理中,【資源關閉】則單獨放在finally語句中。
  2. 關閉資源時,遵循從小到大的順序關閉,且分別 try…catch
  3. 需要提前在 try…catch 外面宣告 Connection、Statement 為null,否則關閉會受影響。

插入、刪除與更新(DML)

excuteUpdate(insert|delete|update)  ---> int
package per.deletedemo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stat = null;
        try {
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployees?serverTimezone=UTC", "root", "212270");
            String sql = "delete from employees where employee_id = 99";
            stat = conn.createStatement();
            assert stat != null;
            int count = stat.executeUpdate(sql);
            System.out.println(count == 1 ? "刪除成功" : "刪除失敗");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

以類載入的方式註冊驅動

註冊驅動的另一種方式(常用方式)

// 第一種註冊方式
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

// 第二種註冊方式 --> 通過反射來進行載入 --> 更為常用
Class.forName("com.mysql.cj.jdbc.Driver");

為什麼更為常用?
因為引數是一個字串,即可以寫到 xxx.properties 檔案中

注意 第二種方法不需要結束返回值,因為我們只需要他的類載入動作完成其靜態方法註冊驅動

從屬性資原始檔中讀取連線資料庫資訊

ResourceBundle bundel = ResourceBundle.getBundle("jdbc");
String driver = bundel.getString("driver");
String url = bundel.getString("url");
String user = bundel.getString("user");
String password = bundel.getString("password");

注意 連線中不建議把連線資料庫的資訊寫死到 Java 程式中。

注意 在 properties 檔案中,以鍵值對形式儲存,不加雙引號!!!

處理查詢結果集(DQL)

excuteQuery(select) ---> ResultSet
boolean next() throws SQLException
    // 如果新的當前行有效,則返回 true
    // 如果不存在下一行,則返回 false
String getString(Integer index|String str) throws SQLException
    // 不管什麼值,都以 String 返回
    // JDBC 中的下標從 1 開始
    // 也可以直接用查詢結果的列名獲取
Integer getInt(...)
Double getDouble(...)

注意 若 select 使用了別名,則 getString() 以列名查詢時必須以別名為引數。(即以返回的列表為準)

ResultSet rs = null;
rs = stmt.executeQuery(sql);
// 開始讀取 rs
String e1 = null;
Integer e2 = null;
while (rs.next()) {
	e1 = rs.getString(1); // 1可以用列名代替
	e2 = rs.getInt(2);    // 數字可以用select中的列名代替 
}
rs.close();

JDBC 實戰

使用者登入業務介紹

  1. 需求:
    模擬使用者登入功能的實現
  2. 需求描述:
    程式執行的時候,提供一個輸入的入口,可以讓使用者輸入使用者名稱和密碼。
    使用者提交使用者名稱和密碼之後,提交資訊,java程式收集到使用者資訊
    Java程式連線資料庫驗證使用者名稱和密碼是否合法,合法則顯示登入成功
  3. 資料的準備:
    在實際開發中,表的設計會使用專業的建模工具—>PowerDesigner
    使用 PD 工具來進行資料庫表的設計

使用PowerDesigner進行物理建模

image-20200504101919052image-20200504102042590

使用者登入–SQL注入

問題引出

String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");

String sql = "select * from t_user where loginName ='" + loginName
                    + "' and loginPwd='" + loginPwd + "'";
rs = stmt.executeQuery(sql);

if (rs.next()) {
	loginSuccess = true;
}

方法存在的漏洞:這種現象稱為 sql 注入(安全隱患)

使用者名稱:jake
密碼:kejk' or '1' = '1
// 返回 true

導致sql注入的根本原因
使用者輸入的資訊中含有 sql 語句的關鍵字,並且這些關鍵字參與 sql 語句的編譯過程。
導致 sql 語句的原意被扭曲,進而達到 sql 注入。

String sql = "select * from t_user where loginName ='jake' and loginPwd='kejk' or '1' = '1' ";
// 以上正好完成了sql語句的拼接
// 以下程式碼的含義是,傳送SQL語句給DBMX,DBMX進行sql編譯
rs = stmt.executeQuery(sql);
// 正好將使用者提供的“非法資訊”編譯進去。導致了原sql語句的含義被扭曲

如何解決?

解決思路:

只需要使用者提供的資訊不參與 sql 的編譯過程
要想使用者訊息不參與 sql 語句的編譯,那麼必須使用 java.sql.PreparedStatement介面,其繼承了java.sql,Statement,屬於預編譯的資料庫操作物件

// Statement stmt = null;
PreparedStatement ps = null;

// stmt = conn.createStatement();
// String sql = "select * from t_user where loginName ='" 
//     + loginName +"' and loginPwd='" + loginPwd + "'";
// rs = stmt.executeQuery(sql);
String sql = "select * from t_user where loginName = ? and loginPwd= ? ";
ps = conn.prepareStatement(sql);	// 傳框架
ps.setString(1, loginName);		// 開始傳值
ps.setString(2, loginPwd);
rs = ps.executeQuery();		// 開始執行

注意

  1. 使用 conn.prepareStatement(sql)將 sql 語句的框架傳送給 DBMS 進行預編譯
  2. sql 語句中使用佔位符?,一個佔位符代表一個值
    佔位符不要使用單引號括起來
  3. 通過ps.setString(index,value)給佔位符賦值
    注意:JDBC中的下標 index 從 1 開始

Statement & PreparedStatement

sql 注入問題效率編譯階段做型別的安全檢查?
Statement未解決略低(編譯一次執行一次)不會
PreparedStatement已解決略高(編譯一次可執行n次)

綜上所述:
PreparedStatement 使用較多,只有極少數情況會使用 Statement。

什麼情況下必須使用 Statement 呢?
業務方面必須支援 SQL注入 的時候,Statement支援 SQL注入,支援 sql 語句的拼接。
比如選擇“升序ASC”和“降序DESC”時,需要在使用者端完成 sql 語句的拼接。

傳值時使用PreparedStatement,在進行sql語句拼接的時候使用Statement

使用PreparedStatement完成增刪改

String sql ="insert into t_user(loginName,loginPwd) values(?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"cai");
ps.setString(2,"123");
int count = ps.executeUpdate();
System.out.println(count);

JDBC的事務自動提交

介紹

JDBC 中的事務是自動提交的。
即只要執行任意一條 DML 語句,則會自動提交。這是 JDBC 預設的事務行為。
但是在實際的開發中,通常都是 N 條 DML 語句共同聯合才能完成的
必須保證這些 DML 語句在同一個事務中同時成功或同時失敗。

功能使用

try{
conn.setAutoCommit(false);	// 關閉自動提交,開始事務
/*
 * 這裡是事務內容,即需要提交的程式碼
 */
conn.commit();		// 提交程式碼,完成事務
} catch (Exception e) {
//--------出現 Exception 則回滾事務----------
    if (conn == null) {
        try {
            conn.rollback();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
    e.printStackTrace();
} finally {...}
conn.setAutoCommit(false);
conn.commit();
Exception ---> conn.rollback();

隔離級別

獲取隔離級別:

conn.getTransactionIsolation(); ---> Integer

設定隔離級別:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-aBl1g40Y-1610006137627)(image-20200508220902395.png)]

JDBC工具類的封裝★

package per.util;

import java.sql.*;

/**
 * JDBC工具類,簡化JDBC程式設計
 */
public class DBUTil {
    
    // 靜態程式碼塊在類載入時執行一次,並且只執行一次
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 工具類中的構造方法都是私有的
     * 因為工具類當中的方法都是靜態的,不需要new物件,直接採用類名呼叫。
     */
    private DBUTil() {
    }

    public static Connection getConnection() throws SQLException {
        // 外部呼叫有 try...catch ,故此處只需扔出異常
        return DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/test?serverTimezone=UTC"
                , "root", "212270");
    }
    /**
     * 關閉資源
     *
     * @param conn 連線物件
     * @param stmt 資料庫操作物件
     * @param rs   結果集
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {		// 判斷 null 之後
            try {				// 如果沒有使用到 ResultSet,則可直接第三個引數為 null
                rs.close();		// 由此可以避免使用過載,便於簡化程式
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

測試工具類

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        // 獲取連線
        conn = DBUTil.getConnection();
        // 獲取預編譯的資料庫操作物件
        String sql = "select last_name from employees where last_name like ? limit 0,5";
        ps = conn.prepareStatement(sql);
        ps.setString(1,"_a%");
        rs = ps.executeQuery();
        while(rs.next()){
            System.out.println(rs.getString("last_name"));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        // 釋放資源
        DBUTil.close(conn,ps,rs);
    }
}

JDBC的增刪改通用寫法

寫法一

// 傳入個數可變
public static void comUpdate(String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            // 獲取連線
            conn = DBUTil.getConnection();
            // 獲取資料庫物件
            ps = conn.prepareStatement(sql);
            // 傳值
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            // 執行
            int count = ps.executeUpdate();
            System.out.println("Changed Rows = " + count);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            DBUTil.close(conn,ps,null);
        }
    }
String sql = "delete from employees where last_name = ?";
comUpdate(sql,"cai");

寫法二

/**
 * 通用的查詢操作,v2.0,考慮了事務
 * @param conn 連線物件
 * @param clazz 物件
 * @param sql SQL語句
 * @param args 傳值
 * @param <T> 物件的型別
 * @return 返回一個包含資訊的物件
 */
public static  <T>T getInstance(Connection conn,Class<T> clazz,String sql,Object... args){
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        ps = conn.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i+1,args[i]);
        }
        rs = ps.executeQuery();
        // 獲取結果集的元資料
        ResultSetMetaData rsmd = rs.getMetaData();
        // 通過ResultSetMetadata獲取結果集中的列數
        int columnCount = rsmd.getColumnCount();
        if(rs.next()){
            T t = clazz.newInstance();
            // 處理結果集一行資料中的每一個列
            for (int i = 0; i < columnCount; i++) {
                // 獲取列值
                Object columnValue = rs.getObject(i + 1);
                // 獲取每個列的列名
                String columnLabel = rsmd.getColumnLabel(i + 1);
                // 給 t 物件指定的 columnName屬性賦值為columnValue:通過反射
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(t, columnValue);
            }
            return t;
        }
    } catch (SQLException | InstantiationException | IllegalAccessException | NoSuchFieldException e) {
        e.printStackTrace();
    } finally {
        DBUTil.close(null,ps,rs);

    }
    return null;
}

操作Blob型別欄位

Blob型別資料

  • MYSQLE中,BLOB是一個二進位制大型物件,是一個可以儲存大量資料的容器,它能容納不同大小的資料。
  • 插入BLOB型別的資料必須使用 Preparedstatement,因為BLOB型別的資料無法使用字串拼接寫的。
  • My5QL的四種BLOB型別(除了在儲存的最大資訊量上不同外,他們是等同的)
型別最大位元組
TinyBlob255 b
Blob65 Kb
MediumBlob16 Mb
LongBlob4 Gb
  • 實際使用中根據需要存入的資料大小定義不同的BLOB型別。
  • 注意:如果儲存的檔案過大,資料庫的效能會下降。
  • 如果在指定了相關的Blob型別以後,還報錯:xxx too large,那麼在mysq的安裝目錄下,找 my inn檔案加上如下的配置引數: max allowed packet=16M
    注意:修改了myin檔案之後,需要重新啟動 mysql服務

##上傳Blob

ps = conn.PreparedStatment(sql);

//ps.setBlob(index,inputStream);
InputStream ins = new FileInputStream(new File("path"));	// FileNotFoundException
ps.setBlob(index,ins);

ins.close(); // IOException

下載Blob

// 從資料庫讀入 Blob
while(rs.next()){
    Blob photo = rs.getBlob("photo");
    InputStream ins = photo.getBinaryStream();
    OutputStream ous = new FileOutputStream(new File("path"));
    // 開始讀資料
    byte[] buffer = new byte[1024];
    Integer len = 0;
    while((len = ins.read(buffer)) != -1){
        ous.write(buffer,0,len);
    }
}

ins.close();
ous.close();

批量插入資料

方式一、PreparedStatement

String sql = "insert into learning.temp_test(name) values(?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 100000; i++) {
    ps.setString(1, "name" + i);
    ps.executeUpdate();
}
// 耗費時間約為 400s

方式二、Butch()

conn = DBUTil.getConnection();
String sql = "insert into learning.temp_test(name) values(?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 100000; i++) {
    ps.setString(1, "name" + i);
    // ---------------------------------------------
    // 1、攢 sql
    ps.addBatch();
    if (i % 500 == 0) {
        // 2、執行
        ps.executeBatch();
        // 3、清空
        ps.clearBatch();
    }
}
// 耗費時間約為 16s左右

注意 MySQL伺服器預設是關閉批處理的,我們需要一個引數讓伺服器開啟對批處理的支援。
?rewriteBatchedStatements=true寫在配置檔案的url後面

注意 批處理需要使用MySQL版本驅動為及以上的:mysq1-connector-java-5.1.37-bin.jar

方式三:結合事務提交

conn = DBUTil.getConnection();
// 開啟事務
conn.setAutoCommit(false);
String sql = "insert into learning.temp_test(name) values(?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 100000; i++) {
    ps.setString(1, "name" + i);
    ps.addBatch();
    if (i % 500 == 0) {
        ps.executeBatch();
        ps.clearBatch();
    }
}
// 提交事務
conn.commit();
// conn.rollback();

// 耗費時間約為5s

Java&SQL 資料型別

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

Java型別SQL型別
booleanBIT
byteTINYINT
shortSMALLINT
intINTEGER
longBIGINT
StringCHAR, VARCHAR, LONGVARCHAR
byte arrayBINARY, VAR BINARY
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP

##Java 如何存取MySQL datetime型別

Java 如何存取MySQL datetime型別

提取資料

​ 如果要從MySQL中獲取yyyy-MM-dd HH:mm:ss日期格式,首先必須使用 rs.getTimestamp("insert_dt")方法,其中"insert_dt" 是資料庫時間欄位,型別為datetime;然後通過SimpleDateFormat 時間格式化類,將取出來的時間轉為String型別

先來對比rs獲取不同日期時間格式的方法

System.out.println(rs.getDate("insert_dt"));	// 2018-03-19
System.out.println(rs.getTime("insert_dt"));	// 22:03:21
System.out.println(rs.getTimestamp("insert_dt")); // 2018-03-19 22:03:21.0

可以看到通過getTimestamp獲取的日期格式最後還有一位數,需要將rs.getTimestamp("insert_dt")轉為String型別

 String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
     .format(rs.getTimestamp("insert_dt"));
 System.out.println(timeStamp);
// 2018-03-19 21:51:57

儲存資料

從前端或者自己模擬一個日期格式,轉為String即可

Date date = new Date();  
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = format.format(date);
DBUtil.update(sql,dateStr);

DAO及其實現類

  • DAO: Data Access Object,訪問資料資訊的類和介面
    包括了對資料的CRUD( Create、 Retrival、 Update、Delete)
    而不包含任何業務相關的資訊。有時也稱作: BASEDAO
  • 作用:為了實現功能的模組化,更有利於程式碼的維護和升級。

內容較多,直接看Idea程式碼及註釋理解

資料庫連線池

JDBC資料庫連線池的必要性

在使用開發基於資料庫的Web程式時,傳統的模式基本是按以下步驟:

  1. 在主程式(如 servlet、 beans)中建立資料庫連線
  2. 進行 sql 操作
  3. 斷開資料庫連線

這種開發模式存在的問題

  • 普通的DBC資料庫連線使用 Drivermanager來獲取,每次向資料庫建立連線的時候都要將 Connection
    載入到記憶體中,再驗證使用者名稱和密碼(得花費0.05S~1s的時間)。需要資料庫連線的時候,就向資料庫要求
    一個,執行完成後再斷開連線。這樣的方式將會消耗大量的資源和時間。資料庫的連線資源並沒有得到很好的重複利用。若同時有幾百人甚至幾千人線上,頻繁的進行資料庫連線操作將佔用很多的系統資源,嚴重的甚至會造成伺服器的崩潰。
  • 對於每一次資料庫連線,使用完後都得斷開。否則,如果程式出現異常而未能關閉,將會導致資料庫系統中的記憶體池漏,最終將導致重啟資料庫。(何為java的記憶體洩漏?記憶體不能被回收 銷燬)
  • 這種開發不能控制被建立的連線物件數,系統資源會被亳無顧及的分配出去,如連線過多,也可能導致內
    存洩漏,伺服器崩潰。

資料庫連線池技術

  • 為解決傳統開發中的資料庫連線問題,可以採用資料庫連線池技術
  • 資料庫連線池的基本思想:就是為資料庫連線建立一個“緩衝池”。預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需從"緩衝池"中取出一個,使用完畢之後再放回去。
  • 資料庫連線池負責分配、管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。
  • 資料庫連線池在初始化時將建立一定數量的資料庫連線放到連線池中,這些資料庫連線的數是由最小資料庫連線數來設定的。無論這些資料庫連線是否被使用,連線池都將一直保證至少擁有這麼多的連線數量。連線池的最大資料庫連線數量限定了這個連線池能佔有的最大連線數,當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中

工作原理

image-20200509144551696

資料庫連線池的優點

  1. 資源重用
  2. 更快的系統反應速度
  3. 新的資源分配手段
  4. 統一的連線管理,避免資料庫連線洩露

多種開源的資料庫連線池

JDBC的資料庫連線池使用 Javax. sql Datasource來表示, Datasource只是一個介面,該介面通常由伺服器weblogic, Websphere, Tomcat)提供實現,也有一些開源組織提供實現

C3P0

是一個開源組織提供的一個數據庫連線池,速度相對較慢穩定性還可以。 hibernate官方推薦使用

DBCP

是Apache提供的資料庫連線池。Tomcat自帶DBCP連線池。數獨相對C3P0較快,但因自身存在bug,Hibernate3已經不再提供支援

Druid(德魯伊)

(開發中主要使用)是阿里提供的資料庫連線池,據說是集DBCP、C3P0、 Proxool 優點於一身的資料庫連線池,但是速度不確定是否有 Bonecp快

C3P0的兩種資料庫實現方式

try {
    // 獲取c3p0資料庫連線池
    ComboPooledDataSource cpds = new ComboPooledDataSource();
    cpds.setDriverClass("com.mysql.jdbc.Driver");
    cpds.setJdbcUrl("jdbc:mysql://localhost:3306/myemployees?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=UTC");
    cpds.setUser("root");
    cpds.setPassword("212270");

    // 通過設定相關的引數對資料庫連線池進行管理
    // 設定初始的資料庫連線池中的連線數
    cpds.setInitialPoolSize(10);

    Connection conn = cpds.getConnection();
    System.out.println(conn);

    // 銷燬連線池(一般不關連線池)
    DataSources.destroy(cpds);
} catch (Exception e){
    e.printStackTrace();
}

方式二

ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");
try {
    Connection connection = cpds.getConnection();
    System.out.println(connection);
} catch (SQLException e) {
    e.printStackTrace();
}
<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
    <named-config name="helloC3P0">
        <!--  提供獲取連線的4個基本資訊  -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/myemployees?useUnicode=true&amp;characterEncoding=utf8&amp;zeroDateTimeBehavior=convertToNull&amp;useSSL=false&amp;serverTimezone=UTC
        </property>
        <property name="user">root</property>
        <property name="password">212270</property>

        <!--  進行資料庫連線池管理的基本資訊  -->
        <!--  當資料庫連線池中的連線數不夠時,c3p0一次性向資料庫伺服器申請的連線數  -->
        <property name="acquireIncrement">5</property>
        <!--  c3p0資料庫連線池中初始化時的連線數  -->
        <property name="initialPoolSize">10</property>
        <!--  資料庫連線池中維護的最少(多)連線數  -->
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <!--  資料庫連線池中維護的Statement的最大個數  -->
        <property name="maxStatements">50</property>
        <!--  每個連線最多可使用的Statement個數  -->
        <property name="maxStatementsPerConnection">2</property>
    </named-config>
</c3p0-config>

Apache-DBUtils

package per.connection.queryrunner;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.*;
import per.bean.Customer;
import per.util.DBUTil;
import per.util.DBUTilDruid;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author CaiKe
 * @create 2020/5/10
 * commons-dbutils 是 Apache組織提供的一個開源的 JDBC 工具類庫,封裝了針對於資料庫的增刪查改操作
 */
public class QueryRunnerTest {
    public static void main(String[] args) {
        try {
//            testListQuery();
//            testMapQuery();
//            testMapListQuery();
//            testGetValue();
            testSelfDefinedQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    /**
     * 增刪改
     *
     * @throws SQLException SQL
     */
    public static void testInsert() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "insert into customers(name,email,birth) values(?,?,?)";
        int count = runner.update(conn, sql, "李莉莉", "[email protected]", "2018-02-19");
        System.out.println("Changed Rows:" + count);

        DBUTil.close(conn, null, null);
    }

    /**
     * BeanHandler:是ResultSetHandler介面的實現類,用於封裝表中的一條記錄
     *
     * @throws SQLException SQl
     */
    public static void testQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "select id,name,email,birth from customers where id = ?";
        //  BeanHandler 即物件Handler,傳入物件Customer
        BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
        Customer cus = runner.query(conn, sql, handler, 5);
        System.out.println(cus);

        DBUTil.close(conn, null, null);
    }

    /**
     * BeanListHandler:用於封裝表中的多條物件記錄構成的集合
     *
     * @throws SQLException SQL
     */
    public static void testListQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "select id,name,email,birth from customers where id < ?";
        //  BeanListHandler 即物件Handler,傳入物件Customer,返回一個List
        BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
        List<Customer> customerList = runner.query(conn, sql, handler, 5);
        System.out.println(customerList);

        DBUTil.close(conn, null, null);
    }

    /**
     * MapHandler:將欄位即值作為Map中的鍵和值
     *
     * @throws SQLException SQL
     */
    public static void testMapQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "select id,name,email,birth from customers where id = ?";
        MapHandler handler = new MapHandler();
        Map<String, Object> map = runner.query(conn, sql, handler, 5);
        System.out.println(map);

        DBUTil.close(conn, null, null);
    }

    /**
     * MapList:對應表中的多條記錄,每條記錄封裝為 map
     *
     * @throws SQLException SQL
     */
    public static void testMapListQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "select id,name,email,birth from customers where id < ?";
        MapListHandler handler = new MapListHandler();
        List<Map<String, Object>> mapList = runner.query(conn, sql, handler, 5);
        for (Map<String, Object> map : mapList) {
            System.out.println(map);
        }

        DBUTil.close(conn, null, null);
    }

    /**
     * ScalarHandler:用於查詢特殊值
     *
     * @throws SQLException SQL
     */
    public static void testGetValue() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();
        String sql = "select count(*) from customers";

        ScalarHandler<Object> handler = new ScalarHandler<>();
        Long count = (Long) runner.query(conn, sql, handler);
        System.out.println(count);

        DBUTil.close(conn, null, null);
    }


    /**
     * ResultSetHandler:自己造一個返回,需要覆寫返回方法
     * @throws SQLException SQL
     */
    public static void testSelfDefinedQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection conn = DBUTilDruid.getConnection();

        String sql = "select id,name,email,birth from customers where id = ?";
        ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
            @Override
            public Customer handle(ResultSet resultSet) throws SQLException {
                System.out.println("這部分是自定義的返回值。");
//                return new Customer(14,"haha","[email protected]",new Date(4324324324L));

                if(resultSet.next()){
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    String email = resultSet.getString("email");
                    Date birth = resultSet.getDate("birth");
                    return new Customer(id,name,email,birth);
                } else{
                    return null;
                }
            }
        };
        Customer cus = runner.query(conn, sql, handler, 4);
        System.out.println(cus);
        DBUTil.close(conn, null, null);
        DbUtils.closeQuietly(conn);
    }
}

= DBUTilDruid.getConnection();

    String sql = "select id,name,email,birth from customers where id = ?";
    ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
        @Override
        public Customer handle(ResultSet resultSet) throws SQLException {
            System.out.println("這部分是自定義的返回值。");

// return new Customer(14,“haha”,“[email protected]”,new Date(4324324324L));

            if(resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String email = resultSet.getString("email");
                Date birth = resultSet.getDate("birth");
                return new Customer(id,name,email,birth);
            } else{
                return null;
            }
        }
    };
    Customer cus = runner.query(conn, sql, handler, 4);
    System.out.println(cus);
    DBUTil.close(conn, null, null);
    DbUtils.closeQuietly(conn);
}

}