這是我JDBC的第一篇java
http://blog.csdn.net/hon_3y/article/details/53535798sql
這是我JDBC的第二篇
http://blog.csdn.net/hon_3y/article/details/53725446數據庫
一個SESSION所進行的全部更新操做要麼一塊兒成功,要麼一塊兒失敗markdown
舉個例子:A向B轉帳,轉帳這個流程中若是出現問題,事務可讓數據恢復成原來同樣【A帳戶的錢沒變,B帳戶的錢也沒變】。ide
事例說明:工具
/* * 咱們來模擬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轉帳的過程當中出現了問題呢?下面模擬一下this
//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帳戶的錢沒有增長。這明顯是不合理的。spa
咱們能夠經過事務來解決上面出現的問題.net
//開啓事務,對數據的操做就不會當即生效。 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帳戶的錢也沒有增長。code
注意:當Connection遇到一個未處理的SQLException時,系統會非正常退出,事務也會自動回滾,但若是程序捕獲到了異常,是須要在catch中顯式回滾事務的。
咱們還可使用savepoint設置中間點。若是在某地方出錯了,咱們設置中間點,回滾到出錯以前便可。
應用場景:如今咱們要算一道數學題,算到後面發現算錯數了。前面的運算都是正確的,咱們不可能重頭再算【直接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; }