1. 程式人生 > >JavaWeb之事務&資料庫連線池

JavaWeb之事務&資料庫連線池

1. 事務

Transaction 其實指的就是一組操作,裡面包含許多單一的邏輯,如果有一個邏輯沒有執行成功,那麼

個事務就是執行失敗,所有的資料都會回滾到未執行前的狀態。

事務是為解決資料安全操作提出的,事務控制實際上就是控制資料的安全訪問,比如銀行轉賬。

2. 事務的使用

  • 命令列方式
# 開啟事務
start transaction;
# 提交事務
commit;
# 回滾事務
rollback;
  • 程式碼方式
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo01 {
    @Test
    public void transactionDemo(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = ConnectionUtil.getConnection();
            //關閉事務自動提交
            conn.setAutoCommit(false);
            String sql = "select * from account";
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name") + "==" + rs.getInt("money"));
            }
            //所有操作執行完成後手動的提交一下
            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                //回滾事務
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            ConnectionUtil.release(rs, pstmt, conn);
        }

    }
}

3. 事務特性(ACID)

  • 原子性 事務中包含的邏輯不可再分
  • 一致性 事務執行前後,資料的完整性保持一直
  • 隔離性 事務在執行期間,不能受到其他事務的影響
  • 永續性 事務執行成功,應該持久化到磁碟上

4. 安全問題

    • 髒讀

    在A視窗中設定隔離級別為讀未提交,在A、B兩個視窗中開啟事務,在B視窗中修改資料。在A中可以查詢到B視窗中還未提交的資料。一個事務中讀取到另一個事務還未提交的資料,就是髒讀。讀到的是資料庫記憶體中的資料,並非磁碟上真正的資料。

    • 不可以重複讀

    在A視窗中設定隔離級別為讀已提交,在A、B兩個視窗中開啟事務,在B視窗中修改資料。在A中就不可以查詢到B視窗還未提交的資料了,這樣就解決了髒讀的問題,但是這樣會引發一個新的問題,那就是隻能讀取到已經提交的資料。這樣的話,前後讀取到的結果是不一致的,發生了不可重複讀,所謂不可重複度,就是不能執行多次讀取,否則會出現查詢結果不一致。

    將隔離級別設定為重複讀,就可以解決整個問題了。

    • 幻讀

    一個事務讀取到另一個事務已插入的資料,導致查詢結果不一致。

    將隔離級別設定為可序列化,就可以解決這些問題了,到底可序列化是怎麼解決這個問題的呢?

    在A視窗中設定隔離級別為可序列化,在A、B兩個視窗中開啟事務,在B視窗中修改資料,只有B視窗執行commit,A窗口才可以查詢資料。這個級別一般比較少用,因為它的效率比較低。

    • 丟失更新

    兩個不同的事務在某一時刻對同一資料執行修改操作 ,導致第一次操作資料丟失

    • 樂觀鎖

      l樂觀鎖認為事務不一定會產生丟失更新,讓事務進行併發修改,不對事務進行鎖定。發現併發修改某行資料時,樂觀鎖丟擲異常。讓使用者解決。可以通過給資料表新增自增的version欄位進行資料修改時,資料庫會檢測version欄位和事務中的version欄位是否一致。若不一致,丟擲異常,交給程式猿自己處理。

    • 悲觀鎖

      悲觀鎖認為一定會發生丟失更新,所以悲觀鎖要求一個事務執行提交之後,其他事務才能查詢修改資料。

5. 隔離級別

  • Read Uncommitted 讀未提交 ,引發髒讀問題
  • Read Committed 讀已提交,解決髒讀,引發不可重複讀問題(Oracle預設隔離級別)
  • Repeatable Read 重複讀,解決不可重複讀,未解決幻讀(MySQL預設隔離級別)
  • Serializable,可序列化 解決所有問題

隔離級別分類

  • 按效能從高到低可劃分為:讀未提交>讀已提交>重複讀>可序列化
  • 按攔截程式從高到低可劃分為:可序列化>重複讀>讀已提交>讀未提交

6. 資料庫連線池

資料庫在使用的時候再去建立連線,這是一件非常耗時的操作,為了改善使用者體驗,我們可以在程式開始的時候,在記憶體中開闢一塊空間,稱為資料庫連線池,一開始往池子裡放多個連線物件,如果有使用者需要使用資料庫連線,就從池子裡取物件,當操作完成後將連線歸還,這樣就可以做到連線複用

  • 自定義資料庫連線池
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * 資料庫連線池
 */
public class MyDataSource implements DataSource {

    List<Connection> dataSoucePool = new ArrayList<Connection>();

    public MyDataSource() {
        for (int i = 0; i < 10;i++){
            //將10個連線放到連線池中
            Connection conn = ConnectionUtil.getConnection();
            dataSoucePool.add(conn);
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (dataSoucePool.size() == 0){
            //如果連線池已經沒有空閒的連線了,擴容
            for (int i = 0; i < 5;i++){
                Connection conn = ConnectionUtil.getConnection();
                dataSoucePool.add(conn);
            }
        }
        //每次都移出連線池第一個連線物件
        Connection conn = dataSoucePool.remove(0);
        Connection connection = new ConnectionWrap(conn,dataSoucePool);
        return connection;
    }

    public Connection backConnectuon(Connection conn){
        dataSourcePool.add(conn);        
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
  • 解決自定義連線池出現的問題
自定義連線池出現了什麼問題呢?
自定義連線池多增加了一個backConnection方法來歸還連線,違背了面向介面程式設計的規範。我們可以使用裝飾者模式來包裝Connection類,以符合面向介面的規範。
import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 包裝Connection類
 */
public class ConnectionWrap implements java.sql.Connection {
    Connection connection = null;
    List<Connection> list = null;

    public ConnectionWrap(Connection connection, List<Connection> list) {
        this.connection = connection;
        this.list = list;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return connection.prepareStatement(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {

    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

    @Override
    public void commit() throws SQLException {

    }

    @Override
    public void rollback() throws SQLException {

    }

    @Override
    public void close() throws SQLException {
        System.out.println(list.size());
        list.add(connection);
        System.out.println(list.size());
    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return null;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {

    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {

    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {

    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {

    }

    @Override
    public void setHoldability(int holdability) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {

    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {

    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

7. 常用開源資料庫連線池有哪些?

  • DBCP
# 不使用配置檔案方式
public void demo(){
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        basicDataSource.setUrl("jdbc:mysql://localhost:3306/bank");
        basicDataSource.setUsername("root");
        basicDataSource.setPassword("123456");
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try{
            connection = basicDataSource.getConnection();
            String sql = "select * from account";
            pstmt = connection.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name") + rs.getInt("money"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ConnectionUtil.release(rs, pstmt, connection);
        }
    }
# 使用配置檔案方式
public void demo()  {

        BasicDataSourceFactory factory = new BasicDataSourceFactory();
        Properties properties = new Properties();
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            InputStream inputStream = new FileInputStream("src\\dbcpconfig.properties");
            properties.load(inputStream);
            DataSource dataSource = factory.createDataSource(properties);
            connection = dataSource.getConnection();
            String sql = "select * from account";
            pstmt = connection.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name") + rs.getInt("money"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ConnectionUtil.release(rs, pstmt, connection);
        }

    }
  • C3P0
# 不使用配置檔案方式
public void demo(){
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bank");
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try{
            connection = dataSource.getConnection();
            String sql = "select * from account";
            pstmt = connection.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name") + rs.getInt("money"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ConnectionUtil.release(rs, pstmt, connection);
        }
    }
# 使用配置檔案方式
public void demo()  {

        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = dataSource.getConnection();
            String sql = "select * from account";
            pstmt = connection.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name") + rs.getInt("money"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ConnectionUtil.release(rs, pstmt, connection);
        }
    }

8. DbUtils

  • 增刪改

    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);
  • 查詢

    • 直接new介面的匿名實現類
    public void demo(){
          QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
          Account account = null;
          try {
              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;
                  }
              }, 3);
          } catch (SQLException e) {
              e.printStackTrace();
          }
          System.out.println(account.toString());
      }
  • 直接使用框架已經寫好的實現類

    # 查詢單個物件
    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,:查詢到的多個數據封裝成一個集合 ,集合裡面的元素是陣列

    MapListHandler:查詢到的多個數據封裝成一個集合 ,集合裡面的元素是map