javaweb開發過程中小工具系列之帶事務的QueryRunner
阿新 • • 發佈:2019-01-06
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(); } } } }
從上面的程式碼示例可以看出,功能可以被實現,但是產生了程式碼冗餘,比如說,每次都要得到連線,再把連線作為引數傳給QueryRunner,最後關閉連線。每次事務操作時,都要進行這幾步,那我們,是不是可以把這幾步抽取出來???我們的解決辦法是,重寫一個類TSQueryRunner來繼承QueryRunner,然後覆蓋其中帶有Connection的方法,這樣我們就不用再傳Connection進去了。但問題就在於開啟事務,提交事務,回滾事務時,所有的連線要是同一個連線,我們如何保證呢?我們所有的方法是,在JdbcUtils中在開啟事務時得到的的Connection存放在ThreadLocal中,這樣提交事務,回滾事務時所有的連線就是同一個連線了。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(); } } } }
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();
}
}
}
}