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();
}
}
}
}