4.4、ThreadLocal + Filter 处理事务
以上介绍了JDBC开发中事务处理的三种方式,以下是使用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、Conection获取数据库连接对象
connection = JdbcUtils.getConnection();
//2、开启事务
connection.setAutoCommit(false);
//3、利用ThreadLocal把Conection获取数据库连接对象和当前线程绑定
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中取出数据库连接来操作数据库,因此需要编写一个Conectioncontext类来存储Threadlocal,ConnectionContext代码如下:
package wkcto.com.web.filter;
import java.sql.Connection;
/**
* @ClassName: ConnectionContext
* @Description:连接上下文的数据库
*
*/
public class ConnectionContext {
/**
* 私有化的结构方法,将ConectionContext设计成单例
*/
private ConnectionContext(){
}
//创建Conectioncontext实例对象
private static ConnectionContext connectionContext = new ConnectionContext();
/**
* @Method: getInstance
* @Description:conectioncontext实例对象获得Conection
*
* @return
*/
public static ConnectionContext getInstance(){
return connectionContext;
}
/**
* @Field: connectionThreadLocal
* 使用Threadlocal存储数据库连接对象
*/
private ThreadLocal connectionThreadLocal = new ThreadLocal();
/**
* @Method: bind
* @Description:使用Threadlocal将数据库连接对象Conection与当前线程绑定
*
* @param connection
*/
public void bind(Connection connection){
connectionThreadLocal.set(connection);
}
/**
* @Method: getConnection
* @Description:从当前线程中取出Conection对象
*
* @return
*/
public Connection getConnection(){
return connectionThreadLocal.get();
}
/**
* @Method: remove
* @Description: Conection在当前线程上的绑定
*
*/
public void remove(){
connectionThreadLocal.remove();
}
}
当DAO层想要获得数据库连接时,可以使用ConectionContexttextet.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: CRUD针对Account对象
*
*/
public class AccountDao3 {
public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//ConnectionContext.getInstance().getConnection()在当前线程中获取Conection对象
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()在当前线程中获取Conection对象
return (Account) qr.query(ConnectionContext.getInstance().getConnection(),sql, id, new BeanHandler(Account.class));
}
}
businesservice层不需要处理事务和数据库连接问题,这些问题在transactionfilter中统一管理,businesservice层只需要专注于业务逻辑,如下所示:
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层的Servicet调用businessservice层的业务方法来处理用户请求。需要注意的是,在调用businesservice层的方法出现异常后,继续抛出异常,以便在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);
}
}