第6章: 數(shù)據(jù)庫事務
6.1 數(shù)據(jù)庫事務介紹
事務:一組邏輯操作單元,使數(shù)據(jù)從一種狀態(tài)變換到另一種狀態(tài)是钥。
事務處理(事務操作):保證所有事務都作為一個工作單元來執(zhí)行涕烧,即使出現(xiàn)了故障,都不能改變這種執(zhí)行方式碌嘀。當在一個事務中執(zhí)行多個操作時秃励,要么所有的事務都被提交(commit),那么這些修改就永久地保存下來围辙;要么數(shù)據(jù)庫管理系統(tǒng)將放棄所作的所有修改我碟,整個事務回滾(rollback)到最初狀態(tài)。
為確保數(shù)據(jù)庫中數(shù)據(jù)的一致性姚建,數(shù)據(jù)的操縱應當是離散的成組的邏輯單元:當它全部完成時矫俺,數(shù)據(jù)的一致性可以保持,而當這個單元中的一部分操作失敗,整個事務應全部視為錯誤恳守,所有從起始點以后的操作應全部回退到開始狀態(tài)考婴。
6.2 JDBC事務處理
數(shù)據(jù)一旦提交,就不可回滾催烘。
-
數(shù)據(jù)什么時候意味著提交沥阱?
- 當一個連接對象被創(chuàng)建時,默認情況下是自動提交事務:每次執(zhí)行一個 SQL 語句時伊群,如果執(zhí)行成功考杉,就會向數(shù)據(jù)庫自動提交,而不能回滾舰始。
- 關(guān)閉數(shù)據(jù)庫連接崇棠,數(shù)據(jù)就會自動的提交。如果多個操作丸卷,每個操作使用的是自己單獨的連接枕稀,則無法保證事務。即同一個事務的多個操作必須在同一個連接下谜嫉。
-
JDBC程序中為了讓多個 SQL 語句作為一個事務執(zhí)行:
- 調(diào)用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務
- 在所有的 SQL 語句都成功執(zhí)行后萎坷,調(diào)用 commit(); 方法提交事務
- 在出現(xiàn)異常時,調(diào)用 rollback(); 方法回滾事務
若此時 Connection 沒有被關(guān)閉沐兰,還可能被重復使用哆档,則需要恢復其自動提交狀態(tài) setAutoCommit(true)。尤其是在使用數(shù)據(jù)庫連接池技術(shù)時住闯,執(zhí)行close()方法前瓜浸,建議恢復自動提交狀態(tài)。
【案例:用戶AA向用戶BB轉(zhuǎn)賬100】
public class Test1 {
/**
* 通用的增加刪除修改
*
* @param sql 需要執(zhí)行的sql語句
* @param objs 需要傳入的參數(shù)
* @throws Exception
*/
public static void update(Connection conn, String sql, Object... objs) {
PreparedStatement prep = null;
try {
prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
prep.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(null, prep);
}
}
@Test
public void test2() {
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
//取消數(shù)據(jù)的自動提交
conn.setAutoCommit(false);
String sql1 = "update user_table set balance=balance-100 where user = ?";
String sql2 = "update user_table set balance=balance+100 where user = ?";
update(conn, sql1, "AA");
System.out.println(10 / 0);
update(conn, sql2, "BB");
System.out.println("轉(zhuǎn)賬成功");
//提交事務
conn.commit();
} catch (Exception e) {
try {
//回滾數(shù)據(jù)
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
//修改為自動提交數(shù)據(jù)
//主要針對 使用數(shù)據(jù)庫連接池的使用
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn, null);
}
}
}
6.3 事務的ACID屬性
原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位比原,事務中的操作要么都發(fā)生插佛,要么都不發(fā)生。一致性(Consistency)
事務必須使數(shù)據(jù)庫從一個一致性狀態(tài)變換到另外一個一致性狀態(tài)量窘。隔離性(Isolation)
事務的隔離性是指一個事務的執(zhí)行不能被其他事務干擾朗涩,即一個事務內(nèi)部的操作及使用的數(shù)據(jù)對并發(fā)的其他事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾绑改。持久性(Durability)
持久性是指一個事務一旦被提交谢床,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的,接下來的其他操作和數(shù)據(jù)庫故障不應該對其有任何影響厘线。
6.3.1 數(shù)據(jù)庫的并發(fā)問題
-
對于同時運行的多個事務, 當這些事務訪問數(shù)據(jù)庫中相同的數(shù)據(jù)時, 如果沒有采取必要的隔離機制, 就會導致各種并發(fā)問題:
- 臟讀: 對于兩個事務 T1, T2, T1 讀取了已經(jīng)被 T2 更新但還沒有被提交的字段识腿。之后, 若 T2 回滾, T1讀取的內(nèi)容就是臨時且無效的。
- 不可重復讀: 對于兩個事務T1, T2, T1 讀取了一個字段, 然后 T2 更新了該字段造壮。之后, T1再次讀取同一個字段, 值就不同了渡讼。
- 幻讀: 對于兩個事務T1, T2, T1 從一個表中讀取了一個字段, 然后 T2 在該表中插入了一些新的行骂束。之后, 如果 T1 再次讀取同一個表, 就會多出幾行。
數(shù)據(jù)庫事務的隔離性: 數(shù)據(jù)庫系統(tǒng)必須具有隔離并發(fā)運行各個事務的能力, 使它們不會相互影響, 避免各種并發(fā)問題成箫。
一個事務與其他事務隔離的程度稱為隔離級別展箱。數(shù)據(jù)庫規(guī)定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 數(shù)據(jù)一致性就越好, 但并發(fā)性越弱。
6.3.2 四種隔離級別
-
數(shù)據(jù)庫提供的4種事務隔離級別:
Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE蹬昌。 Oracle 默認的事務隔離級別為: READ COMMITED 混驰。
Mysql 支持 4 種事務隔離級別。Mysql 默認的事務隔離級別為: REPEATABLE READ皂贩。
6.3.3 在MySql中設(shè)置隔離級別
每啟動一個 mysql 程序, 就會獲得一個單獨的數(shù)據(jù)庫連接. 每個數(shù)據(jù)庫連接都有一個全局變量 @@tx_isolation, 表示當前的事務隔離級別栖榨。
查看當前的隔離級別:
SELECT @@tx_isolation;
設(shè)置當前 mySQL 連接的隔離級別:
set transaction isolation level read committed;
設(shè)置數(shù)據(jù)庫系統(tǒng)的全局的隔離級別:
set global transaction isolation level read committed;
補充操作:
- 創(chuàng)建mysql數(shù)據(jù)庫用戶:
create user tom identified by 'abc123';
授予權(quán)限
#授予通過網(wǎng)絡方式登錄的tom用戶,對所有庫所有表的全部權(quán)限明刷,密碼設(shè)為abc123.
grant all privileges on *.* to tom@'%' identified by 'abc123';
#給tom用戶使用本地命令行方式婴栽,授予atguigudb這個庫下的所有表的插刪改查的權(quán)限。
grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
Java代碼設(shè)置數(shù)據(jù)庫的隔離級別
package cn.icanci.jdbc.transaction;
import cn.icanci.jdbc.domain.Customer;
import cn.icanci.jdbc.domain.User2;
import cn.icanci.jdbc.utils.JdbcUtils;
import jdk.nashorn.internal.scripts.JD;
import org.junit.Test;
import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.sql.*;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.transaction
* @Date: Created in 2020/2/16 11:23
* @ClassAction:
*/
public class Test1 {
/**
* 通用的增加刪除修改
*
* @param sql 需要執(zhí)行的sql語句
* @param objs 需要傳入的參數(shù)
* @throws Exception
*/
public static void update(Connection conn, String sql, Object... objs) {
PreparedStatement prep = null;
try {
prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
prep.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(null, prep);
}
}
@Test
public void test2() {
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
//取消數(shù)據(jù)的自動提交
conn.setAutoCommit(false);
String sql1 = "update user_table set balance=balance-100 where user = ?";
String sql2 = "update user_table set balance=balance+100 where user = ?";
update(conn, sql1, "AA");
System.out.println(10 / 0);
update(conn, sql2, "BB");
System.out.println("轉(zhuǎn)賬成功");
//提交事務
conn.commit();
} catch (Exception e) {
try {
//回滾數(shù)據(jù)
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
//修改為自動提交數(shù)據(jù)
//主要針對 使用數(shù)據(jù)庫連接池的使用
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn, null);
}
}
//使用Java代碼設(shè)置事務的隔離級別
public static <T>T getInstance(Connection conn, String sql,Class<T> clazz,Object... objs)throws Exception{
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
ResultSet resultSet = prep.executeQuery();
//獲取結(jié)果集的元數(shù)據(jù)
ResultSetMetaData metaData = resultSet.getMetaData();
//獲取結(jié)果集的列數(shù)
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//獲取每個列的名字
String columnName = metaData.getColumnName(i + 1);
//給Customer 指定 賦值
Field declaredField = clazz.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, value);
}
return t;
}
resultSet.close();
JdbcUtils.closeConnection(null,prep);
return null;
}
@Test
public void test4() throws Exception{
Connection conn = JdbcUtils.getConnection();
//獲取隔離級別
System.out.println(conn.getTransactionIsolation());
//設(shè)置隔離級別
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//取消自動提交
conn.setAutoCommit(false);
String sql = "select * from user_table where user = ?";
try {
User2 instance = getInstance(conn, sql, User2.class, "CC");
System.out.println(instance);
} catch (Exception e) {
e.printStackTrace();
} finally {
// JdbcUtils.closeConnection(conn,null);
}
}
@Test
public void test5()throws Exception{
Connection conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);
String sql = "update user_table set balance = balance +100 where user = ?";
update(conn,sql,"CC");
Thread.sleep(2000);
System.out.println("over");
conn.commit();
}
}
第7章:DAO及相關(guān)實現(xiàn)類 DAO設(shè)計模式
- DAO:Data Access Object訪問數(shù)據(jù)信息的類和接口辈末,包括了對數(shù)據(jù)的CRUD(Create愚争、Retrival、Update挤聘、Delete)准脂,而不包含任何業(yè)務相關(guān)的信息。有時也稱作:BaseDAO
- 作用:為了實現(xiàn)功能的模塊化檬洞,更有利于代碼的維護和升級。
此處 @DAO設(shè)計模式
第8章:數(shù)據(jù)庫連接池
8.1 JDBC數(shù)據(jù)庫連接池的必要性
-
在使用開發(fā)基于數(shù)據(jù)庫的web程序時沟饥,傳統(tǒng)的模式基本是按以下步驟:
- 在主程序(如servlet添怔、beans)中建立數(shù)據(jù)庫連接
- 進行sql操作
- 斷開數(shù)據(jù)庫連接
-
這種模式開發(fā),存在的問題:
- 普通的JDBC數(shù)據(jù)庫連接使用 DriverManager 來獲取贤旷,每次向數(shù)據(jù)庫建立連接的時候都要將 Connection 加載到內(nèi)存中广料,再驗證用戶名和密碼(得花費0.05s~1s的時間)。需要數(shù)據(jù)庫連接的時候幼驶,就向數(shù)據(jù)庫要求一個艾杏,執(zhí)行完成后再斷開連接。這樣的方式將會消耗大量的資源和時間盅藻。數(shù)據(jù)庫的連接資源并沒有得到很好的重復利用购桑。若同時有幾百人甚至幾千人在線,頻繁的進行數(shù)據(jù)庫連接操作將占用很多的系統(tǒng)資源氏淑,嚴重的甚至會造成服務器的崩潰勃蜘。
- 對于每一次數(shù)據(jù)庫連接,使用完后都得斷開假残。否則缭贡,如果程序出現(xiàn)異常而未能關(guān)閉,將會導致數(shù)據(jù)庫系統(tǒng)中的內(nèi)存泄漏,最終將導致重啟數(shù)據(jù)庫阳惹。(回憶:何為Java的內(nèi)存泄漏谍失?)
- 這種開發(fā)不能控制被創(chuàng)建的連接對象數(shù),系統(tǒng)資源會被毫無顧及的分配出去莹汤,如連接過多快鱼,也可能導致內(nèi)存泄漏,服務器崩潰体啰。
8.2 數(shù)據(jù)庫連接池技術(shù)
為解決傳統(tǒng)開發(fā)中的數(shù)據(jù)庫連接問題攒巍,可以采用數(shù)據(jù)庫連接池技術(shù)。
數(shù)據(jù)庫連接池的基本思想:就是為數(shù)據(jù)庫連接建立一個“緩沖池”荒勇。預先在緩沖池中放入一定數(shù)量的連接柒莉,當需要建立數(shù)據(jù)庫連接時,只需從“緩沖池”中取出一個沽翔,使用完畢之后再放回去兢孝。
數(shù)據(jù)庫連接池負責分配、管理和釋放數(shù)據(jù)庫連接仅偎,它允許應用程序重復使用一個現(xiàn)有的數(shù)據(jù)庫連接跨蟹,而不是重新建立一個。
-
數(shù)據(jù)庫連接池在初始化時將創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接放到連接池中橘沥,這些數(shù)據(jù)庫連接的數(shù)量是由最小數(shù)據(jù)庫連接數(shù)來設(shè)定的窗轩。無論這些數(shù)據(jù)庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數(shù)量座咆。連接池的最大數(shù)據(jù)庫連接數(shù)量限定了這個連接池能占有的最大連接數(shù)痢艺,當應用程序向連接池請求的連接數(shù)超過最大連接數(shù)量時,這些請求將被加入到等待隊列中介陶。
工作原理:
數(shù)據(jù)庫連接池技術(shù)的優(yōu)點
1. 資源重用
由于數(shù)據(jù)庫連接得以重用堤舒,避免了頻繁創(chuàng)建,釋放連接引起的大量性能開銷哺呜。在減少系統(tǒng)消耗的基礎(chǔ)上舌缤,另一方面也增加了系統(tǒng)運行環(huán)境的平穩(wěn)性。
2. 更快的系統(tǒng)反應速度
數(shù)據(jù)庫連接池在初始化過程中某残,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于連接池中備用国撵。此時連接的初始化工作均已完成。對于業(yè)務請求處理而言玻墅,直接利用現(xiàn)有可用連接卸留,避免了數(shù)據(jù)庫連接初始化和釋放過程的時間開銷,從而減少了系統(tǒng)的響應時間
3. 新的資源分配手段
對于多應用共享同一數(shù)據(jù)庫的系統(tǒng)而言椭豫,可在應用層通過數(shù)據(jù)庫連接池的配置耻瑟,實現(xiàn)某一應用最大可用數(shù)據(jù)庫連接數(shù)的限制旨指,避免某一應用獨占所有的數(shù)據(jù)庫資源
4. 統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄漏
在較為完善的數(shù)據(jù)庫連接池實現(xiàn)中喳整,可根據(jù)預先的占用超時設(shè)定谆构,強制回收被占用連接,從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄露
8.3 多種開源的數(shù)據(jù)庫連接池
- JDBC 的數(shù)據(jù)庫連接池使用 javax.sql.DataSource 來表示框都,DataSource 只是一個接口搬素,該接口通常由服務器(Weblogic, WebSphere, Tomcat)提供實現(xiàn),也有一些開源組織提供實現(xiàn):
- DBCP 是Apache提供的數(shù)據(jù)庫連接池魏保。tomcat 服務器自帶dbcp數(shù)據(jù)庫連接池熬尺。速度相對c3p0較快,但因自身存在BUG谓罗,Hibernate3已不再提供支持粱哼。
- C3P0 是一個開源組織提供的一個數(shù)據(jù)庫連接池,速度相對較慢檩咱,穩(wěn)定性還可以揭措。hibernate官方推薦使用
- Proxool 是sourceforge下的一個開源項目數(shù)據(jù)庫連接池,有監(jiān)控連接池狀態(tài)的功能刻蚯,穩(wěn)定性較c3p0差一點
- BoneCP 是一個開源組織提供的數(shù)據(jù)庫連接池绊含,速度快
- Druid 是阿里提供的數(shù)據(jù)庫連接池,據(jù)說是集DBCP 炊汹、C3P0 躬充、Proxool 優(yōu)點于一身的數(shù)據(jù)庫連接池,但是速度不確定是否有BoneCP快
- DataSource 通常被稱為數(shù)據(jù)源讨便,它包含連接池和連接池管理兩個部分充甚,習慣上也經(jīng)常把 DataSource 稱為連接池
- DataSource用來取代DriverManager來獲取Connection,獲取速度快器钟,同時可以大幅度提高數(shù)據(jù)庫訪問速度。
- 特別注意:
- 數(shù)據(jù)源和數(shù)據(jù)庫連接不同妙蔗,數(shù)據(jù)源無需創(chuàng)建多個傲霸,它是產(chǎn)生數(shù)據(jù)庫連接的工廠,因此整個應用只需要一個數(shù)據(jù)源即可眉反。
- 當數(shù)據(jù)庫訪問結(jié)束后昙啄,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫連接:conn.close(); 但conn.close()并沒有關(guān)閉數(shù)據(jù)庫的物理連接,它僅僅把數(shù)據(jù)庫連接釋放寸五,歸還給了數(shù)據(jù)庫連接池梳凛。
8.3.1 C3P0數(shù)據(jù)庫連接池
- 獲取連接方式一
@Test
public void testC3p0()throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///test");
dataSource.setUser("root");
dataSource.setPassword("ok");
//設(shè)置初始的數(shù)據(jù)庫連接池個數(shù)
dataSource.setInitialPoolSize(10);
//設(shè)置最大的鏈接個數(shù)
dataSource.setMaxPoolSize(100);
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
- 獲取連接方式二
private static DataSource cpds = new ComboPooledDataSource("helloc3p0");
@Test
public void testC3p0XML()throws Exception{
//使用C3P0數(shù)據(jù)庫連接池的配置文件方式,獲取數(shù)據(jù)庫的連接:推薦
Connection connection = cpds.getConnection();
System.out.println(connection);
}
@Test
public void testC3p0XML()throws Exception{
//使用C3P0數(shù)據(jù)庫連接池的配置文件方式梳杏,獲取數(shù)據(jù)庫的連接:推薦
ComboPooledDataSource dataSource = new ComboPooledDataSource("helloc3p0");
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
其中韧拒,配置文件為:c3p0-config.xml 名字不要修改
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="helloc3p0">
<!-- 獲取連接的4個基本信息 -->
<property name="user">root</property>
<property name="password">ok</property>
<property name="jdbcUrl">jdbc:mysql:///test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 涉及到數(shù)據(jù)庫連接池的管理的相關(guān)屬性的設(shè)置 -->
<!-- 若數(shù)據(jù)庫中連接數(shù)不足時, 一次向數(shù)據(jù)庫服務器申請多少個連接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化數(shù)據(jù)庫連接池時連接的數(shù)量 -->
<property name="initialPoolSize">5</property>
<!-- 數(shù)據(jù)庫連接池中的最小的數(shù)據(jù)庫連接數(shù) -->
<property name="minPoolSize">5</property>
<!-- 數(shù)據(jù)庫連接池中的最大的數(shù)據(jù)庫連接數(shù) -->
<property name="maxPoolSize">10</property>
<!-- C3P0 數(shù)據(jù)庫連接池可以維護的 Statement 的個數(shù) -->
<property name="maxStatements">20</property>
<!-- 每個連接同時可以使用的 Statement 對象的個數(shù) -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
抽取獲取和關(guān)閉連接的操作 使用c3p0連接池
package cn.icanci.jdbc.c3p0Utils;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.c3p0Utils
* @Date: Created in 2020/2/16 14:20
* @ClassAction:
*/
public class JdbcC3p0Utils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource("helloc3p0");
/**
* 獲取連接
* @return 返回 c3p0 連接池的連接
* @throws Exception
*/
public static Connection getConnection() throws Exception{
Connection conn = dataSource.getConnection();
return conn;
}
public static void closeConnection(Connection conn,PreparedStatement ps,ResultSet rs){
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
8.3.2 DBCP數(shù)據(jù)庫連接池
- DBCP 是 Apache 軟件基金組織下的開源連接池實現(xiàn)淹接,該連接池依賴該組織下的另一個開源系統(tǒng):Common-pool。如需使用該連接池實現(xiàn)叛溢,應在系統(tǒng)中增加如下兩個 jar 文件:
- Commons-dbcp.jar:連接池的實現(xiàn)
- Commons-pool.jar:連接池實現(xiàn)的依賴庫
- Tomcat 的連接池正是采用該連接池來實現(xiàn)的塑悼。該數(shù)據(jù)庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用楷掉。
- 數(shù)據(jù)源和數(shù)據(jù)庫連接不同厢蒜,數(shù)據(jù)源無需創(chuàng)建多個,它是產(chǎn)生數(shù)據(jù)庫連接的工廠烹植,因此整個應用只需要一個數(shù)據(jù)源即可斑鸦。
- 當數(shù)據(jù)庫訪問結(jié)束后,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫連接:conn.close(); 但上面的代碼并沒有關(guān)閉數(shù)據(jù)庫的物理連接草雕,它僅僅把數(shù)據(jù)庫連接釋放巷屿,歸還給了數(shù)據(jù)庫連接池。
- 配置屬性說明
屬性 | 默認值 | 說明 |
---|---|---|
initialSize | 0 | 連接池啟動時創(chuàng)建的初始化連接數(shù)量 |
maxActive | 8 | 連接池中可同時連接的最大的連接數(shù) |
maxIdle | 8 | 連接池中最大的空閑的連接數(shù)促绵,超過的空閑連接將被釋放攒庵,如果設(shè)置為負數(shù)表示不限制 |
minIdle | 0 | 連接池中最小的空閑的連接數(shù),低于這個數(shù)量會被創(chuàng)建新的連接败晴。該參數(shù)越接近maxIdle浓冒,性能越好,因為連接的創(chuàng)建和銷毀尖坤,都是需要消耗資源的稳懒;但是不能太大。 |
maxWait | 無限制 | 最大等待時間慢味,當沒有可用連接時场梆,連接池等待連接釋放的最大時間,超過該時間限制會拋出異常纯路,如果設(shè)置-1表示無限等待 |
poolPreparedStatements | false | 開啟池的Statement是否prepared |
maxOpenPreparedStatements | 無限制 | 開啟池的prepared 后的同時最大連接數(shù) |
minEvictableIdleTimeMillis | 連接池中連接持际,在時間段內(nèi)一直空閑偿短, 被逐出連接池的時間 | |
removeAbandonedTimeout | 300 | 超過時間限制,回收沒有用(廢棄)的連接 |
removeAbandoned | false | 超過removeAbandonedTimeout時間后,是否進 行沒用連接(廢棄)的回收 |
- 獲取連接方式一:
@Test
public void test1()throws Exception{
//創(chuàng)建了dbcp的連接池
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setUsername("root");
dataSource.setPassword("ok");
//設(shè)置其他的數(shù)據(jù)庫連接池的屬性
dataSource.setInitialSize(10);
dataSource.setMaxActive(10);
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
- 獲取連接方式二:
@Test
public void test2()throws Exception{
Properties properties = new Properties();
InputStream inputStream = DbcpTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(inputStream);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=ok
initialSize=10
#...
8.3.3 Druid(德魯伊)數(shù)據(jù)庫連接池
Druid是阿里巴巴開源平臺上一個數(shù)據(jù)庫連接池實現(xiàn)展鸡,它結(jié)合了C3P0泼舱、DBCP杯活、Proxool等DB池的優(yōu)點缨恒,同時加入了日志監(jiān)控,可以很好的監(jiān)控DB池連接和SQL的執(zhí)行情況搓逾,可以說是針對監(jiān)控而生的DB連接池卷谈,可以說是目前最好的連接池之一。
使用方式1
@Test
public void test1() throws Exception{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setUsername("root");
dataSource.setPassword("ok");
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
使用方式2
@Test
public void test2() throws Exception{
Properties properties = new Properties();
InputStream inputStream = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(inputStream);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
配置文件 注意 :配置文件必須這樣寫 和官方手冊的一致 否則報錯
url=jdbc:mysql:///test
driverClassName=com.mysql.jdbc.Driver
username=root
password=ok
配置 | 缺省 | 說明 |
---|---|---|
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。詳細看這里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根據(jù)url自動識別 這一項可配可不配而昨,如果不配置druid會根據(jù)url自動識別dbType救氯,然后選擇相應的driverClassName(建議配置下) | |
initialSize | 0 | 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法歌憨,或者第一次getConnection時 |
maxActive | 8 | 最大連接池數(shù)量 |
maxIdle | 8 | 已經(jīng)不再使用着憨,配置了也沒效果 |
minIdle | 最小連接池數(shù)量 | |
maxWait | 獲取連接時最大等待時間,單位毫秒务嫡。配置了maxWait之后甲抖,缺省啟用公平鎖,并發(fā)效率會有所下降心铃,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖准谚。 | |
poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache去扣。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大柱衔,比如說oracle。在mysql下建議關(guān)閉愉棱。 |
maxOpenPreparedStatements | -1 | 要啟用PSCache唆铐,必須配置大于0,當大于0時奔滑,poolPreparedStatements自動觸發(fā)修改為true艾岂。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題朋其,可以把這個數(shù)值配置大一些王浴,比如說100 |
validationQuery | 用來檢測連接是否有效的sql,要求是一個查詢語句令宿。如果validationQuery為null叼耙,testOnBorrow腕窥、testOnReturn粒没、testWhileIdle都不會其作用。 | |
testOnBorrow | true | 申請連接時執(zhí)行validationQuery檢測連接是否有效簇爆,做了這個配置會降低性能癞松。 |
testOnReturn | false | 歸還連接時執(zhí)行validationQuery檢測連接是否有效爽撒,做了這個配置會降低性能 |
testWhileIdle | false | 建議配置為true,不影響性能响蓉,并且保證安全性硕勿。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis枫甲,執(zhí)行validationQuery檢測連接是否有效源武。 |
timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據(jù),詳細看testWhileIdle屬性的說明 | |
numTestsPerEvictionRun | 不再使用想幻,一個DruidDataSource只支持一個EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理連接初始化的時候執(zhí)行的sql | |
exceptionSorter | 根據(jù)dbType自動識別 當數(shù)據(jù)庫拋出一些不可恢復的異常時粱栖,拋棄連接 | |
filters | 屬性類型是字符串,通過別名的方式配置擴展插件脏毯,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 類型是List闹究,如果同時配置了filters和proxyFilters,是組合關(guān)系食店,并非替換關(guān)系 |
第9章:Apache-DBUtils實現(xiàn)CRUD操作
9.1 Apache-DBUtils簡介
commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫渣淤,它是對JDBC的簡單封裝,學習成本極低吉嫩,并且使用dbutils能極大簡化jdbc編碼的工作量价认,同時也不會影響程序的性能。
-
API介紹:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
- 工具類:org.apache.commons.dbutils.DbUtils
-
API包說明:
9.2 主要API的使用
9.2.1 DbUtils
-
DbUtils :提供如關(guān)閉連接率挣、裝載JDBC驅(qū)動程序等常規(guī)工作的工具類刻伊,里面的所有方法都是靜態(tài)的。主要方法如下:
public static void close(…) throws java.sql.SQLException: DbUtils類提供了三個重載的關(guān)閉方法椒功。這些方法檢查所提供的參數(shù)是不是NULL捶箱,如果不是的話动漾,它們就關(guān)閉Connection、Statement和ResultSet晨川。
public static void closeQuietly(…): 這一類方法不僅能在Connection、Statement和ResultSet為NULL情況下避免關(guān)閉删豺,還能隱藏一些在程序中拋出的SQLEeception。
public static void commitAndClose(Connection conn)throws SQLException: 用來提交連接的事務妈拌,然后關(guān)閉連接
public static void commitAndCloseQuietly(Connection conn): 用來提交連接,然后關(guān)閉連接蓬蝶,并且在關(guān)閉連接時不拋出SQL異常尘分。
public static void rollback(Connection conn)throws SQLException:允許conn為null,因為方法內(nèi)部做了判斷
public static void rollbackAndClose(Connection conn)throws SQLException
rollbackAndCloseQuietly(Connection)
public static boolean loadDriver(java.lang.String driverClassName):這一方裝載并注冊JDBC驅(qū)動程序著摔,如果成功就返回true定续。使用該方法私股,你不需要捕捉這個異常ClassNotFoundException。
9.2.2 QueryRunner類
該類簡單化了SQL查詢庇茫,它與ResultSetHandler組合在一起使用可以完成大部分的數(shù)據(jù)庫操作旦签,能夠大大減少編碼量。
-
QueryRunner類提供了兩個構(gòu)造器:
默認的構(gòu)造器
需要一個 javax.sql.DataSource 來作參數(shù)的構(gòu)造器
-
QueryRunner類的主要方法:
-
更新
public int update(Connection conn, String sql, Object... params) throws SQLException:用來執(zhí)行一個更新(插入偿曙、更新或刪除)操作羔巢。
......
-
插入
public <T> T insert(Connection conn,String sql,ResultSetHandler<T> rsh, Object... params) throws SQLException:只支持INSERT語句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自動生成的鍵值
....
-
批處理
-
查詢
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:執(zhí)行一個查詢操作启摄,在這個查詢中歉备,對象數(shù)組中的每個元素值被用來作為查詢語句的置換參數(shù)匪燕。該方法會自行處理 PreparedStatement 和 ResultSet 的創(chuàng)建和關(guān)閉。
......
-
測試 增加和刪除
public class dbutils {
@Test
public void test1() throws Exception {
QueryRunner runner = new QueryRunner();
Connection connection = JdbcUtils.getConnection();
runner.update(connection, "insert into user_table (user,password,balance) values (?,?,?)", "哈希", "haxilove", 1255);
JdbcUtils.closeConnection(connection,null);
}
@Test
public void test2() throws Exception {
QueryRunner runner = new QueryRunner();
Connection connection = JdbcUtils.getConnection();
int haxi = runner.update(connection, "delete from user_table where user =?", "哈希");
System.out.println(haxi);
JdbcUtils.closeConnection(connection,null);
}
}
9.2.3 ResultSetHandler接口及實現(xiàn)類
該接口用于處理 java.sql.ResultSet龟再,將數(shù)據(jù)按要求轉(zhuǎn)換為另一種形式利凑。
ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)。
-
接口的主要實現(xiàn)類:
ArrayHandler:把結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)成對象數(shù)組截碴。
ArrayListHandler:把結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)成一個數(shù)組日丹,再存放到List中蚯嫌。
BeanHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個對應的JavaBean實例中。
BeanListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個對應的JavaBean實例中束凑,存放到List里栅盲。
ColumnListHandler:將結(jié)果集中某一列的數(shù)據(jù)存放到List中。
KeyedHandler(name):將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個Map里谈秫,再把這些map再存到一個map里,其key為指定的key该编。
MapHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個Map里硕淑,key是列名,value就是對應的值于樟。
MapListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個Map里,然后再存放到List
ScalarHandler:查詢單個值對象
- 測試 查詢 查詢一個
@Test
public void test3() throws Exception {
QueryRunner runner = new QueryRunner();
Connection connection = JdbcUtils.getConnection();
User2 query = runner.query(connection, "select * from user_table where user = ?", new BeanHandler<User2>(User2.class),"AA");
System.out.println(query);
JdbcUtils.closeConnection(connection,null);
}
- 查詢 查詢多個
@Test
public void test4() throws Exception {
QueryRunner runner = new QueryRunner();
Connection connection = JdbcUtils.getConnection();
List<User2> query = runner.query(connection, "select * from user_table ", new BeanListHandler<User2>(User2.class));
query.forEach(System.out::println);
JdbcUtils.closeConnection(connection,null);
}
/*
* 自定義ResultSetHandler的實現(xiàn)類
*/
@Test
public void testQueryInstance1() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "select id,name,email,birth from customers where id = ?";
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet rs) throws SQLException {
System.out.println("handle");
// return new Customer(1,"Tom","tom@126.com",new Date(123323432L));
if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
return new Customer(id, name, email, birth);
}
return null;
}
};
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
/*
* 如何查詢類似于最大的奢米,最小的鬓长,平均的涉波,總和,個數(shù)相關(guān)的數(shù)據(jù)苍日,
* 使用ScalarHandler
*
*/
@Test
public void testQueryValue() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
//測試一:
// String sql = "select count(*) from customers where id < ?";
// ScalarHandler handler = new ScalarHandler();
// long count = (long) runner.query(conn, sql, handler, 20);
// System.out.println(count);
//測試二:
String sql = "select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date birth = (Date) runner.query(conn, sql, handler);
System.out.println(birth);
JDBCUtils.closeResource(conn, null);
}
JDBC總結(jié)
總結(jié)
@Test
public void testUpdateWithTx() {
Connection conn = null;
try {
//1.獲取連接的操作(
//① 手寫的連接:JDBCUtils.getConnection();
//② 使用數(shù)據(jù)庫連接池:C3P0;DBCP;Druid
//2.對數(shù)據(jù)表進行一系列CRUD操作
//① 使用PreparedStatement實現(xiàn)通用的增刪改窗声、查詢操作(version 1.0 \ version 2.0)
//version2.0的增刪改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查詢 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
//② 使用dbutils提供的jar包中提供的QueryRunner類
//提交數(shù)據(jù)
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾數(shù)據(jù)
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
//3.關(guān)閉連接等操作
//① JDBCUtils.closeResource();
//② 使用dbutils提供的jar包中提供的DbUtils類提供了關(guān)閉的相關(guān)操作
}
}