JavaWeb之事務&資料庫連線池

lvxfcjf發表於2021-09-09


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;

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

©著作權歸作者所有:來自51CTO部落格作者灰白世界的原創作品,如需轉載,請註明出處,否則將追究法律責任


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3486/viewspace-2819425/,如需轉載,請註明出處,否則將追究法律責任。

相關文章