數據庫開發四:JDBC數據庫開發進階三(jdbcutils拓展QueryRunner處理事務以及處理多線程併發)

拓展QuerRunner

package jdbc2;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * Created by kevin on 2020/4/2.
 */
public class TxQueryRunner extends QueryRunner{
    @Override
    public int[] batch(String sql, Object[][] params) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        int []  result  = batch(connection,sql, params);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        T  result  = query(connection,sql, rsh, params);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        T  result  = query(connection,sql, rsh);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        int  result  = update(connection,sql);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql, Object param) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        int  result  = update(connection,sql,param);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql, Object... params) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        int  result  = update(connection,sql,params);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T insert(String sql, ResultSetHandler<T> rsh) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        T  result  = insert(connection,sql, rsh);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T insert(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        T  result  = insert(connection,sql, rsh,params);
        JdbcUtils.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T insertBatch(String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        T  result  = insertBatch(connection,sql, rsh, params);
        JdbcUtils.releaseConnection(connection);
        return result;
    }


}

dao層

package jdbc2;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * Created by kevin on 2020/3/29.
 */
public class AccountDao {
    public void update(String name,double money){
        try {
            QueryRunner queryRunner = new TxQueryRunner();
            String sql = "update account set balance = balance+? where name = ?";
            Object [] params = {money,name};
            //需要保證多次調用的是同一個連接
//            Connection connection = JdbcUtils.getConnection();
            queryRunner.update(sql,params);
//            JdbcUtils.releaseConnection(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

模擬service層

package jdbc2;

import org.junit.Test;

/**
 * Created by kevin on 2020/3/30.
 */
public class Demo1 {
    private AccountDao dao = new AccountDao();
    @Test
    public void ServiceMethod(){
        try {
            //事務連接不會被關閉
            JdbcUtils.beginTransaction();
            dao.update("zs",10000);
            if(true)throw new RuntimeException("出異常了,給我回滾!");//事務測試
            dao.update("ls",-10000);
            JdbcUtils.commitTransaction();
        } catch (Exception e) {
            try {
                JdbcUtils.rollbackTransaction();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw e;
        }
    }
}

jdbcutils


要點
jdbcutils處理多線程併發問題描述:
1如果開始事務同時被2個線程開啓,第二個將會拋出異常–已開啓事務
2如果第一個線程開啓後第二線程進行提交,第一個提交時會拋出異常——沒有事務
原因:共享了同一個connection
解決:使用ThreadLocal

package jdbc2;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 實現功能
 * 提供連接池獲取連接
 * 提供事務連接
 * 提供開關事務
 * 提供釋放非事務連接
 * 支持多線程併發訪問
 * Created by kevin on 2020/3/29.
 */
public class JdbcUtils {
    //加載配置文件的默認配置,需要配置c3p0-config.xml(放在src/main/Resource目錄下即可)
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //事務專用連接
//    private static Connection connection = null;

    //由原來的一個對象成爲了一個存儲對象的容器
    private static ThreadLocal<Connection> connectionThreadLocal = new InheritableThreadLocal<Connection>();
    /**
     * 如果connection不爲null那麼返回的就是事務專用連接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException{
        Connection connection = connectionThreadLocal.get();
        if(connection!=null){
            return connection;
        }else{
            return dataSource.getConnection();
        }
    }

    public static DataSource getDataSource(){
        return dataSource;
    }

    /**
     * 開啓事務
     * 1.獲取一個Connection,設置它的setAutoCommit(false)
     * 2.還要保證dao中使用的連接是我們剛剛創建的
     */
    public static void beginTransaction(){
        Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
        try {
            if(connection!=null){
                throw new SQLException("已經開啓事務,請勿重複操作");
            }
            connection = getConnection();
            connection.setAutoCommit(false);
            connectionThreadLocal.set(connection);//存儲當前線程專用連接
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事務
     * 1.獲取beginTransaction提供的Connection,然後調用commit方法
     */
    public static void commitTransaction(){
        Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
        try {
            if(connection==null){
                throw new SQLException("還沒有開啓事務!不能提交");
            }
            connection.commit();
            connection.close();
            connectionThreadLocal.remove();//移除當前線程專用連接
//            connection = null;//必須清空值否則下次會得到一個已經關閉的連接
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滾事務
     * 1.獲取beginTransaction提供的Connection,然後調用了rollback方法
     */
    public static void rollbackTransaction(){
        Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
        try {
            if(connection==null){
                throw new SQLException("還沒有開啓事務!不能回滾");
            }
            connection.rollback();
            connection.close();
            connectionThreadLocal.remove();//移除當前線程專用連接
//            connection= null;//必須清空值否則下次會得到一個已經關閉的連接
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 釋放非事務專用連接
     * 判斷它是不是事務專用連接,如果是,不關閉
     * 如果不是,就進行關閉
     *
     */
    public static void releaseConnection(Connection inCon) throws SQLException{
        Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
        //如果connection == null 說明現在沒有事務,那麼connection一定不是事務專用
        if(connection == null){
            inCon.close();
        }
        //如果connection!=null 說明有事務,那麼需要判斷連接參數是否與connection相等
        //若不等說明參數連接不是事務專用連接
        if(connection!=inCon){
            inCon.close();
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章