上面介绍了JDBC开发中事务处理的3种方式,下面再介绍的一种使用ThreadLocal + Filter进行统一的事务处理,这种方式主要是使用过滤器进行统一的事务处理,如下图所示
1、编写一个事务过滤器TransactionFilter
代码如下:
package wkcto.com.web.filter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import wkcto.com.util.JdbcUtils;
/**
* @ClassName: TransactionFilter
* @Description:ThreadLocal + Filter 统一处理数据库事务
*
*/
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Connection connection = null;
try {
//1、获取数据库连接对象Connection
connection = JdbcUtils.getConnection();
//2、开启事务
connection.setAutoCommit(false);
//3、利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
ConnectionContext.getInstance().bind(connection);
//4、把请求转发给目标Servlet
chain.doFilter(request, response);
//5、提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//6、回滚事务
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//req.setAttribute("errMsg", e.getMessage());
//req.getRequestDispatcher("/error.jsp").forward(req, res);
//出现异常之后跳转到错误页面
res.sendRedirect(req.getContextPath()+"/error.jsp");
}finally{
//7、解除绑定
ConnectionContext.getInstance().remove();
//8、关闭数据库连接
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void destroy() {
}
}
我们在TransactionFilter中把获取到的数据库连接使用ThreadLocal绑定到当前线程之后,在DAO层还需要从ThreadLocal中取出数据库连接来操作数据库,因此需要编写一个ConnectionContext类来存储ThreadLocal,ConnectionContext类的代码如下:
package wkcto.com.web.filter;
import java.sql.Connection;
/**
* @ClassName: ConnectionContext
* @Description:数据库连接上下文
*
*/
public class ConnectionContext {
/**
* 构造方法私有化,将ConnectionContext设计成单例
*/
private ConnectionContext(){
}
//创建ConnectionContext实例对象
private static ConnectionContext connectionContext = new ConnectionContext();
/**
* @Method: getInstance
* @Description:获取ConnectionContext实例对象
*
* @return
*/
public static ConnectionContext getInstance(){
return connectionContext;
}
/**
* @Field: connectionThreadLocal
* 使用ThreadLocal存储数据库连接对象
*/
private ThreadLocal connectionThreadLocal = new ThreadLocal();
/**
* @Method: bind
* @Description:利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
*
* @param connection
*/
public void bind(Connection connection){
connectionThreadLocal.set(connection);
}
/**
* @Method: getConnection
* @Description:从当前线程中取出Connection对象
*
* @return
*/
public Connection getConnection(){
return connectionThreadLocal.get();
}
/**
* @Method: remove
* @Description: 解除当前线程上绑定Connection
*
*/
public void remove(){
connectionThreadLocal.remove();
}
}
在DAO层想获取数据库连接时,就可以使用ConnectionContext.getInstance().getConnection()来获取,如下所示:
package wkcto.com.dao;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import wkcto.com.web.filter.ConnectionContext;
import wkcto.com.domain.Account;
/*
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('A',1000);
insert into account(name,money) values('B',1000);
insert into account(name,money) values('C',1000);
*/
/**
* @ClassName: AccountDao
* @Description: 针对Account对象的CRUD
*
*/
public class AccountDao3 {
public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
qr.update(ConnectionContext.getInstance().getConnection(),sql, params);
}
public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
return (Account) qr.query(ConnectionContext.getInstance().getConnection(),sql, id, new BeanHandler(Account.class));
}
}
businessService层也不用处理事务和数据库连接问题了,这些统一在TransactionFilter中统一管理了,businessService层只需要专注业务逻辑的处理即可,如下所示:
package wkcto.com.service;
import java.sql.SQLException;
import wkcto.com.dao.AccountDao3;
import wkcto.com.domain.Account;
public class AccountService3 {
/**
* @Method: transfer
* @Description:在业务层处理两个账户之间的转账问题
* @param sourceid
* @param tartgetid
* @param money
* @throws SQLException
*/
public void transfer(int sourceid, int tartgetid, float money)
throws SQLException {
AccountDao3 dao = new AccountDao3();
Account source = dao.find(sourceid);
Account target = dao.find(tartgetid);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
dao.update(source);
// 模拟程序出现异常让事务回滚
int x = 1 / 0;
dao.update(target);
}
}
Web层的Servlet调用businessService层的业务方法处理用户请求,需要注意的是:调用businessService层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作,如下所示:
package wkcto.com.web.controller;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import wkcto.com.service.AccountService3;
public class AccountServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
AccountService3 service = new AccountService3();
try {
service.transfer(1, 2, 100);
} catch (SQLException e) {
e.printStackTrace();
//注意:调用service层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作
throw new RuntimeException(e);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
不写代码你养我啊08-23 11:30
HelloWorld09-29 17:28
兔子06-15 17:15