day12-功能实现11

锦通  金牌会员 | 2022-12-31 21:36:13 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 669|帖子 669|积分 2007

家居网购项目实现011

以下皆为部分代码,详见 https://github.com/liyuelian/furniture_mall.git
27.功能25-事务管理

27.1下订单问题思考

在生成订单的功能中,系统会去同时修改数据库中的order,order_item,furn三张表,如果有任意一个表修改失败,就会出现数据不一致问题。因此出现了事务控制问题。
27.2思路分析

之前,我们每次调用底层的dao操作,每次进行的都是独立事务,因此一但在一次业务中调用了多个dao操作,就不能保证多表的事务一致性。
因为JDBC局部事务是控制是由java.sql.Connection来完成的,要保证多个DAO的数据访问处于一个事务中,我们需要保证他们使用的是同一个java.sql.Connection.
要保证数据一致性,就要使用事务。使用事务的前提是保证同一个连接connection。我们的想法是,在进行dao操作的前面就开启事务,然后在进行各种dao操作后,如果没有出现异常,则手动进行事务提交,否则进行回滚。
现在的问题是:
q1. 我们之前使用数据库连接池,无法保证每次进行dao操作都是同一个connection连接对象
q2. 设置开启手动提交事务以及事务回滚的时机
解决方法:

  • 使用Filter+ThreadLocal进行事务管理
  • 在一次http请求,servlet-service-dao的调用过程,始终是一个线程,这是使用ThreadLocal的前提
  • 使用ThreadLocal来确保所有dao操作都在同一个Connection连接对象中完成
  • 根据过滤器的机制,在所有代码都走完之后会回来走过滤器的chain.dofilter()的后置代码,这个特性非常适合进行事务管理
27.3代码实现

27.3.1uilts包

重写JDBCUtilsByDruid,修改getConnection方法,同时设置手动提交事务
  1. package com.li.furns.utils;
  2. import com.alibaba.druid.pool.DruidDataSourceFactory;
  3. import javax.sql.DataSource;
  4. import java.io.FileInputStream;
  5. import java.sql.Connection;
  6. import java.sql.ResultSet;
  7. import java.sql.SQLException;
  8. import java.sql.Statement;
  9. import java.util.Properties;
  10. /**
  11. * 基于Druid数据库连接池的工具类
  12. */
  13. public class JDBCUtilsByDruid {
  14.     private static DataSource ds;
  15.     //定义属性ThreadLocal,这里存放一个Connection
  16.     private static ThreadLocal<Connection> threadLocalConn = new ThreadLocal<>();
  17.     //在静态代码块完成ds的初始化
  18.     //静态代码块在加载类的时候只会执行一次,因此数据源也只会初始化一次
  19.     static {
  20.         Properties properties = new Properties();
  21.         try {
  22.             //因为我们是web项目,它的工作目录不在src下面,文件的加载需要使用类加载器
  23.             properties.load(JDBCUtilsByDruid.class.getClassLoader()
  24.                     .getResourceAsStream("druid.properties"));
  25.             //properties.load(new FileInputStream("src\\druid.properties"));
  26.             ds = DruidDataSourceFactory.createDataSource(properties);
  27.         } catch (Exception e) {
  28.             e.printStackTrace();
  29.         }
  30.     }
  31. //    //编写getConnection方法
  32. //    public static Connection getConnection() throws SQLException {
  33. //        return ds.getConnection();
  34. //    }
  35.     /**
  36.      * 获取连接方法
  37.      * 从ThreadLocal中获取connection,
  38.      * 从而保证在同一个线程中获取的是同一个Connection
  39.      *
  40.      * @return
  41.      * @throws SQLException
  42.      */
  43.     public static Connection getConnection() {
  44.         Connection connection = threadLocalConn.get();
  45.         if (connection == null) {//说明当前的threadLocalConn没有连接
  46.             //就从数据库连接池中获取一个连接,放到ThreadLocal中
  47.             try {
  48.                 connection = ds.getConnection();
  49.                 //设置为手动提交,即不要自动提交
  50.                 connection.setAutoCommit(false);
  51.             } catch (SQLException e) {
  52.                 e.printStackTrace();
  53.             }
  54.             threadLocalConn.set(connection);
  55.         }
  56.         return connection;
  57.     }
  58.     /**
  59.      * 提交事务
  60.      */
  61.     public static void commit() {
  62.         Connection connection = threadLocalConn.get();
  63.         if (connection != null) {//确保该连接是有效的
  64.             try {
  65.                 connection.commit();
  66.             } catch (SQLException e) {
  67.                 e.printStackTrace();
  68.             } finally {
  69.                 try {
  70.                     connection.close();//将连接释放回连接池
  71.                 } catch (SQLException e) {
  72.                     e.printStackTrace();
  73.                 }
  74.             }
  75.             //1.当提交后,需要把connection从threadLocalConn中清除掉
  76.             //2.否则会造成ThreadLocalConn长时间持有该连接,会影响效率
  77.             //3.也因为我们Tomcat底层使用的是线程池技术
  78.             threadLocalConn.remove();
  79.         }
  80.     }
  81.     /**
  82.      * 回滚,回滚的是和connection相关的dml操作
  83.      */
  84.     public static void rollback() {
  85.         Connection connection = threadLocalConn.get();
  86.         if (connection != null) {//保证当前的连接是有效的
  87.             try {
  88.                 connection.rollback();
  89.             } catch (SQLException e) {
  90.                 e.printStackTrace();
  91.             } finally {
  92.                 try {
  93.                     connection.close();
  94.                 } catch (SQLException e) {
  95.                     e.printStackTrace();
  96.                 }
  97.             }
  98.         }
  99.         threadLocalConn.remove();
  100.     }
  101.     //关闭连接(注意:在数据库连接池技术中,close不是真的关闭连接,而是将Connection对象放回连接池中)
  102.     public static void close(ResultSet resultSet, Statement statemenat, Connection connection) {
  103.         try {
  104.             if (resultSet != null) {
  105.                 resultSet.close();
  106.             }
  107.             if (statemenat != null) {
  108.                 statemenat.close();
  109.             }
  110.             if (connection != null) {
  111.                 connection.close();
  112.             }
  113.         } catch (SQLException e) {
  114.             throw new RuntimeException(e);
  115.         }
  116.     }
  117. }
复制代码
因为现在连接的关闭是在commit或者rollback中发生的,因此BasicDAO中写的关闭连接已经没有意义了,将其删掉即可。
27.3.2filter

配置TransactionFilter
  1. <filter>
  2.     <filter-name>TransactionFilter</filter-name>
  3.     <filter-class>com.li.furns.filter.TransactionFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6.     <filter-name>TransactionFilter</filter-name>
  7.    
  8.     <url-pattern>/*</url-pattern>
  9. </filter-mapping>
复制代码
TransactionFilter:
  1. package com.li.furns.filter;
  2. import com.li.furns.utils.JDBCUtilsByDruid;
  3. import javax.servlet.*;
  4. import java.io.IOException;
  5. /**
  6. * 管理事务
  7. *
  8. * @author 李
  9. * @version 1.0
  10. */
  11. public class TransactionFilter implements Filter {
  12.     public void init(FilterConfig config) throws ServletException {
  13.     }
  14.     public void destroy() {
  15.     }
  16.     @Override
  17.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
  18.         try {
  19.             //先放行
  20.             chain.doFilter(request, response);
  21.             //统一提交
  22.             JDBCUtilsByDruid.commit();
  23.         } catch (Exception e) {
  24.             //只有在try{}中出现了异常,才会进行catch{}
  25.             //这里想要捕获异常,前提是底层的代码没有将抛出的异常捕获
  26.             JDBCUtilsByDruid.rollback();//回滚
  27.             e.printStackTrace();
  28.         }
  29.     }
  30. }
复制代码
由于之前在BasicServlet中捕获了异常,因此需要修改BasicServlet,将捕获的异常抛出给Filter,否则无法在出现异常时进行回滚。
27.4完成测试

为了测试,在FurnDAOImpl操作中写入错误的sql语句,模拟表操作失败
现在来测试一下,当发生dao操作失败后会产生什么现象。
登录用户,点击添加某个家居,点击购物车生成订单,因为生成订单涉及到furn表的操作,因此可以看到点击后页面没有跳转到正常的显示订单页面
查看后台输出,发现抛出异常
查看数据库:
相关的表没有进行改动,说明事务管理起作用了。
order_item表:
order表:
furn表:(操作前后的sales和stock字段一致)
28.功能26-统一错误提示页面

28.1需求分析/图解


  • 如果在访问/操作网站时,出现了内部错误,统一显示 500.jsp
  • 如果访问/操作不存在的页面/servlet时,统一显示 404.jsp
28.2思路分析


  • 在发生错误/异常时,将错误/异常 抛给tomcat
  • 在web.xml配置不同的错误显示不同的页面即可
28.3代码实现

404.jsp用于显示404错误;500.jsp用于显示服务器内部错误。

  • 页面代码:略。
  • 在web.xml文件中配置错误提示页:
  1. <error-page>
  2.     <error-code>404</error-code>
  3.     <location>/views/error/404.jsp</location>
  4. </error-page>
  5. <error-page>
  6.     <error-code>500</error-code>
  7.     <location>/views/error/500.jsp</location>
  8. </error-page>
复制代码
如果在代码中捕获了异常,那么将不会起到效果,应该要将异常抛出给tomcat,让tomcat可以根据不同的异常进行页面展示。
TransactionFilter:
28.4完成测试

在浏览器中输入一个项目不存在的资源http://localhost:8080/furniture_mall/abc.jsp,访问结果:
内部发生错误:

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

锦通

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表