需求(现状):
1.在进行生成订单时,同时还需要清空购物车、更新商品的库存和销量。是对多个表进行操作,需要进行事务管理,保证数据的一致性。
2.在此过程中, 每次对数据库的操作使用的connection都是从 工具类(基于druid数据库连接池技术)拿的,connection不一定是同一个。
思路分析:
实现功能程序框架图
1. 使用 Filter + ThreadLocal 进行事务管理
2. 说明: 在一次 http 请求,servlet-service-dao 的调用过程,始终是一个线程, 这是使用 ThreadLocal 的前提
实现要点:
1.保证在一次请求中,拿到的connection是同一个,并提前关闭自动提交。
因为原先在对多个表操作时,每次操作使用的connection都是从工具类(基于druid数据库连接池技术)拿的,每次拿到的connection不一定相同,无法进行事务管理。
所以,可以使用ThreadLocal,存放一个connection对象,保证在一次http请求中使用的connection是同一个。并在将connection放入ThreadLocal前将其自动提交关闭 conn.setAutoCommit(false);
关键代码:
private static DataSource ds;
public static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//在静态代码块完成 ds初始化
static {
Properties properties = new Properties();
try {
//因为这里是web项目,它的工作目录是out,文件加载,需要使用类加载器
//properties.load(new FileInputStream("src\\druid.properties"));
properties.load(JDBCUtilsByDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//编写getConnection方法
public static Connection getConnection() throws SQLException {
Connection conn = threadLocal.get();
if (conn == null) {
conn = ds.getConnection();
//设置手动提交, 事务管理需要
conn.setAutoCommit(false);
threadLocal.set(conn);
}
return conn;
}
2.再编写提交事务和回滚事务的方法
//提交事务
public static void commit() {
Connection connection = threadLocal.get();
if (connection != null) {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
//关闭连接
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//说明
//1. 当提交后,需要把connection从 threadLocalConn 清除掉
//2. 不然,会造成 threadLocalConn 长时间持有该连接, 会影响效率
//3. 也因为我们Tomcat底层使用的是线程池技术
threadLocal.remove();
}
//回滚事务
public static void rollback() {
Connection connection = threadLocal.get();
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
threadLocal.remove();
}
2.使用Filter对事务进行控制提交
TransactionFilter 代码如下:
package com.jiamian.furns.filter;
import com.jiamian.furns.utils.JDBCUtilsByDruid;
import javax.servlet.*;
import java.io.IOException;
public class TransactionFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
//放行.
filterChain.doFilter(servletRequest,servletResponse);
JDBCUtilsByDruid.commit();//统一提交, 出现了异常
} catch (Exception e) {
//解读: 只有在try{} 中出现了异常,才会进行catch{} 执行
//, 才会进行回滚.
JDBCUtilsByDruid.rollback();//回滚
e.printStackTrace();
//抛出异常, 给tomcat, tomcat会根据errorpage 来显示对应页面
//这里 体会: 异常机制是可以参与业务逻辑
throw new RuntimeException(e);
}
}
public void init(FilterConfig config) throws ServletException {
}
}
TransactionFilter 的配置如下:
<!--配置过滤器,该过滤器用于事务管理-->
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.jiamian.furns.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意事项(异常的处理):
要将事务异常抛给 TransactionFilter(一直抛,直到抛给TransactionFilter ),TransactionFilter捕获到异常才会回滚事务。若不抛给TransactionFilter,就无法进行事务管理。
最后,放上 基于druid数据库连接池的工具类第二版(事务管理版)
package com.jiamian.furns.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 基于druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
public static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//在静态代码块完成 ds初始化
static {
Properties properties = new Properties();
try {
//因为这里是web项目,它的工作目录是out,文件加载,需要使用类加载器
//properties.load(new FileInputStream("src\\druid.properties"));
properties.load(JDBCUtilsByDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//编写getConnection方法
public static Connection getConnection() throws SQLException {
Connection conn = threadLocal.get();
if (conn == null) {
conn = ds.getConnection();
//设置手动提交, 事务管理需要
conn.setAutoCommit(false);
threadLocal.set(conn);
}
return conn;
}
//提交事务
public static void commit() {
Connection connection = threadLocal.get();
if (connection != null) {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
//关闭连接
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//说明
//1. 当提交后,需要把connection从 threadLocalConn 清除掉
//2. 不然,会造成 threadLocalConn 长时间持有该连接, 会影响效率
//3. 也因为我们Tomcat底层使用的是线程池技术
threadLocal.remove();
}
//回滚事务
public static void rollback() {
Connection connection = threadLocal.get();
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
threadLocal.remove();
}
//关闭连接, 老师再次强调: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的Connection对象放回连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}