數據庫事務
- 事務:一組邏輯操作單元,使數據從一種狀態變換到另一種狀態。
- 事務處理(事務操作):保證所有事務都作爲一個工作單元來執行,即使出現了故障,都不能改變這種執行方式。當在一個事務中執行多個操作時,要麼所有的事務都被提交(commit),那麼這些修改就永久地保存下來;要麼數據庫管理系統將放棄所作的所有修改,整個事務回滾(rollback)到最初狀態
- 數據一旦提交,就不可回滾。
- 數據什麼時候意味着提交?
①,當一個連接對象被創建時,默認情況下是自動提交事務:每次執行一個 SQL 語句時,如果執行成功,就會向數據庫自動提交,而不能回滾。
②,關閉數據庫連接,數據就會自動的提交。如果多個操作,每個操作使用的是自己單獨的連接,則無法保證
事務。即同一個事務的多個操作必須在同一個連接下
- JDBC程序中爲了讓多個 SQL 語句作爲一個事務執行:
①,調用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務
②,在所有的 SQL 語句都成功執行後,調用 commit(); 方法提交事務
③,在出現異常時,調用 rollback(); 方法回滾事務
④,若此時 Connection 沒有被關閉,還可能被重複使用,則需要恢復其自動提交狀態setAutoCommit(true)。尤其是在使用數據庫連接池技術時,執行close()方法前,建議恢復自動提交狀態。
- 用戶AA向用戶BB轉賬100,代碼如下:
@Test
public void testJDBCTransaction() {
Connection conn = null;
try {
// 1.獲取數據庫連接
conn = JDBCUtils.getConnection();
// 2.開啓事務
conn.setAutoCommit(false);
// 3.進行數據庫操作
String sql1 = "update user_table set balance = balance - 100 where user = ?";
update(conn, sql1, "AA");
// 模擬網絡異常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
update(conn, sql2, "BB");
// 4.若沒有異常,則提交事務
conn.commit();
} catch (Exception e) {
// 5.若有異常,則回滾事務
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//6.恢復每次DML操作的自動提交功能
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//7.關閉連接
JDBCUtils.closeResource(conn, null, null);
}
}
//使用事務以後的通用的增刪改操作(version 2.0)
public void update(Connection conn ,String sql, Object... args) {
PreparedStatement ps = null;
try {
// 1.獲取PreparedStatement的實例 (或:預編譯sql語句)
ps = conn.prepareStatement(sql);
// 2.填充佔位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.執行sql語句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.關閉資源
JDBCUtils.closeResource(null, ps);
}
}
數據庫連接池
- 在使用開發基於數據庫的web程序時,傳統的模式基本是按以下步驟:
①,在主程序(如servlet、beans)中建立數據庫連接
②,進行sql操作
③,斷開數據庫連接
- 這種模式開發,存在的問題:
①,數據庫的連接資源並沒有得到很好的重複利用。若同時有幾百人甚至幾千人在線,頻繁的進行數據庫連接操作將佔用很多的系統資源,嚴重的甚至會造成服務器的崩潰。
②,對於每一次數據庫連接,使用完後都得斷開。否則,如果程序出現異常而未能關閉,將會導致數據庫系統中的內存泄漏,最終將導致重啓數據庫。
③,這種開發不能控制被創建的連接對象數,系統資源會被毫無顧及的分配出去,如連接過多,也可能導致內存泄漏,服務器崩潰。
- 數據庫連接池的基本思想:就是爲數據庫連接建立一個“緩衝池”。預先在緩衝池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩衝池”中取出一個,使用完畢之後再放回去。
- 數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重複使用一個現有的數據庫連接,而不是重新建立一個。
- 數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這麼多的連接數量。連接池的最大數據庫連接數量限定了這個連接池能佔有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。
- 工作原理:
- 數據庫連接池技術的優點
①, 資源重用
②, 更快的系統反應速度
③,新的資源分配手段
④,統一的連接管理,避免數據庫連接泄漏
JDBC 的數據庫連接池使用 javax.sql.DataSource 來表示,DataSource 只是一個接口,該接口通常由服務器(Weblogic, WebSphere, Tomcat)提供實現,也有一些開源組織提供實現:
- DBCP 是Apache提供的數據庫連接池。tomcat 服務器自帶dbcp數據庫連接池。速度相對c3p0較快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一個開源組織提供的一個數據庫連接池,速度相對較慢,穩定性還可以。hibernate官方推薦使用
- Proxool 是sourceforge下的一個開源項目數據庫連接池,有監控連接池狀態的功能,穩定性較c3p0差一點
- BoneCP 是一個開源組織提供的數據庫連接池,速度快
- Druid 是阿里提供的數據庫連接池,據說是集DBCP 、C3P0 、Proxool 優點於一身的數據庫連接池,但是速度不確定是否有BoneCP快
DataSource用來取代DriverManager來獲取Connection,獲取速度快,同時可以大幅度提高數據庫訪問速度。
- C3P0數據庫連接池
c3p0的jar包下載地址
private static DataSource cpds = new ComboPooledDataSource("c3p0");
public static Connection getConnection2() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
其中,src下的配置文件爲:【c3p0-config.xml】
<c3p0-config>
<named-config name="c3p0">
<!-- 獲取連接的4個基本信息 -->
<property name="user">root</property>
<property name="password">root</property>
<property name="jdbcUrl">jdbc:mysql:///test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 涉及到數據庫連接池的管理的相關屬性的設置 -->
<!-- 若數據庫中連接數不足時, 一次向數據庫服務器申請多少個連接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化數據庫連接池時連接的數量 -->
<property name="initialPoolSize">5</property>
<!-- 數據庫連接池中的最小的數據庫連接數 -->
<property name="minPoolSize">5</property>
<!-- 數據庫連接池中的最大的數據庫連接數 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 數據庫連接池可以維護的 Statement 的個數 -->
<property name="maxStatements">20</property>
<!-- 每個連接同時可以使用的 Statement 對象的個數 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
-
DBCP數據庫連接池
- DBCP 是 Apache 軟件基金組織下的開源連接池實現,該連接池依賴該組織下的另一個開源系統:Common-pool。如需使用該連接池實現,應在系統中增加如下兩個 jar 文件:
①,Commons-dbcp.jar:連接池的實現
②,Commons-pool.jar:連接池實現的依賴庫
Tomcat 的連接池正是採用該連接池來實現的
數據源和數據庫連接不同,數據源無需創建多個,它是產生數據庫連接的工廠,因此整個應用只需要一個數據源即可。
當數據庫訪問結束後,程序還是像以前一樣關閉數據庫連接:conn.close(); 但上面的代碼並沒有關閉數據庫的物理連接,它僅僅把數據庫連接釋放,歸還給了數據庫連接池。
配置屬性說明:
屬性 | 默認值 | 說明 |
---|---|---|
initialSize | 0 | 連接池啓動時創建的初始化連接數量 |
maxActive | 8 | 連接池中可同時連接的最大的連接數 |
maxIdle | 8 | 連接池中最大的空閒的連接數,超過的空閒連接將被釋放,如果設置爲負數表示不限制 |
minIdle | 0 | 連接池中最小的空閒的連接數,低於這個數量會被創建新的連接。該參數越接近maxIdle,性能越好,因爲連接的創建和銷燬,都是需要消耗資源的;但是不能太大。 |
maxWait | 無限制 | 最大等待時間,當沒有可用連接時,連接池等待連接釋放的最大時間,超過該時間限制會拋出異常,如果設置-1表示無限等待 |
poolPreparedStatements | false | 開啓池的Statement是否prepared |
maxOpenPreparedStatements | 無限制 | 開啓池的prepared 後的同時最大連接數 |
minEvictableIdleTimeMillis | 連接池中連接,在時間段內一直空閒, 被逐出連接池的時間 | |
removeAbandonedTimeout | 300 | 超過時間限制,回收沒有用(廢棄)的連接 |
removeAbandoned | false | 超過removeAbandonedTimeout時間後,是否進 行沒用連接(廢棄)的回收 |
private static DataSource source = null;
static {
try {
Properties pros = new Properties();
InputStream is = Dbcp.class.getClassLoader().getResourceAsStream("dbcp.properties");
pros.load(is);
//根據提供的BasicDataSourceFactory創建對應的DataSource對象
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
Connection conn = source.getConnection();
return conn;
}
其中,src下的配置文件爲:【dbcp.properties】
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=root
initialSize=10
- Druid(德魯伊)數據庫連接池
Druid是阿里巴巴開源平臺上一個數據庫連接池實現,它結合了C3P0、DBCP、Proxool等DB池的優點,同時加入了日誌監控,可以很好的監控DB池連接和SQL的執行情況,可以說是針對監控而生的DB連接池,可以說是目前最好的連接池之一。
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
pro.load(DruidTest.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
System.out.println(conn);
}
其中,src下的配置文件爲:【druid.properties】
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
詳細配置參數:
配置 | 缺省 | 說明 |
---|---|---|
name | 配置這個屬性的意義在於,如果存在多個數據源,監控的時候可以通過名字來區分開來。 如果沒有配置,將會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 連接數據庫的url,不同數據庫不一樣。例如:mysql :jdbc:mysql://10.20.153.104:3306/druid2 oracle :jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
password | 連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細看這裏:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
username | 連接數據庫的用戶名 | |
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檢測連接是否有效,做了這個配置會降低性能。 |
minIdle | false | 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能 |
minIdle | true | 建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效 |
timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 | |
numTestsPerEvictionRun | 不再使用,一個DruidDataSource只支持一個EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理連接初始化的時候執行的sql | |
exceptionSorter | 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄連接 | |
filters | 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wal | |
proxyFilters | 類型是List,如果同時配置了filters和proxyFilters,是組合關係,並非替換關係 |
Apache-DBUtils實現CRUD操作
- commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,並且使用dbutils能極大簡化jdbc編碼的工作量,同時也不會影響程序的性能。
- API介紹:
org.apache.commons.dbutils.QueryRunner
org.apache.commons.dbutils.ResultSetHandler
工具類:org.apache.commons.dbutils.DbUtils
-
DbUtils
-
DbUtils :提供如關閉連接、裝載JDBC驅動程序等常規工作的工具類,裏面的所有方法都是靜態的。主要方法如下:
public static void close(…) throws java.sql.SQLException
: DbUtils類提供了三個重載的關閉方法。這些方法檢查所提供的參數是不是NULL,如果不是的話,它們就關閉Connection、Statement和ResultSet。public static void closeQuietly(…):
這一類方法不僅能在Connection、Statement和ResultSet爲NULL情況下避免關閉,還能隱藏一些在程序中拋出的SQLEeception。public static void commitAndClose(Connection conn)throws SQLException
: 用來提交連接的事務,然後關閉連接public static void commitAndCloseQuietly(Connection conn)
: 用來提交連接,然後關閉連接,並且在關閉連接時不拋出SQL異常。public static void rollback(Connection conn)throws SQLException
:允許conn爲null,因爲方法內部做了判斷public static void rollbackAndClose(Connection conn)throws SQLException
rollbackAndCloseQuietly(Connection)
public static boolean loadDriver(java.lang.String driverClassName)
:這一方裝載並註冊JDBC驅動程序,如果成功就返回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException。
-
QueryRunner類
- 該類簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的數據庫操作,能夠大大減少編碼量。
- QueryRunner類提供了兩個構造器:
默認的構造器
需要一個 javax.sql.DataSource 來作參數的構造器 - QueryRunner類的主要方法
更新
public int update(Connection conn, String sql, Object... params) throws SQLException:
用來執行一個更新(插入、更新或刪除)操作。
…
插入
public T insert(Connection conn,String sql,ResultSetHandler 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 int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT,UPDATE, or DELETE
語句
public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throwsSQLException
:只支持INSERT語句
…
查詢
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException
:執行一個查詢操作,在這個查詢中,對象數組中的每個元素值被用來作爲查詢語句的置換參數。該方法會自行處理 PreparedStatement 和 ResultSet 的創建和關閉。
-
測試
@Test
public void testInsert() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int count = runner.update(conn, sql, "Tommey周", "[email protected]", "1992-09-08");
System.out.println("添加了" + count + "條記錄");
JDBCUtils.closeResource(conn, null);
}
- 該接口用於處理 java.sql.ResultSet,將數據按要求轉換爲另一種形式。
- ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)。
- 接口的主要實現類:
ArrayHandler
:把結果集中的第一行數據轉成對象數組。
ArrayListHandler
:把結果集中的每一行數據都轉成一個數組,再存放到List中。
BeanHandler
:將結果集中的第一行數據封裝到一個對應的JavaBean實例中。
BeanListHandler
:將結果集中的每一行數據都封裝到一個對應的JavaBean實例中,存放到List裏。
ColumnListHandler
:將結果集中某一列的數據存放到List中。
KeyedHandler
(name):將結果集中的每一行數據都封裝到一個Map裏,再把這些map再存到一個map裏,其key爲指定的key。
MapHandler
:將結果集中的第一行數據封裝到一個Map裏,key是列名,value就是對應的值。
MapListHandler
:將結果集中的每一行數據都封裝到一個Map裏,然後再存放到List。
ScalarHandler
:查詢單個值對象。
- 測試
/*
* 測試查詢:查詢一條記錄
*
* 使用ResultSetHandler的實現類:BeanHandler
*/
@Test
public void testQueryInstance() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "select id,name,email,birth from customers where id = ?";
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
/*
* 測試查詢:查詢多條記錄構成的集合
*
* 使用ResultSetHandler的實現類:BeanListHandler
*/
@Test
public void testQueryList() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection3();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 23);
list.forEach(System.out::println);
JDBCUtils.closeResource(conn, null);
}
/*
* 自定義ResultSetHandler的實現類
*/
@Test
public void testQueryInstance1() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
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");
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);
}
/*
* 如何查詢類似於最大的,最小的,平均的,總和,個數相關的數據,
* 使用ScalarHandler
*
*/
@Test
public void testQueryValue() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnection();
//測試一:
// 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);
}