day12-功能实现11
家居网购项目实现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方法,同时设置手动提交事务
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需求分析/图解
- 如果在访问/操作网站时,出现了内部错误,统一显示 500.jsp
- 如果访问/操作不存在的页面/servlet时,统一显示 404.jsp
28.2思路分析
- 在发生错误/异常时,将错误/异常 抛给tomcat
- 在web.xml配置不同的错误显示不同的页面即可
28.3代码实现
404.jsp用于显示404错误;500.jsp用于显示服务器内部错误。
页面代码:略。
在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的更多相关文章
- 数据库——mysql内置功能(11)
1.视图 视图是一个虚拟表(非真实存在),其本质是(根据SQL语句获取动态的数据集,并未其命名),用户使用时只需使用(名称)即可获取结果集,可以将该结果集当做表来使用 使用视图我们可以把查询过程中的临 ...
- Java 11 新功能来了!
关键时刻,第一时间送达! 目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出 ...
- Java 11新功能抢先了解
目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出台的每6个月发布一次Jav ...
- Rspec: everyday-rspec实操: 第10章测试其他功能,第11章TDD 第12章总结。
10.测试文件上传 作者推荐的Paperclip,官方维护组已经不推荐使用deprecated. 推荐使用rails自带的 ActiveStorage. Active Storage: 推进文件上传到 ...
- Eclipse功能集合
大家好,这篇博客的目的是总结一下Eclipse这个软件中一些不为常用的功能.与大家分享.谢谢~ 1.利用one hour看了一下Eclipse的使用,用two hour写了这篇blog. 2.在现实项 ...
- (视频) 《快速创建网站》 2.3 WordPress初始化和功能简介
本文是<快速创建网站>系列的第4篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http://devopshub.cn/tag ...
- 《图解HTTP》 第11章 web的攻击技术
11.1 针对Web的攻击技术 简单的HTTP协议本身并不存在安全性问题,所以协议本身几乎不会成为攻击的对象. 11.1.1 HTTP不具备必要的安全功能 11.1.2 在客户端即可篡改请求 在HTT ...
- 13.Django1.11.6文档
第一步 入门 检查版本 python -m django --version 创建第一个项目 django-admin startproject mysite 运行 python manage.py ...
- 深入理解C++11【5】
[深入理解C++11[5]] 1.原子操作与C++11原子类型 C++98 中的原子操作.mutex.pthread: #include<pthread.h> #include <i ...
- 【整理】Java 11新特性总结
闲语 2018年9月25日,Java 11正式发布,与JDK 10不同,JDK 11将提供长期支持,还将作为Java平台的参考实现以及标准版(Java SE)11.Oracle直到2023年9月都会为 ...
随机推荐
- POJ3260 The Fewest Coins(混合背包)
支付对应的是多重背包问题,找零对应完全背包问题. 难点在于找上限T+maxv*maxv,可以用鸽笼原理证明,实在想不到就开一个尽量大的数组. 1 #include <map> 2 #inc ...
- 代码随想录第十三天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素
第一题150. 逆波兰表达式求值 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除法只保留整数部分. ...
- Explain:你见过这样的Sql吗?
上一篇我们讲到Mysql索引底层逻辑,为了了解后续sql知识,我们还是需要先学习一下相关"工具"得使用 一.Explain介绍 EXPLAIN是MySQl必不可少的一个分析工具,主 ...
- Java中的名称命名规范
包名:多单词组成时所有字母都小写:xxxyyyzzz 类名.接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz 变量名.方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首 ...
- git clone开启云上AI开发
摘要:相比于传统的软件开发,AI开发存在以下4个痛点:算法繁多:训练时间长:算力需求大:模型需手动管理,我们可以使用云上AI开发的方式来缓解以上4个痛点. 本文分享自华为云社区<git clon ...
- 后端框架学习-----mybatis(4)
文章目录 4.解决属性名和字段名不一致的问题 4.解决属性名和字段名不一致的问题 1.问题.数据库字段名和属性名不一致,导致查出的数据部分为空 2.resultMap(用于解决数据库表中的字段和属性) ...
- 超好用的截图软件Snipaste(包含安装包)、如何设置Snipaste开机自启
文章目录 1.设置开机自动启动 2.基本使用 链接: https://pan.baidu.com/s/1oR2qkOZl5-etPxr7kWip7Q 提取码:8888 1.设置开机自动启动 2.基本使 ...
- 在mybatis中#{}和${}的区别
文章目录 1.第一个#{} 2.第二个${} 3.区别 1.第一个#{} 解释: 使用#{}格式的语法在mybatis中使用preparement语句来安全的设置值 PreparedStatement ...
- 齐博x1前台后台地址跳转的处理
系统有三个入口,分别是 admin.php index.php member.php 所以就不能简单的使用TP默认的 url() 函数 而插件跟频道模块又有所不同,下面先讲解最基本的频道模块当中如何使 ...
- LcdToos如何在线对屏进行读写指令调试
在实际屏调试过程中,工程师经常需要对屏的寄存器频繁进行参数修改和读取测试,LcdTools针对这个做了很好的支持,可以在线进行指令调试,大大提高调试效率. 打开点屏工程,连接PX01并使模组上电点亮. ...