JDBC【事務、元數據、改造JDBC工具類】

1.事務

一個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


savapoint

咱們還可使用savepoint設置中間點。若是在某地方出錯了,咱們設置中間點,回滾到出錯以前便可。code

應用場景:如今咱們要算一道數學題,算到後面發現算錯數了。前面的運算都是正確的,咱們不可能重頭再算【直接rollback】,最好的作法就是在保證前面算對的狀況下,設置一個保存點。從保存點開始從新算。對象

注意:savepoint不會結束當前事務,普通提交和回滾都會結束當前事務的


事務的隔離級別

數據庫定義了4個隔離級別:

  1. Serializable【可避免髒讀,不可重複讀,虛讀】
  2. Repeatable read【可避免髒讀,不可重複讀】
  3. Read committed【可避免髒讀】
  4. Read uncommitted【級別最低,什麼都避免不了】

分別對應Connection類中的4個常量

  1. TRANSACTION_READ_UNCOMMITTED
  2. TRANSACTION_READ_COMMITTED
  3. TRANSACTION_REPEATABLE_READ
  4. TRANSACTION_SERIALIZABLE

髒讀:一個事務讀取到另一個事務未提交的數據

例子:A向B轉帳,A執行了轉帳語句,但A尚未提交事務,B讀取數據,發現本身帳戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再查看帳戶的錢時,發現錢並無多。


不可重複讀:一個事務讀取到另一個事務已經提交的數據,也就是說一個事務能夠看到其餘事務所作的修改

注:A查詢數據庫獲得數據,B去修改數據庫的數據,致使A屢次查詢數據庫的結果都不同【危害:A每次查詢的結果都是受B的影響的,那麼A查詢出來的信息就沒有意思了】


虛讀(幻讀):是指在一個事務內讀取到了別的事務插入的數據,致使先後讀取不一致。

注:和不可重複讀相似,但虛讀(幻讀)會讀到其餘事務的插入的數據,致使先後讀取不一致


簡單總結:髒讀是不可容忍的,不可重複讀和虛讀在必定的狀況下是能夠的【作統計的確定就不行】


2.元數據

什麼是元數據

元數據其實就是數據庫,表,列的定義信息

爲何咱們要用元數據

即便咱們寫了一個簡單工具類,咱們的代碼仍是很是冗餘。對於增刪改而言,只有SQL和參數是不一樣的,咱們爲什麼不把這些相同的代碼抽取成一個方法?對於查詢而言不一樣的實體查詢出來的結果集是不同的。咱們要使用元數據獲取結果集的信息,才能對結果集進行操做

  • ParameterMetaData --參數的元數據
  • ResultSetMetaData --結果集的元數據
  • DataBaseMetaData --數據庫的元數據

3.改造JDBC工具類

問題:咱們對數據庫的增刪改查都要鏈接數據庫,關閉資源,獲取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

相關文章
相關標籤/搜索