当前位置: 首页 > 图灵资讯 > 技术篇> Apache_DBUtils框架教程学习笔记(五)_Filter 处理事务

Apache_DBUtils框架教程学习笔记(五)_Filter 处理事务

来源:图灵教育
时间:2023-04-16 09:26:51

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