一個SESSION所進行的全部更新操做要麼一塊兒成功,要麼一塊兒失敗java
舉個例子:A向B轉帳,轉帳這個流程中若是出現問題,事務可讓數據恢復成原來同樣【A帳戶的錢沒變,B帳戶的錢也沒變】。sql
事例說明:數據庫
/* * 咱們來模擬A向B帳號轉帳的場景 * A和B帳戶都有1000塊,如今我讓A帳戶向B帳號轉500塊錢 * * */
//JDBC默認的狀況下是關閉事務的,下面咱們看看關閉事務去操做轉帳操做有什麼問題
//A帳戶減去500塊
String sql = "UPDATE a SET money=money-500 ";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
//B帳戶多了500塊
String sql2 = "UPDATE b SET money=money+500";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
複製代碼
從上面看,咱們的確能夠發現A向B轉帳,成功了。但是**若是A向B轉帳的過程當中出現了問題呢?**下面模擬一下微信
//A帳戶減去500塊
String sql = "UPDATE a SET money=money-500 ";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
//這裏模擬出現問題
int a = 3 / 0;
String sql2 = "UPDATE b SET money=money+500";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
複製代碼
顯然,上面代碼是會拋出異常的,咱們再來查詢一下數據。A帳戶少了500塊錢,B帳戶的錢沒有增長。這明顯是不合理的。ide
咱們能夠經過事務來解決上面出現的問題工具
//開啓事務,對數據的操做就不會當即生效。
connection.setAutoCommit(false);
//A帳戶減去500塊
String sql = "UPDATE a SET money=money-500 ";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
//在轉帳過程當中出現問題
int a = 3 / 0;
//B帳戶多500塊
String sql2 = "UPDATE b SET money=money+500";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
//若是程序能執行到這裏,沒有拋出異常,咱們就提交數據
connection.commit();
//關閉事務【自動提交】
connection.setAutoCommit(true);
} catch (SQLException e) {
try {
//若是出現了異常,就會進到這裏來,咱們就把事務回滾【將數據變成原來那樣】
connection.rollback();
//關閉事務【自動提交】
connection.setAutoCommit(true);
} catch (SQLException e1) {
e1.printStackTrace();
}
複製代碼
上面的程序也同樣拋出了異常,A帳戶錢沒有減小,B帳戶的錢也沒有增長。this
注意:當Connection遇到一個未處理的SQLException時,系統會非正常退出,事務也會自動回滾,但若是程序捕獲到了異常,是須要在catch中顯式回滾事務的。spa
咱們還可使用savepoint設置中間點。若是在某地方出錯了,咱們設置中間點,回滾到出錯以前便可。code
應用場景:如今咱們要算一道數學題,算到後面發現算錯數了。前面的運算都是正確的,咱們不可能重頭再算【直接rollback】,最好的作法就是在保證前面算對的狀況下,設置一個保存點。從保存點開始從新算。對象
注意:savepoint不會結束當前事務,普通提交和回滾都會結束當前事務的
數據庫定義了4個隔離級別:
分別對應Connection類中的4個常量
髒讀:一個事務讀取到另一個事務未提交的數據
例子:A向B轉帳,A執行了轉帳語句,但A尚未提交事務,B讀取數據,發現本身帳戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再查看帳戶的錢時,發現錢並無多。
不可重複讀:一個事務讀取到另一個事務已經提交的數據,也就是說一個事務能夠看到其餘事務所作的修改
注:A查詢數據庫獲得數據,B去修改數據庫的數據,致使A屢次查詢數據庫的結果都不同【危害:A每次查詢的結果都是受B的影響的,那麼A查詢出來的信息就沒有意思了】
虛讀(幻讀):是指在一個事務內讀取到了別的事務插入的數據,致使先後讀取不一致。
注:和不可重複讀相似,但虛讀(幻讀)會讀到其餘事務的插入的數據,致使先後讀取不一致
簡單總結:髒讀是不可容忍的,不可重複讀和虛讀在必定的狀況下是能夠的【作統計的確定就不行】。
元數據其實就是數據庫,表,列的定義信息
即便咱們寫了一個簡單工具類,咱們的代碼仍是很是冗餘。對於增刪改而言,只有SQL和參數是不一樣的,咱們爲什麼不把這些相同的代碼抽取成一個方法?對於查詢而言,不一樣的實體查詢出來的結果集是不同的。咱們要使用元數據獲取結果集的信息,才能對結果集進行操做。
問題:咱們對數據庫的增刪改查都要鏈接數據庫,關閉資源,獲取PreparedSteatment對象,獲取Connection對象此類的操做,這樣的代碼重複率是極高的,因此咱們要對工具類進行加強
//咱們發現,增刪改只有SQL語句和傳入的參數是不知道的而已,因此讓調用該方法的人傳遞進來
//因爲傳遞進來的參數是各類類型的,並且數目是不肯定的,因此使用Object[]
public static void update(String sql, Object[] objects) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
//根據傳遞進來的參數,設置SQL佔位符的值
for (int i = 0; i < objects.length; i++) {
preparedStatement.setObject(i + 1, objects[i]);
}
//執行SQL語句
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
複製代碼
/* 1:對於查詢語句來講,咱們不知道對結果集進行什麼操做【經常使用的就是把數據封裝成一個Bean對象,封裝成一個List集合】 2:咱們能夠定義一個接口,讓調用者把接口的實現類傳遞進來 3:這樣接口調用的方法就是調用者傳遞進來實現類的方法。【策略模式】 */
//這個方法的返回值是任意類型的,因此定義爲Object。
public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
//根據傳遞進來的參數,設置SQL佔位符的值
if (objects != null) {
for (int i = 0; i < objects.length; i++) {
preparedStatement.setObject(i + 1, objects[i]);
}
}
resultSet = preparedStatement.executeQuery();
//調用調用者傳遞進來實現類的方法,對結果集進行操做
return rsh.hanlder(resultSet);
}
複製代碼
接口:
/* * 定義對結果集操做的接口,調用者想要對結果集進行什麼操做,只要實現這個接口便可 * */
public interface ResultSetHandler {
Object hanlder(ResultSet resultSet);
}
複製代碼
實現類:
//接口實現類,對結果集封裝成一個Bean對象
public class BeanHandler implements ResultSetHandler {
//要封裝成一個Bean對象,首先要知道Bean是什麼,這個也是調用者傳遞進來的。
private Class clazz;
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object hanlder(ResultSet resultSet) {
try {
//建立傳進對象的實例化
Object bean = clazz.newInstance();
if (resultSet.next()) {
//拿到結果集元數據
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
//獲取到每列的列名
String columnName = resultSetMetaData.getColumnName(i+1);
//獲取到每列的數據
String columnData = resultSet.getString(i+1);
//設置Bean屬性
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(bean,columnData);
}
//返回Bean對象
return bean;
}
複製代碼
【策略模式】簡單理解:
咱們並不知道調用者想對結果集進行怎麼樣的操做,因而讓調用者把想要作的操做對象傳遞過來
咱們只要用傳遞過來的對象對結果集進行封裝就行了。
對我我的理解,策略模式就是咱們在使用別人API時,可使用匿名內部類的時候。別人用的就是策略模式。
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y