一、數(shù)據(jù)庫連接池
1.1 什么是數(shù)據(jù)庫連接池
連接池是Connection對象的緩沖區(qū),它里面會存放一些Connection,當(dāng)我們Java程序需要使用Connection的時候,如果連接池中有則直接從連接池獲取,不需要去新創(chuàng)建Connection了。連接池讓Java程序能夠復(fù)用連接、管理連接。
1.2 為什么要使用連接池
- 因為每次創(chuàng)建和銷毀連接都會帶來較大的系統(tǒng)開銷
- 每次創(chuàng)建和銷毀連接都要消耗大概0.05~1s的時間
- 可以防止大量用戶并發(fā)訪問數(shù)據(jù)庫服務(wù)器
1.3 連接池的優(yōu)勢
1.3.1 資源重用
由于數(shù)據(jù)庫連接得到重用,避免了頻繁創(chuàng)建、釋放連接引起的大量性能開銷。在減少系統(tǒng)開銷的基礎(chǔ)之上,也增進(jìn)了系統(tǒng)運行環(huán)境的穩(wěn)定性。
1.3.2 更快的系統(tǒng)響應(yīng)速度
數(shù)據(jù)庫連接池在初始化過程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于池中備用。此時連接的初始化工作均已完成。對于業(yè)務(wù)請求處理而言,直接利用現(xiàn)有可用連接,避免了數(shù)據(jù)庫連接初始化和釋放過程的時間開銷,從而縮減了系統(tǒng)整體響應(yīng)時間。
1.3.3 新的資源分配手段
對于多應(yīng)用共享同一數(shù)據(jù)庫而言,可在應(yīng)用層通過數(shù)據(jù)庫連接的配置。
1.3.4 統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄露
在較為完備的數(shù)據(jù)庫連接池實現(xiàn)中,可根據(jù)預(yù)先的連接占用超時設(shè)定,強制收回被占用連接。從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄露。
1.4 連接池的原理
- 連接池維護(hù)者兩個容器空閑池和活動池。
- 空閑池用于存放未使用的連接,活動池用于存放正在使用中的連接,活動池中的連接使用完之后要歸還回空閑池。
- 當(dāng)Java程序需要連接的時候,先要判斷空閑池中是否有連接,如果空閑池中有連接則取出一個連接放置到活動池供Java程序使用。
- Java程序需要連接的時候,如果空閑池中沒有連接了,就先判斷活動池的連接數(shù)是否已經(jīng)達(dá)到了最大連接數(shù),如果未達(dá)到最大連接數(shù),則會新創(chuàng)建一個連接放置到活動池,供Java程序使用。
- 如果空閑池中沒有連接了,活動池中的連接也已經(jīng)達(dá)到了最大連接數(shù),就不能再新創(chuàng)建連接了,此時會判斷是否等待超時,如果沒有等待超時就需要等待活動池中的連接歸還回空閑池
- 如果等待超時了,則可以采取多種處理方式。如:直接拋出超時異常,或者將活動池中使用最久的連接移除掉歸還回空閑池以供Java程序使用。
1.5 連接池的實現(xiàn)
1.5.1 DataSource接口
JDBC的數(shù)據(jù)庫連接池使用javax.sql.DataSource來表示,DataSource只是一個接口,所有的Java數(shù)據(jù)庫連接池都需要實現(xiàn)該接口。該接口通常由服務(wù)器(Weblogic,WebSphere,Tomcat)提供實現(xiàn),也有一些開源組織提供實現(xiàn)。
1.5.2 常見的數(shù)據(jù)庫連接池
- DBCP是Apache提供的數(shù)據(jù)庫連接池,速度相對c3p0較快,但因為其自身存在BUG,Hibernate3已不再提供支持。
- C3P0是一個開源組織提供的一個數(shù)據(jù)庫連接池,速度相對較慢,穩(wěn)定性還行。
- Proxool是sourceforge下的一個開源項目數(shù)據(jù)庫連接池,有監(jiān)控連接池狀態(tài)的功能,穩(wěn)定性較c3p0差一點。
- HikariCP俗稱光連接池,是目前速度最快的連接池。
- Druid是阿里提供的額數(shù)據(jù)庫連接池,據(jù)說是集DBCP、C3P0、Proxool優(yōu)點于一身的數(shù)據(jù)庫連接池。
1.5.3 Druid連接池的使用
- 導(dǎo)入Druid的jar包
- 創(chuàng)建Druid連接池的配置文件druid.properties文件,放置到resources文件夾下
- 代碼中使用
public static void test01() throws Exception{
// 1. 創(chuàng)建一個Properties對象,讓其去讀取druid.properties文件
Properties properties = new Properties();
// 1.1 將druid.properties配置文件轉(zhuǎn)成字節(jié)輸入流
// FileInputStream is = new FileInputStream("絕對路徑");
// 1.2 使用相對路徑來將配置文件轉(zhuǎn)成字節(jié)輸入流,可以使用類加載器來讀取類路徑下文件
// 獲取ClassLoader對象
ClassLoader classLoader = TestDataSource.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("druid.properties");
// 1.2 使用properties對象加載流
properties.load(is);
// 2. 使用創(chuàng)建DruidDataSourceFactory創(chuàng)建Druid的連接池對象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 從連接池中取出一個連接使用
Connection connection1 = dataSource.getConnection();
Connection connection2 = dataSource.getConnection();
Connection connection3 = dataSource.getConnection();
Connection connection4 = dataSource.getConnection();
Connection connection5 = dataSource.getConnection();
Connection connection6 = dataSource.getConnection();
Connection connection7 = dataSource.getConnection();
Connection connection8 = dataSource.getConnection();
Connection connection9 = dataSource.getConnection();
// 歸還一個連接:沒有使用連接池的時候connection.cloase()就是銷毀連接;如果是從連接池中取出的connection對象,它調(diào)用close()方法就是將連接歸還回連接池
// 底層原理是動態(tài)代理
connection1.close();
Connection connection10 = dataSource.getConnection();
Connection connection11 = dataSource.getConnection();
}
1.5.4 Druid連接池的配置參數(shù)
| 配置 | 說明 |
|---|---|
| name | 配置這個屬性的意義在于,如果存在多個數(shù)據(jù)源,監(jiān)控的時候可以通過名字來區(qū)分開來。 如果沒有配置,將會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this) |
| url | 連接數(shù)據(jù)庫的url,不同數(shù)據(jù)庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto |
| username | 連接數(shù)據(jù)庫的用戶名 |
| password | 連接數(shù)據(jù)庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。 |
| driverClassName | 根據(jù)url自動識別 這一項可配可不配,如果不配置druid會根據(jù)url自動識別dbType,然后選擇相應(yīng)的driverClassName(建議配置下) |
| initialSize | 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法,或者第一次getConnection時 |
| maxActive | 最大連接池數(shù)量 |
| minIdle | 最小連接池數(shù)量 |
| maxWait | 獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 |
| poolPreparedStatements | 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標(biāo)的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關(guān)閉。 |
| maxOpenPreparedStatements | 要啟用PSCache,必須配置大于0,當(dāng)大于0時,poolPreparedStatements自動觸發(fā)修改為true。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題,可以把這個數(shù)值配置大一些,比如說100 |
| validationQuery | 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。 |
| testOnBorrow | 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。 |
| testOnReturn | 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能 |
| testWhileIdle | 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。 |
| timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據(jù),詳細(xì)看testWhileIdle屬性的說明 |
| connectionInitSqls | 物理連接初始化的時候執(zhí)行的sql |
| exceptionSorter | 根據(jù)dbType自動識別 當(dāng)數(shù)據(jù)庫拋出一些不可恢復(fù)的異常時,拋棄連接 |
| filters | 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall |
| proxyFilters | 類型是List,如果同時配置了filters和proxyFilters,是組合關(guān)系,并非替換關(guān)系 |
1.5.5 封裝JDBCTools
/**
* 這個工具類會提供三個方法:
* 1. 獲取連接池對象
* 2. 從連接池中獲取連接
* 3. 將連接歸還到連接池
*/
public class JDBCTools {
public static DataSource dataSource;
static {
try {
// 1.使用類加載器讀取配置文件,轉(zhuǎn)成字節(jié)輸入流
InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties");
// 2. 使用Properties對象加載字節(jié)輸入流
Properties properties = new Properties();
properties.load(is);
// 3. DruidDataSourceFactory創(chuàng)建連接池對象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取連接池對象
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 獲取連接
* @return
*/
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
// 運行時發(fā)生異常再拋出一場
throw new RuntimeException(e.getMessage());
}
}
/**
* 將連接歸還連接池
* @param connection
* @throws SQLException
*/
public static void releaseConnection(Connection connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
二、Apache的DBUtils
2.1 DBUtils的概述
commons-dbutils是Apache組織提供的一個開源JDBC工具類庫,它是對JDBC的簡單封裝,學(xué)習(xí)成本極低,并且使用dbutils能極大簡化JDBC編碼的工作量,同時也不會影響程序的性能。
其中QueryRunner類封裝了SQL的執(zhí)行,是線程安全的。
- 可以實現(xiàn)增刪改查、批處理
- 考慮了事務(wù)處理需要共用Connection
- 該類最主要的就是簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的數(shù)據(jù)庫操作,能夠大大減少編碼量。
2.2 DBUtils執(zhí)行增刪改的SQL語句
public static void testInsert() throws SQLException {
// 第一種:需要在執(zhí)行sql語句的時候手動傳入連接對象
// // 往user表中添加一行數(shù)據(jù)
// // 1. 創(chuàng)建QueryRunner對象
// QueryRunner queryRunner = new QueryRunner();
// // 2. 調(diào)用queryRunner對象的方法執(zhí)行sql語句,如果是執(zhí)行增刪改的SQL語句則調(diào)用update方法
// String sql = "insert into user(username,password,nickname) values (?,?,?)";
// queryRunner.update(JDBCTools.getConnection(),sql,"黃忠","111111","千鈞一發(fā)");
//第二種:直接將連接池對象交給QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
String sql = "insert into user(username,password,nickname) values (?,?,?)";
queryRunner.update(sql,"趙云","123456","七進(jìn)七出");
}
public static void testDelete() throws SQLException {
// 1. 創(chuàng)建QueryRunner對象
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
String sql = "delete from user where id=?";
queryRunner.update(sql,2000);
}
public static void testUpdate() throws SQLException {
// 將id為5的用戶昵稱改為張飛
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
String sql = "update user set nickname=? where id=? ";
queryRunner.update(sql,"張飛",6);
}
2.3 DBUtils執(zhí)行批處理
public static void testBatchAdd() throws SQLException {
// 測試批量添加
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
// 編寫SQL語句
String sql = "insert into user (username,password,nickname) values (?,?,?)";
// 創(chuàng)建一個二維數(shù)組,用于存儲批量參數(shù),二維數(shù)組的第一維表示批量操作多少條數(shù)據(jù),第二維表示每條數(shù)據(jù)要設(shè)置多少個參數(shù)
Object[][] params = new Object[1000][3];
// 循環(huán)設(shè)置批量添加的數(shù)據(jù)
for (int i = 0; i < 1000; i++) {
params[i][0] = "關(guān)羽"+i;
params[i][1]="444444"+i;
params[i][2] = "武圣"+i;
}
// 執(zhí)行批量處理
queryRunner.batch(sql,params);
}
2.4 使用QueryRunner類實現(xiàn)查詢
2.4.1 Handler類型介紹
| Handler類型 | 說明 |
|---|---|
| ArrayHandler | 將結(jié)果集中的第一條記錄封裝到一個Object[]數(shù)組中,數(shù)組中的每一個元素就是這條記錄中的每一個字段的值 |
| ArrayListHandler | 將結(jié)果集中的每一條記錄都封裝到一個Object[]數(shù)組中,將這些數(shù)組在封裝到List集合中。 |
| BeanHandler | 將結(jié)果集中第一條記錄封裝到一個指定的javaBean中。 |
| BeanListHandler | 將結(jié)果集中每一條記錄封裝到指定的javaBean中,將這些javaBean在封裝到List集合中 |
| ColumnListHandler | 將結(jié)果集中指定的列的字段值,封裝到一個List集合中 |
| KeyedHandler | 將結(jié)果集中每一條記錄封裝到Map<String,Object>,在將這個map集合做為另一個Map的value,另一個Map集合的key是指定的字段的值。 |
| MapHandler | 將結(jié)果集中第一條記錄封裝到了Map<String,Object>集合中,key就是字段名稱,value就是字段值 |
| MapListHandler | 將結(jié)果集中每一條記錄封裝到了Map<String,Object>集合中,key就是字段名稱,value就是字段值,在將這些Map封裝到List集合中。 |
| ScalarHandler | 它是用于單個數(shù)據(jù)。例如select count(*) from 表。 |
2.4.2 代碼示例
public static void testFindById() throws SQLException {
// 使用DBUtils執(zhí)行查詢一行數(shù)據(jù)
// 查詢一行數(shù)據(jù)轉(zhuǎn)換成User對象
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
// 編寫SQL語句
String sql = "select * from user where id=?";
// queryRunner執(zhí)行查詢的SQL語句,使用query()方法
User user = queryRunner.query(sql, new BeanHandler<>(User.class), 1);
System.out.println(user);
}
public static void testFindMore() throws SQLException {
// 查詢多條數(shù)據(jù)轉(zhuǎn)換成User對象
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
// 編寫SQL語句
String sql = "select * from user where id<?";
List<User> userList = queryRunner.query(sql, new BeanListHandler<>(User.class), 10);
for (int i = 0; i < userList.size(); i++) {
System.out.println(userList.get(i));
}
}
public static void testFindCount() throws SQLException {
// 目標(biāo):查詢總用戶數(shù)
QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
String sql = "select count(id) from user";
// ScalarHandler是處理單個數(shù)據(jù)的結(jié)果集
Long count = queryRunner.query(sql, new ScalarHandler<>());
System.out.println(count);
}
三、jar包版本不兼容問題
- 使用最新的MySQL驅(qū)動jar包
- 把驅(qū)動的類名改為:com.mysql.cj.jdbc.Driver
- 在訪問mysql的url后加入時區(qū)設(shè)置:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC