Java web项目中使用事务小记:ThreadLocal+Filter

什么是事务

一个或一组SQL语句组成一个执行单元,这个执行单元要么全部执行,要么全部不执行
案例:转账
存储引擎:MySQL中的数据用各种不同的技术存储在文件中
SHOW ENGINES; 
innodb支持事务,myisam,memory不支持事务
事务的ACID属性
atomicity 原子性 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
consistency一致性 事务必须使数据库从一个一致性状态变换到另一个一致性状态
isolation隔离性 一个事务的执行不受其他事务的干扰,并发执行的各个事务之间不能互相干扰
durability持久性 一个事务一旦被提交,它对数据库中数据的改变是永久性的

事务处理框架

在Service的一个方法中可能会操作多个dao方法,为了确保他们全都执行或者全部不执行,就需要使用事务

Connection conn = JDBCUtils.getConnection();
try{
    
    
	conn.setAutoCommit(false); 	===>获取同一连接
	
	执行jdbc操作.........		===>在service中调用不同的dao的方法
	//提交事务
	conn.commit();				===>过滤器 调用 提交和关闭连接方法
}catch(Exception e){
    
    			
	//回滚事务
	conn.rollBack();			===>过滤器 调用 回滚和关闭连接方法
}finally{
    
    
	JDBCUtils.close(conn);		
}

ThreadLocal获取同一连接

使用事务需要确保各dao方法都使用同一个连接。如何确保是同一个连接呢?可以使用ThreadLocal对象,一个ThreadLocal对象能且仅能关联一个连接对象。所以,首先确保程序使用的是一个线程,那么就只有一个ThreadLocal对象存在,也就只有一个连接了。在JDBCUtils类中定义一个ThreadLocal对象:

private static ThreadLocal<Connection> conns = new ThreadLocal<>();

1.JDBCUtils的获取连接方法:如果第一次获取连接,从数据库连接池中获取,将其保存到ThreadLocal对象中。之后就直接从ThreadLocal对象获取,这样使用都是同一个连接。另外,设置手动提交事务

/**
 * 建立连接
 * @return
 * @throws Exception
 */
public static Connection getConnection() throws Exception {
    
    
    Connection connection = conns.get();// 确保同一个连接
    if (connection==null){
    
    //如果ThreadLocal里面没有连接,就从数据库连接池获取连接
//            System.out.println("获取连接");
        connection = dataSource.getConnection();
        // 保存到conns 也就是ThreadLocal对象
        conns.set(connection);
        // 手动管理
        connection.setAutoCommit(false);
    }
    return connection;
}

关闭连接:因为事务要么是提交然后关闭连接,要么回滚再提交连接,所以写两个方法:提交事务并关闭连接,和回滚事务并关闭连接。注意在关闭连接之后,因为连接仍然存在于ThreadLocal对象中,需要将其remove();

/**
 * 提交事务,关闭连接
 */
public static void commitAndClose() throws SQLException {
    
    
    Connection connection = conns.get();
    if(connection!=null){
    
    // 如果这个连接使用过
        connection.commit();    //提交事务
        connection.close();     //关闭连接
    }
    // 一定要执行remove 否则会出错 因为Tomcat底层使用了线程池
    conns.remove();
}

/**
 * 回滚事务,关闭连接
 */
public static void rollbackAndClose() {
    
    
    Connection connection = conns.get();
    if (connection != null) {
    
    // 如果这个连接使用过
        try {
    
    
            connection.rollback();    //回滚事务
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
        try {
    
    
            connection.close();     //关闭连接
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }
    // 一定要执行remove 否则会出错 因为Tomcat底层使用了线程池
    conns.remove();
}

2.修改BaseDao(所有Dao的父类用于增删改查):为了确保事务中的dao方法操作遇到异常回滚,将所有异常抛出throw new RuntimeException(e); 而且不要在里面关闭连接

public <T> List<T> queryForMulti(Class<T> type, String sql, Object ... args){
    
    
    Connection connection = null;
    try {
    
    
        connection = JdbcUtils.getConnection();
        return queryRunner.query(connection, sql, new BeanListHandler<T>(type),args);
    } catch (Exception e) {
    
    
        e.printStackTrace();
        throw new RuntimeException(e);
//        } finally {
    
    
//            JdbcUtils.close(connection);
    }
}

3.一种方法是在web层或者调用这个包含事务的Service方法的语句外添加try catch块,并添加事务的相关语句:在调用service方法之后,调用JDBCUtils中提交事务并关闭连接方法;在catch块中调用JDBCUtils中回滚事务并关闭连接方法。

// 使用事务
String orderId = null;
try {
    
    
    orderId = orderService.createOrder(cart, user.getId());//多dao方法调用
    JdbcUtils.commitAndClose();//提交事务并关闭
} catch (Exception e) {
    
    
    JdbcUtils.rollbackAndClose();//回滚事务并关闭
    e.printStackTrace();
}

4.调试:在事务范围内(即service方法中)调用不同的dao方法之间添加一个被零除,清空dao相关的数据库表,打断点进行debug,可以发现在被零除的情况下,执行到的这部分dao方法没有在数据库中记录,这样就实现了事务操作。

Filter过滤器 提交/回滚事务并关闭连接

5.使用3的方法只能对当前service方法有效,那么所有业务都使用事务管理呢?可以使用过滤器拦截所有程序,也就拦截了所有service方法。注意将所有异常抛出,这样就可以一次性将所有service方法设置为事务了。
在这里插入图片描述
6. 如果希望出现异常时跳转到errorPage页面,要在过滤器继续抛异常给tomcat。最终的过滤器代码如下:

public class TransactionFilter implements Filter {
    
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    
    

    }

    // 为所有业务添加事务
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    
        try {
    
    
            //拦截所有Servlet,从而实现拦截所有service方法,实现事务管理
            filterChain.doFilter(servletRequest, servletResponse);
            JdbcUtils.commitAndClose();//提交事务
        }catch (Exception e){
    
    
            JdbcUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
            throw new RuntimeException(e);//把异常抛给Tomcat服务器展示页面
        }
    }

    @Override
    public void destroy() {
    
    

    }
}

Filter的web.xml配置:

    <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>indi.huishi.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <url-pattern>/*</url-pattern><!--拦截所有请求-->
    </filter-mapping>

猜你喜欢

转载自blog.csdn.net/qq_36937684/article/details/115254884
今日推荐