1. 程式人生 > >javaweb開發過程中小工具系列之帶事務的QueryRunner

javaweb開發過程中小工具系列之帶事務的QueryRunner

        DBUtils簡化了對資料庫的操作,再加上我們上一節講的JdbcUtils,兩者配合起來用,也是非常方便,但是操作在操作事務時,也會產生一些冗餘的程式碼。程式碼示例如下:

Jdbcutils

package cn.ccnu.jdbc;

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

import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils {
	//通過c3p0連線池得到DataSource
	private static DataSource ds = new ComboPooledDataSource();
	//定義一個Connection來判斷是否有事務
	private static Connection con = null;
	//返回DataSource
	public static DataSource getDataSource(){
		return ds;
	}
	
	//通過DataSource得到Connection
	public static Connection getConnection() throws SQLException{
		//如果開啟了事務,則con不為空,應該直接返回con
		if(con != null){
			return con;
		}
		return ds.getConnection();
	}
	
	// 開啟事務
	public static void beginTransaction() throws SQLException {
		//判斷con是否為空,如果不為空,則說明事務已經開啟
		if(con != null){
			throw new SQLException("事務已經開啟了,不能重複開啟事務");
		}
		//如果不為空,則開啟事務
		con = getConnection();
		//設定事務提交為手動
		con.setAutoCommit(false);
	}

	// 提交事務
	public static void commitTransaction() throws SQLException {
		//判斷con是否為空,如果為空,則說明沒有開啟事務
		if(con == null){
			throw new SQLException("沒有開啟事務,不能提交事務");
		}
		//如果con不為空,提交事務
		con.commit();
		//事務提交後,關閉連線
		con.close();
		//表示事務已經結束
		con = null;
	}

	// 回滾事務
	public static void rollbackTransaction() throws SQLException {
		//判斷con是否為空,如果為空,則說明沒有開啟事務,也就不能回滾事務
		if(con == null){
			throw new SQLException("沒有開啟事務,不能回滾事務");
		}
		//事務回滾
		con.rollback();
		//事務回滾後,關閉連線
		con.close();
		//把con置為null,表示事務已經結束
		con = null;
	}

	// 關閉事務
	public static void releaseConnection(Connection connection) throws SQLException {
		//如果引數連線與當前事務連線不相等,則說明引數連線不是事務連線,可以關閉,否則由事務關閉
		if(connection != null && con != connection){
			//如果連線沒有被關閉,關閉之
			if(!connection.isClosed()){
				connection.close();
			}
		}
	}
}
測試程式碼:
package cn.ccnu.jdbc;

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

import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;

public class JdbcUtilsTest {
	@Test
	public void test1() throws SQLException {
		String sql = "update user set salary = ? where id = ?";
		QueryRunner qr = new QueryRunner();
		Connection conn = JdbcUtils.getConnection();
		qr.update(conn, sql, "800", 3);
		JdbcUtils.releaseConnection(conn);
	}

	// 對事務的測試之沒有回滾
	@Test
	public void test2() throws SQLException {
		JdbcUtils.beginTransaction();
		Connection conn = JdbcUtils.getConnection();
		QueryRunner qr = new QueryRunner();
		String sql1 = "update user set salary = salary + ? where id = 1;";
		String sql2 = "update user set salary = salary - ? where id = 2;";
		qr.update(conn, sql1, 200);
		qr.update(conn, sql2, 200);
		JdbcUtils.commitTransaction();
		JdbcUtils.releaseConnection(conn);
	}

	// 對事務的測試之有會回滾
	@Test
	public void test3() {
		try {
			//開啟事務
			JdbcUtils.beginTransaction();
			//得到連線
			Connection conn = JdbcUtils.getConnection();
			//得到QueryRunner
			QueryRunner qr = new QueryRunner();
			
			String sql1 = "update user set salary = salary + ? where id = 1;";
			String sql2 = "update user set salary = salary - ? where id = 2;";
			//執行第一條sql
			qr.update(conn, sql1, 100);
			//認為製造異常
			int b = 1/0;
			qr.update(conn, sql2, 100);
			//提交事務
			JdbcUtils.commitTransaction();
			//關閉連線,在處理事務時,這句可以不寫,因為在提交或回滾時會關閉連線
			JdbcUtils.releaseConnection(conn);
		} catch (Exception e) {
			try {
				JdbcUtils.rollbackTransaction();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
}
        從上面的程式碼示例可以看出,功能可以被實現,但是產生了程式碼冗餘,比如說,每次都要得到連線,再把連線作為引數傳給QueryRunner,最後關閉連線。每次事務操作時,都要進行這幾步,那我們,是不是可以把這幾步抽取出來???我們的解決辦法是,重寫一個類TSQueryRunner來繼承QueryRunner,然後覆蓋其中帶有Connection的方法,這樣我們就不用再傳Connection進去了。但問題就在於開啟事務,提交事務,回滾事務時,所有的連線要是同一個連線,我們如何保證呢?我們所有的方法是,在JdbcUtils中在開啟事務時得到的的Connection存放在ThreadLocal中,這樣提交事務,回滾事務時所有的連線就是同一個連線了。

TSQueryRunner

package cn.ccnu.queryrunner;

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

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

public class TSQueryRunner extends QueryRunner {

	@Override
	public int[] batch(String sql, Object[][] params)
			throws SQLException {
		Connection conn = JdbcUtils.getConnection();
		int[] result = super.batch(conn, sql, params);
		JdbcUtils.releaseConnection(conn);
		return result;
	}

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

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

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

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

	@Override
	public int update(String sql) throws SQLException {
		Connection conn = JdbcUtils.getConnection();
		int result =  super.update(conn, sql);
		JdbcUtils.releaseConnection(conn);
		return result;
	}
	
}
JdbcUtils
package cn.ccnu.queryrunner;

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

import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils {
	//通過c3p0連線池得到DataSource
	private static DataSource ds = new ComboPooledDataSource();
	//定義一個Connection來判斷是否有事務,將con放在ThreadLocal中
	//那麼QueryRunner在使用事務時,就可以不用傳con,而交由util控制
	private static ThreadLocal<Connection> conn = new ThreadLocal<Connection>();
	//返回DataSource
	public static DataSource getDataSource(){
		return ds;
	}
	
	//通過DataSource得到Connection
	public static Connection getConnection() throws SQLException{
		//得到ThreadLocal中的connection
		Connection con = conn.get();
		//如果開啟了事務,則con不為空,應該直接返回con
		if(con != null){
			return con;
		}
		return ds.getConnection();
	}
	
	// 開啟事務
	public static void beginTransaction() throws SQLException {
		//得到ThreadLocal中的connection
		Connection con = conn.get();
		//判斷con是否為空,如果不為空,則說明事務已經開啟
		if(con != null){
			throw new SQLException("事務已經開啟了,不能重複開啟事務");
		}
		//如果不為空,則開啟事務
		con = getConnection();
		//設定事務提交為手動
		con.setAutoCommit(false);
		//把當前開啟的事務放入ThreadLocal中
		conn.set(con);
	}

	// 提交事務
	public static void commitTransaction() throws SQLException {
		//得到ThreadLocal中的connection
		Connection con = conn.get();
		//判斷con是否為空,如果為空,則說明沒有開啟事務
		if(con == null){
			throw new SQLException("沒有開啟事務,不能提交事務");
		}
		//如果con不為空,提交事務
		con.commit();
		//事務提交後,關閉連線
		con.close();
		//將連線移出ThreadLocal
		conn.remove();
	}

	// 回滾事務
	public static void rollbackTransaction() throws SQLException {
		//得到ThreadLocal中的connection
		Connection con = conn.get();
		//判斷con是否為空,如果為空,則說明沒有開啟事務,也就不能回滾事務
		if(con == null){
			throw new SQLException("沒有開啟事務,不能回滾事務");
		}
		//事務回滾
		con.rollback();
		//事務回滾後,關閉連線
		con.close();
		//將連線移出ThreadLocal
		conn.remove();
	}

	// 關閉事務
	public static void releaseConnection(Connection connection) throws SQLException {
		//得到ThreadLocal中的connection
		Connection con = conn.get();
		//如果引數連線與當前事務連線不相等,則說明引數連線不是事務連線,可以關閉,否則交由事務關閉
		if(connection != null && con != connection){
			//如果連線沒有被關閉,關閉之
			if(!connection.isClosed()){
				connection.close();
			}
		}
	}
}
測試程式碼
package cn.ccnu.queryrunner;

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

import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;

public class JdbcUtilsTest {
	// 沒有事務的測試
	@Test
	public void test1() throws SQLException {
		String sql = "update user set salary = ? where id = ?";
		QueryRunner qr = new TSQueryRunner();
		qr.update(sql, 800, 3);
	}

	// 對事務的測試之沒有回滾
	@Test
	public void test2() throws SQLException {
		JdbcUtils.beginTransaction();
		QueryRunner qr = new TSQueryRunner();
		String sql1 = "update user set salary = salary + ? where id = 1;";
		String sql2 = "update user set salary = salary - ? where id = 2;";
		qr.update(sql1, 200);
		qr.update(sql2, 200);
		JdbcUtils.commitTransaction();
	}

	// 對事務的測試之有會回滾
	@Test
	public void test3() {
		try {
			//開啟事務
			JdbcUtils.beginTransaction();
			//得到QueryRunner
			QueryRunner qr = new TSQueryRunner();
			String sql1 = "update user set salary = salary + ? where id = 1;";
			String sql2 = "update user set salary = salary - ? where id = 2;";
			//執行第一條sql
			qr.update(sql1, 100);
			//人為製造異常
			int b = 1/0;
			qr.update(sql2, 100);
			//提交事務
			JdbcUtils.commitTransaction();
		} catch (Exception e) {
			try {
				JdbcUtils.rollbackTransaction();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
}