1. 程式人生 > >13-事務&資料庫連線池&DBUtiles

13-事務&資料庫連線池&DBUtiles

事務&資料庫連線池&DBUtils

事務

 Transaction  其實指的一組操作,裡面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麼都算失敗。 所有的資料都回歸到最初的狀態(回滾)

為什麼要有事務?

 為了確保邏輯的成功。 例子: 銀行的轉賬。 

使用命令列方式演示事務。

開啟事務

    start transaction;

 提交或者回滾事務

    commit; 提交事務, 資料將會寫到磁碟上的資料庫
    rollback ;  資料回滾,回到最初的狀態。

1. 關閉自動提交功能。



2. 演示事務



使用程式碼方式演示事務
 程式碼裡面的事務,主要是針對連線來的。 


  1.  通過conn.setAutoCommit(false )來關閉自動提交的設定。

  2. 提交事務  conn.commit();

  3. 回滾事務 conn.rollback();

    

 @Test
    public void testTransaction(){
        
        Connection conn = null;
        PreparedStatement ps 
= null; ResultSet rs = null; try { conn = JDBCUtil.getConn(); //連線,事務預設就是自動提交的。 關閉自動提交。 conn.setAutoCommit(false); String sql = "update account set money = money - ? where id = ?"; ps = conn.prepareStatement(sql);
//扣錢, 扣ID為1 的100塊錢 ps.setInt(1, 100); ps.setInt(2, 1); ps.executeUpdate(); int a = 10 /0 ; //加錢, 給ID為2 加100塊錢 ps.setInt(1, -100); ps.setInt(2, 2); ps.executeUpdate(); //成功: 提交事務。 conn.commit(); } catch (SQLException e) { try { //事變: 回滾事務 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtil.release(conn, ps, rs); } }



事務的特性

* 原子性

> 指的是 事務中包含的邏輯,不可分割。 

* 一致性

> 指的是 事務執行前後。資料完整性

* 隔離性

> 指的是 事務在執行期間不應該受到其他事務的影響

* 永續性

> 指的是 事務執行成功,那麼資料應該持久儲存到磁碟上。


事務的安全隱患 

不考慮隔離級別設定,那麼會出現以下問題。

* 讀
 髒讀 不可重讀讀  幻讀.


    * 髒讀

 一個事務讀到另外一個事務還未提交的資料

    * 不可重複讀 

 一個事務讀到了另外一個事務提交的資料 ,造成了前後兩次查詢結果不一致。


讀未提交 演示

1. 設定A視窗的隔離級別為 讀未提交 


2. 兩個視窗都分別開啟事務



* 寫

丟失更新



讀已提交演示 

1. 設定A視窗的隔離級別為 讀已提交



2. A B 兩個視窗都開啟事務, 在B視窗執行更新操作。



3. 在A視窗執行的查詢結果不一致。 一次是在B視窗提交事務之前,一次是在B視窗提交事務之後。



這個隔離級別能夠遮蔽 髒讀的現象, 但是引發了另一個問題  ,不可重複讀。


可序列化

如果有一個連線的隔離級別設定為了序列化 ,那麼誰先打開了事務, 誰就有了先執行的權利, 誰後開啟事務,誰就只能得著,等前面的那個事務,提交或者回滾後,才能執行。  但是這種隔離級別一般比較少用。 容易造成效能上的問題。 效率比較低。


* 按效率劃分,從高到低

 讀未提交  > 讀已提交  > 可重複讀  > 可序列化

* 按攔截程度 ,從高到底

可序列化 > 可重複讀 > 讀已提交 >  讀未提交


事務總結

需要掌握的

1. 在程式碼裡面會使用事務 

        conn.setAutoCommit(false);


        conn.commit();

        conn.rollback();


2. 事務只是針對連線連線物件,如果再開一個連線物件,那麼那是預設的提交。

3. 事務是會自動提交的。 


需要了解的

安全隱患

    讀
        髒讀
            一個事務讀到了另一個事務未提交的資料
        不可重複讀
            一個事務讀到了另一個事務已提交的資料,造成前後兩次查詢結果不一致
        幻讀
            一個事務讀到了另一個事務insert的資料 ,造成前後查詢結果不一致 。

    寫

        丟失更新。

隔離級別

讀未提交

引發問題: 髒讀 

讀已提交

 解決: 髒讀 , 引發: 不可重複讀

可重複讀

 解決: 髒讀 、 不可重複讀 , 未解決: 幻讀

可序列化

 解決: 髒讀、 不可重複讀 、 幻讀。

mySql 預設的隔離級別是 可重複讀

Oracle 預設的隔離級別是  讀已提交


丟失更新




解決丟失更新

* 悲觀鎖

可以在查詢的時候,加入 for update



* 樂觀鎖

> 要求程式設計師自己控制。 




資料庫連線池

1. 資料庫的連線物件建立工作,比較消耗效能。 

2.一開始現在記憶體中開闢一塊空間(集合) , 一開先往池子裡面放置 多個連線物件。  後面需要連線的話,直接從池子裡面去。不要去自己建立連線了。  使用完畢, 要記得歸還連線。確保連線物件能迴圈利用。




自定義資料庫連線池 


* 程式碼實現

* 出現的問題:

    1. 需要額外記住 addBack方法
 
     2. 單例。
 
     3. 無法面向介面程式設計。 
 
         UserDao dao = new UserDaoImpl();
         dao.insert();
 
 
         DataSource dataSource = new MyDataSource();
 
         因為接口裡面沒有定義addBack方法。 
 
     4. 怎麼解決?   以addBack 為切入點。

解決自定義資料庫連線池出現的問題。 

 由於多了一個addBack 方法,所以使用這個連線池的地方,需要額外記住這個方法,並且還不能面向介面程式設計。

我們打算修改介面中的那個close方法。  原來的Connection物件的close方法,是真的關閉連線。 
打算修改這個close方法,以後在呼叫close, 並不是真的關閉,而是歸還連線物件。


如何擴充套件某一個方法?

 原有的方法邏輯,不是我們想要的。 想修改自己的邏輯

1. 直接改原始碼  無法實現。

2. 繼承, 必須得知道這個介面的具體實現是誰。 

3. 使用裝飾者模式。 



開源連線池

 DBCP

1. 匯入jar檔案

2. 不使用配置檔案:

 

public void testDBCP01(){
        
            
            Connection conn = null;
            PreparedStatement ps = null;
            try {
                
                //1. 構建資料來源物件
                BasicDataSource dataSource = new BasicDataSource();
                //連的是什麼型別的資料庫, 訪問的是哪個資料庫 , 使用者名稱, 密碼。。
                //jdbc:mysql://localhost/bank 主協議:子協議 ://本地/資料庫
                dataSource.setDriverClassName("com.mysql.jdbc.Driver");
                dataSource.setUrl("jdbc:mysql://localhost/bank");
                dataSource.setUsername("root");
                dataSource.setPassword("root");
                
                
                //2. 得到連線物件
                conn = dataSource.getConnection();
                String sql = "insert into account values(null , ? , ?)";
                ps = conn.prepareStatement(sql);
                ps.setString(1, "admin");
                ps.setInt(2, 1000);
                
                ps.executeUpdate();
                
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                JDBCUtil.release(conn, ps);
            }
            
        }



2. 使用配置檔案方式:

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            Properties properties = new Properties();
            InputStream is = new FileInputStream("src//dbcpconfig.properties");
            properties.load(is);
            DataSource dataSource = factory.createDataSource(properties);
            
            //2. 得到連線物件
            conn = dataSource.getConnection();
            String sql = "insert into account values(null , ? , ?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "liangchaowei");
            ps.setInt(2, 100);
            
            ps.executeUpdate();
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.release(conn, ps);
        }


        



* C3P0

拷貝jar檔案 到 lib目錄

不使用配置檔案方式


       

  Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1. 建立datasource
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            //2. 設定連線資料的資訊
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            
            //忘記了---> 去以前的程式碼 ---> jdbc的文件
            dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            
            //2. 得到連線物件
            conn = dataSource.getConnection();
            String sql = "insert into account values(null , ? , ?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "admi234n");
            ps.setInt(2, 103200);
            
            ps.executeUpdate();
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.release(conn, ps);
        }

 

使用配置檔案方式


            

 //預設會找 xml 中的 default-config 分支。 
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            //2. 設定連線資料的資訊
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            

            //忘記了---> 去以前的程式碼 ---> jdbc的文件
            dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            
            //2. 得到連線物件
            conn = dataSource.getConnection();
            String sql = "insert into account values(null , ? , ?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "admi234n");
            ps.setInt(2, 103200);

 




DBUtils

增刪改

                //dbutils 只是幫我們簡化了CRUD 的程式碼, 但是連線的建立以及獲取工作。 不在他的考慮範圍
        QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
    
        
        //增加
        //queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
        
        //刪除
        //queryRunner.update("delete from account where id = ?", 5);
        
        //更新
        //queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);

 



查詢

1. 直接new介面的匿名實現類

        QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
    

        Account  account =  queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){

            @Override
            public Account handle(ResultSet rs) throws SQLException {
                Account account  =  new Account();
                while(rs.next()){
                    String name = rs.getString("name");
                    int money = rs.getInt("money");
                    
                    account.setName(name);
                    account.setMoney(money);
                }
                return account;
            }
             
         }, 6);
        
        System.out.println(account.toString());

 



2. 直接使用框架已經寫好的實現類。


    * 查詢單個物件

        QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
        //查詢單個物件
        Account account = queryRunner.query("select * from account where id = ?", 
                new BeanHandler<Account>(Account.class), 8);
    
    
    * 查詢多個物件

        QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
        List<Account> list = queryRunner.query("select * from account ",
                new BeanListHandler<Account>(Account.class));

ResultSetHandler 常用的實現類
以下兩個是使用頻率最高的

   

  BeanHandler,  查詢到的單個數據封裝成一個物件
    BeanListHandler, 查詢到的多個數據封裝 成一個List<物件>

------------------------------------------

    ArrayHandler,  查詢到的單個數據封裝成一個數組
    ArrayListHandler,  查詢到的多個數據封裝成一個集合 ,集合裡面的元素是陣列。 
    
    
    
    MapHandler,  查詢到的單個數據封裝成一個map
    MapListHandler,查詢到的多個數據封裝成一個集合 ,集合裡面的元素是map。 

 






ColumnListHandler
KeyedHandler
ScalarHandler


總結

事務

    使用命令列演示
    
    使用程式碼演示

髒讀、

不可重複讀、

幻讀
丟失更新

    悲觀鎖
    樂觀鎖

    4個隔離級別
        讀未提交
        讀已提交
        可重複讀
        可序列化

 


    

資料連線池

* DBCP
    
    不使用配置

    使用配置

* C3P0

    不使用配置

    使用配置 (必須掌握)

* 自定義連線池 

    裝飾者模式

DBUtils

 簡化了我們的CRUD , 裡面定義了通用的CRUD方法。 

    queryRunner.update();
    queryRunner.query