家居网购项目实现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. 设置开启手动提交事务以及事务回滚的时机

解决方法:

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

27.3代码实现

27.3.1uilts包

重写JDBCUtilsByDruid,修改getConnection方法,同时设置手动提交事务

package com.li.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; /**
* 基于Druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid { private static DataSource ds;
//定义属性ThreadLocal,这里存放一个Connection
private static ThreadLocal<Connection> threadLocalConn = new ThreadLocal<>(); //在静态代码块完成ds的初始化
//静态代码块在加载类的时候只会执行一次,因此数据源也只会初始化一次
static {
Properties properties = new Properties();
try {
//因为我们是web项目,它的工作目录不在src下面,文件的加载需要使用类加载器
properties.load(JDBCUtilsByDruid.class.getClassLoader()
.getResourceAsStream("druid.properties"));
//properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
} // //编写getConnection方法
// public static Connection getConnection() throws SQLException {
// return ds.getConnection();
// } /**
* 获取连接方法
* 从ThreadLocal中获取connection,
* 从而保证在同一个线程中获取的是同一个Connection
*
* @return
* @throws SQLException
*/
public static Connection getConnection() {
Connection connection = threadLocalConn.get();
if (connection == null) {//说明当前的threadLocalConn没有连接
//就从数据库连接池中获取一个连接,放到ThreadLocal中
try {
connection = ds.getConnection();
//设置为手动提交,即不要自动提交
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
threadLocalConn.set(connection);
}
return connection;
} /**
* 提交事务
*/
public static void commit() {
Connection connection = threadLocalConn.get();
if (connection != null) {//确保该连接是有效的
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();//将连接释放回连接池
} catch (SQLException e) {
e.printStackTrace();
}
}
//1.当提交后,需要把connection从threadLocalConn中清除掉
//2.否则会造成ThreadLocalConn长时间持有该连接,会影响效率
//3.也因为我们Tomcat底层使用的是线程池技术
threadLocalConn.remove();
}
} /**
* 回滚,回滚的是和connection相关的dml操作
*/
public static void rollback() {
Connection connection = threadLocalConn.get();
if (connection != null) {//保证当前的连接是有效的
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
threadLocalConn.remove();
} //关闭连接(注意:在数据库连接池技术中,close不是真的关闭连接,而是将Connection对象放回连接池中)
public static void close(ResultSet resultSet, Statement statemenat, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statemenat != null) {
statemenat.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

因为现在连接的关闭是在commit或者rollback中发生的,因此BasicDAO中写的关闭连接已经没有意义了,将其删掉即可。

27.3.2filter

配置TransactionFilter

<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.li.furns.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!--这里我们对所有请求都进行事务管理-->
<url-pattern>/*</url-pattern>
</filter-mapping>

TransactionFilter:

package com.li.furns.filter;

import com.li.furns.utils.JDBCUtilsByDruid;

import javax.servlet.*;
import java.io.IOException; /**
* 管理事务
*
* @author 李
* @version 1.0
*/
public class TransactionFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
} public void destroy() {
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
//先放行
chain.doFilter(request, response);
//统一提交
JDBCUtilsByDruid.commit(); } catch (Exception e) {
//只有在try{}中出现了异常,才会进行catch{}
//这里想要捕获异常,前提是底层的代码没有将抛出的异常捕获
JDBCUtilsByDruid.rollback();//回滚
e.printStackTrace();
}
}
}

由于之前在BasicServlet中捕获了异常,因此需要修改BasicServlet,将捕获的异常抛出给Filter,否则无法在出现异常时进行回滚。

27.4完成测试

为了测试,在FurnDAOImpl操作中写入错误的sql语句,模拟表操作失败

现在来测试一下,当发生dao操作失败后会产生什么现象。

登录用户,点击添加某个家居,点击购物车生成订单,因为生成订单涉及到furn表的操作,因此可以看到点击后页面没有跳转到正常的显示订单页面

查看后台输出,发现抛出异常

查看数据库:

相关的表没有进行改动,说明事务管理起作用了。

order_item表:

order表:

furn表:(操作前后的sales和stock字段一致)

28.功能26-统一错误提示页面

28.1需求分析/图解

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

28.2思路分析

  1. 在发生错误/异常时,将错误/异常 抛给tomcat
  2. 在web.xml配置不同的错误显示不同的页面即可

28.3代码实现

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

  1. 页面代码:略。

  2. 在web.xml文件中配置错误提示页:

<!--404错误提示页面-->
<error-page>
<error-code>404</error-code>
<location>/views/error/404.jsp</location>
</error-page>
<!--500错误提示页面-->
<error-page>
<error-code>500</error-code>
<location>/views/error/500.jsp</location>
</error-page>

如果在代码中捕获了异常,那么将不会起到效果,应该要将异常抛出给tomcat,让tomcat可以根据不同的异常进行页面展示。

TransactionFilter:

28.4完成测试

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

内部发生错误:

day12-功能实现11的更多相关文章

  1. 数据库——mysql内置功能(11)

    1.视图 视图是一个虚拟表(非真实存在),其本质是(根据SQL语句获取动态的数据集,并未其命名),用户使用时只需使用(名称)即可获取结果集,可以将该结果集当做表来使用 使用视图我们可以把查询过程中的临 ...

  2. Java 11 新功能来了!

    关键时刻,第一时间送达! 目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出 ...

  3. Java 11新功能抢先了解

    目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出台的每6个月发布一次Jav ...

  4. Rspec: everyday-rspec实操: 第10章测试其他功能,第11章TDD 第12章总结。

    10.测试文件上传 作者推荐的Paperclip,官方维护组已经不推荐使用deprecated. 推荐使用rails自带的 ActiveStorage. Active Storage: 推进文件上传到 ...

  5. Eclipse功能集合

    大家好,这篇博客的目的是总结一下Eclipse这个软件中一些不为常用的功能.与大家分享.谢谢~ 1.利用one hour看了一下Eclipse的使用,用two hour写了这篇blog. 2.在现实项 ...

  6. (视频) 《快速创建网站》 2.3 WordPress初始化和功能简介

    本文是<快速创建网站>系列的第4篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http://devopshub.cn/tag ...

  7. 《图解HTTP》 第11章 web的攻击技术

    11.1 针对Web的攻击技术 简单的HTTP协议本身并不存在安全性问题,所以协议本身几乎不会成为攻击的对象. 11.1.1 HTTP不具备必要的安全功能 11.1.2 在客户端即可篡改请求 在HTT ...

  8. 13.Django1.11.6文档

    第一步 入门 检查版本 python -m django --version 创建第一个项目 django-admin startproject mysite 运行 python manage.py ...

  9. 深入理解C++11【5】

    [深入理解C++11[5]] 1.原子操作与C++11原子类型 C++98 中的原子操作.mutex.pthread: #include<pthread.h> #include <i ...

  10. 【整理】Java 11新特性总结

    闲语 2018年9月25日,Java 11正式发布,与JDK 10不同,JDK 11将提供长期支持,还将作为Java平台的参考实现以及标准版(Java SE)11.Oracle直到2023年9月都会为 ...

随机推荐

  1. java中的自动拆装箱与缓存(Java核心技术阅读笔记)

    最近在读<深入理解java核心技术>,对于里面比较重要的知识点做一个记录! 众所周知,Java是一个面向对象的语言,而java中的基本数据类型却不是面向对象的!为了解决这个问题,Java为 ...

  2. vue项目Eslint和prettier结合使用

    一.eslint介绍--代码语法检查工具 Eslint是一个代码检查工具,用来检查你的代码语法是否符合指定的规范,ECMAScript标准 二.prettier插件--代码格式化工具 prettier ...

  3. 测试Thread中的常用方法:

    测试Thread中的常用方法:start():启动当前线程:调用当前线程的run()run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中currentThread ...

  4. uoj220【NOI2016】网格

    刚了几个小时啊,这tm要是noi我怕不是直接滚粗了.我判答案为1的情况试了几种做法,最后终于想到了一个靠谱的做法,然后细节巨多,调了好久,刚拿到97分时代码有6.2KB了,后来发现有些东西好像没啥用就 ...

  5. 支持向量机(SVM)公式整理

    支持向量机可以分为三类: 线性可分的情况 ==> 硬间隔最大化 ==> 硬间隔SVM 近似线性可分的情况 ==> 软间隔最大化 ==> 线性支持向量机 线性不可分的情况 ==& ...

  6. VS Code插件推荐

    VS Code插件推荐 ​ VS Code作为前端开发人员在学习工作中必不可少的开发软件,其强大的功能以及丰富多样的插件都让开发人员爱不释手.下面推荐个人觉得还不错的几个插件,希望可以帮助到你.如果你 ...

  7. 如何使用webgl(three.js)实现3D储能,3D储能站,3D智慧储能、储能柜的三维可视化解决方案——第十七课

    前言 上节课我们讲了<3D光伏发电>,与之配套的就是能量存储 这节课我们主要讲讲储能,储能站,在分布式能源系统中起到调节用对电的尖峰平谷进行削峰填谷的作用.特别是小型储能站,更加灵活,因地 ...

  8. ui自动化测试数据复原遇到的坑——2、python连接informix时pytest报致命错误Windows fatal exception: access violation

    python连接informix只能通过jdbc(需要先部署java环境.我试过到IBM上下载ODBC但结局是失败的),在执行pytest时发现有一串报错(大致是下面的这样): Windows fat ...

  9. 2022春每日一题:Day 40

    题目:[NOI2010] 超级钢琴 前求出美妙值的前缀和,然后倍增处理一下前缀和的最大值,然后对于一个左端点s,他能取到右端点的只有s+l到s+r,而他的最大贡献就是s+l 到s+r的最大子段和,因此 ...

  10. Go语言核心36讲20

    在上两篇文章中,我主要为你讲解了与go语句.goroutine和Go语言调度器有关的知识和技法. 内容很多,你不用急于完全消化,可以在编程实践过程中逐步理解和感悟,争取夯实它们. 现在,让我们暂时走下 ...