7、书城第八阶段
1、使用Filter过滤器拦截/pages/manager/所有内容,实现权限检查
新建com.atguigu/filter/MangerFilter
package com.atguigu.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class ManageFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest; Object user = httpServletRequest.getSession().getAttribute("user"); if (user==null){ httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse); }else { filterChain.doFilter(servletRequest,servletResponse); } } @Override public void destroy() { } }
配置web.xml
<filter> <filter-name>ManageFilter</filter-name> <filter-class>com.atguigu.filter.ManageFilter</filter-class> </filter> <filter-mapping> <filter-name>ManageFilter</filter-name> <url-pattern>/pages/manager/*</url-pattern> <url-pattern>/manager/bookServlet</url-pattern> </filter-mapping>
2、ThreadLocal的使用
ThreadLocal的作用,它可以解决多线程的数据安全问题。
ThreadLocal它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
Threadlocal的特点:
1、Threadlocal可以为当前线程关联一个数据。(它可以像Map一样存取数据,key为当前线程)
2、每一个Threadlocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 Threadlocal对象实例。
3、每个Threadlocal对象实例定义的时候,一般都是static类型
4、Threadlocal中保存数据,在线程销毁后。会由JVM虚拟自动释放。
新建tmp/src/threadlocal/ThreadLocalTest
package threadlocal; import java.util.Random; public class ThreadLocalTest { // public final static Map<String,Object> data=new ConcurrentHashMap<>();线程安全 // public final static Map<String,Object> data=new Hashtable<>(); public static ThreadLocal<Object> threadLocal=new ThreadLocal<>(); private static Random random=new Random(); public static class Task implements Runnable{ @Override public void run() { // threadLocal.set("abc"); // threadLocal.set("bbj"); // System.out.println(threadLocal.get());//bbj 覆盖 //在run方法中 ,随机生成一个变量(线程要关联的数据),然后一当前线程名为key保存到map中 Integer i = random.nextInt(1000); //获取当前线程名 String name = Thread.currentThread().getName(); System.out.println("线程["+name+"]生成的随机数是:"+i); // data.put(name,i); threadLocal.set(i); //模拟操作 // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } new OrderService().createOrder(); //在Run方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作 // Object o = data.get(name); Object o=threadLocal.get(); System.out.println("线程["+name+"]快结束时取出关联的数据是:"+o); } } public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Task()).start(); } } }
新建threadlocal/OrderService
package threadlocal; public class OrderService { public void createOrder(){ String name = Thread.currentThread().getName(); System.out.println("OrderService 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get()); new OrderDao().saveOrder(); } }
新建threadlocal/OrderDao
package threadlocal; public class OrderDao { public void saveOrder(){ String name = Thread.currentThread().getName(); // System.out.println("OrderDao 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.data.get(name)); System.out.println("OrderDao 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get()); } }
结果
3、使用Filter和ThreadLocal组合管理事务
3.1 使用ThreadLocal确保所有操作都使用同一个Connection来实现
验证是否为同一线程
修改 OrderServiceImpl 模拟错误
package com.atguigu.service.impl; import com.atguigu.dao.BookDao; import com.atguigu.dao.OrderDao; import com.atguigu.dao.OrderItemDao; import com.atguigu.dao.impl.BookDaoImpl; import com.atguigu.dao.impl.OrderDaoImpl; import com.atguigu.dao.impl.OrderItemDaoImpl; import com.atguigu.pojo.*; import com.atguigu.service.OrderService; import java.util.Date; import java.util.List; import java.util.Map; public class OrderServiceImpl implements OrderService { private OrderDao orderDao =new OrderDaoImpl(); private OrderItemDao orderItemDao=new OrderItemDaoImpl(); private BookDao bookDao=new BookDaoImpl(); @Override public String createOrder(Cart cart, Integer userId) { System.out.println("OrderServiceImpl 程序在["+Thread.currentThread().getName()+"]中"); //订单号==唯一性 String orderId=System.currentTimeMillis()+""+userId; //创建一个订单对象 Order order=new Order(orderId,new Date(),cart.getTotalPrice(),0,userId); //保存订单 orderDao.saveOrder(order); //模拟错误 int i=12/0; //遍历购物车中每一个商品项转换为订单保存到数据库 for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()) { //获取购物车每一个商品项 CartItem cartItem=entry.getValue(); //转换为订单 OrderItem orderItem=new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId); //保存到数据库 orderItemDao.saveOrderItem(orderItem); //更新库存和销量 Book book = bookDao.queryBookById(cartItem.getId()); book.setSales(book.getSales()+cartItem.getCount()); book.setStock(book.getStock()-cartItem.getCount()); bookDao.updateBook(book); } //清空购物车 cart.clear(); return orderId; } @Override public List<Order> showAllOrders() { return orderDao.queryOrders(); } @Override public int sendOrder(String orderId) { return orderDao.changeOrderStatus(orderId,1); } @Override public List<OrderItem> showOrderDetail(String orderId) { return orderItemDao.queryOrderItemByOrderId(orderId); } @Override public List<Order> showMyOrders(int userId) { return orderDao.queryByUserId(userId); } @Override public int receiverOrder(String orderId) { return orderDao.changeOrderStatus(orderId,2); } }
结果 网页,添加购物车去结账
t_order表无记录
t_order_item有记录
原理
修改 JdbcUtils
package com.atguigu.utils; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class JdbcUtils { private static DruidDataSource dataSource; private static ThreadLocal<Connection> conns=new ThreadLocal<>(); static { try { Properties properties=new Properties(); //读取jdbc.properties属性配置文件 InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); //从流中加载数据 properties.load(inputStream); //创建数据连接池 dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);//Ctrl+ALT+T } catch (Exception e){ e.printStackTrace(); } } /** * 获取数据库连接池中的连接 * @return 如果返回null,说明获取连接失败<br/> 有值就是获取连接成功 */ public static Connection getConnection(){ Connection conn=conns.get(); if (conn==null){ try { conn= dataSource.getConnection();//从数据库连接池中获取连接 conns.set(conn);//保存到ThreadLocal对象中,供后面的jdbc操作使用 conn.setAutoCommit(false);//设置为手动管理事务 } catch (SQLException throwables) { throwables.printStackTrace(); } } return conn; } /** * 提交事务,并关闭释放连接 */ public static void commitAndClose(){ Connection connection=conns.get(); if (connection!=null){//如果不等于null,说明之前使用过连接,操作过数据库 try { connection.commit();//提交 事务 } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { connection.close();//关闭连接,释放资源 } catch (SQLException throwables) { throwables.printStackTrace(); } } } //一定要执行remove操作,否则就会出错。(因为Tomcat服务器底层使用了线程池技术) conns.remove(); } /** * 回滚事务,并关闭释放连接 */ public static void rollbackAndClose(){ Connection connection=conns.get(); if (connection!=null){//如果不等于null,说明之前使用过连接,操作过数据库 try { connection.rollback();//回滚 事务 } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { connection.close();//关闭连接,释放资源 } catch (SQLException throwables) { throwables.printStackTrace(); } } } //一定要执行remove操作,否则就会出错。(因为Tomcat服务器底层使用了线程池技术) conns.remove(); } /** * 关闭连接,放回数据库连接池 * @param conn public static void close(Connection conn){ if (conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } */ }
修改 JdbcUtilsTest
package com.atguigu.test; import org.junit.Test; public class JdbcUtilsTest { @Test public void testJdbcUtils(){ // for (int i = 0; i < 100; i++) { // Connection connection = JdbcUtils.getConnection(); // System.out.println(connection); // JdbcUtils.close(connection); // } } }
修改 BaseDao
package com.atguigu.dao; import com.atguigu.utils.JdbcUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public abstract class BaseDao { //使用DbUtils操作数据库 private QueryRunner queryRunner=new QueryRunner(); /** * update() 方法用来执行,Insert\Update\Delete语句 * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数 */ public int update(String sql,Object... args){ System.out.println("BaseDao 程序在["+Thread.currentThread().getName()+"]中"); Connection connection= JdbcUtils.getConnection(); try { return queryRunner.update(connection,sql,args); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 查询返回一个javabean的sql语句 * @param type 返回的对象类型 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @param <T> 返回类型的泛型 * @return */ public <T> T queryForOne(Class<T> type,String sql,Object... args){ Connection con=JdbcUtils.getConnection(); try { return queryRunner.query(con,sql,new BeanHandler<T>(type),args); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 查询返回多个javabean的sql语句 * @param type 返回的对象类型 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @param <T> 返回类型的泛型 * @return */ public <T>List<T> queryForList(Class<T> type,String sql,Object... args){ Connection con=JdbcUtils.getConnection(); try { return queryRunner.query(con,sql,new BeanListHandler<T>(type),args); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 执行返回一行一列的sql语句 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @return */ public Object queryForSingleValue(String sql,Object... args){ Connection conn=JdbcUtils.getConnection(); try { return queryRunner.query(conn,sql,new ScalarHandler(),args); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } }
修改 OrderServlet
package com.atguigu.web; import com.atguigu.pojo.Cart; import com.atguigu.pojo.User; import com.atguigu.service.OrderService; import com.atguigu.service.impl.OrderServiceImpl; import com.atguigu.utils.JdbcUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class OrderServlet extends BaseServlet { private OrderService orderService = new OrderServiceImpl(); /** * 生成订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先获取Cart购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart"); //获取Userid User loginUser = (User) req.getSession().getAttribute("user"); if (loginUser == null) { req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp); return; } System.out.println("OrderServlet 程序在["+Thread.currentThread().getName()+"]中"); Integer userId = loginUser.getId(); //调用orderservice.createorder(cart,userid);生成订单 String orderId = null; try { orderId = orderService.createOrder(cart, userId); JdbcUtils.commitAndClose();//提交事务 } catch (Exception e) { JdbcUtils.rollbackAndClose();//回滚事务 e.printStackTrace(); } // req.setAttribute("orderId",orderId); //请求转发至pages/cart/checkout.jsp // req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp); req.getSession().setAttribute("orderId", orderId); resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp"); } /** * 查看所有订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showAllOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 发货 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void sendOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 查看订单详情 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showOrderDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 查看我的订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showMyOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 签收订单/确认收货 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void receiverOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
结果 网页,添加购物车去结账
3.2、使用Filter统一给所有Service方法都加上try-catch来实现管理事务
新建 filter/TransactionFilter
package com.atguigu.web; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; public abstract class BaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action=req.getParameter("action"); // System.out.println(action); //action的value和调用的方法名是统一的 // if ("login".equals(action)){ System.out.println("处理登录的需求"); // login(req,resp); // }else if ("regist".equals(action)){ System.out.println("处理注册的需求"); // regist(req,resp); // } //反射 try { //获取action业务鉴别字符串,获取相应的业务方法 反射对象 Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class); //调用目标业务方法 method.invoke(this,req,resp); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e);//把异常抛给Filter过滤器 } } }
配置web.xml
<filter> <filter-name>TransactionFilter</filter-name> <filter-class>com.atguigu.filter.TransactionFilter</filter-class> </filter> <filter-mapping> <filter-name>TransactionFilter</filter-name> <!-- /*当前工程下所有请求 --> <url-pattern>/*</url-pattern> </filter-mapping>
修改 OrderServlet
package com.atguigu.web; import com.atguigu.pojo.Cart; import com.atguigu.pojo.User; import com.atguigu.service.OrderService; import com.atguigu.service.impl.OrderServiceImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class OrderServlet extends BaseServlet { private OrderService orderService = new OrderServiceImpl(); /** * 生成订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先获取Cart购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart"); //获取Userid User loginUser = (User) req.getSession().getAttribute("user"); if (loginUser == null) { req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp); return; } System.out.println("OrderServlet 程序在["+Thread.currentThread().getName()+"]中"); Integer userId = loginUser.getId(); //调用orderservice.createorder(cart,userid);生成订单 String orderId = orderService.createOrder(cart, userId); // req.setAttribute("orderId",orderId); //请求转发至pages/cart/checkout.jsp // req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp); req.getSession().setAttribute("orderId", orderId); resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp"); } /** * 查看所有订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showAllOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 发货 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void sendOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 查看订单详情 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showOrderDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 查看我的订单 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void showMyOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } /** * 签收订单/确认收货 * * @param req * @param resp * @throws ServletException * @throws IOException */ public void receiverOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
修改 BaseServlet
package com.atguigu.web; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; public abstract class BaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action=req.getParameter("action"); // System.out.println(action); //action的value和调用的方法名是统一的 // if ("login".equals(action)){ System.out.println("处理登录的需求"); // login(req,resp); // }else if ("regist".equals(action)){ System.out.println("处理注册的需求"); // regist(req,resp); // } //反射 try { //获取action业务鉴别字符串,获取相应的业务方法 反射对象 Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class); //调用目标业务方法 method.invoke(this,req,resp); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e);//把异常抛给Filter过滤器 } } }
3.3、将所有异常都交给Tomcat,让Tomcat显示友好的错误信息页面
在web.xml 中我们可以通过错误页面配置来进行管理。
新建 pages/error/error500.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>error500</title> <%--静态包含 base标签,css样式,jquery文件 --%> <%@ include file="/pages/common/head.jsp"%> </head> <body> 很抱歉,您访问的页面不存在,或已经被删除!!!<br> <a href="index.jsp">返回首页</a> </body> </html>
并配置 web.xml
<!-- error-page标签配置,服务器出错之后,自动跳转的页面 --> <error-page> <!-- error-code是错误类型 --> <error-code>500</error-code> <!-- location标签表示,要跳转的页面路径--> <location>/pages/error/error500.jsp</location> </error-page>
修改 TransactionFilter
package com.atguigu.filter; import com.atguigu.utils.JdbcUtils; import javax.servlet.*; import java.io.IOException; 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 { filterChain.doFilter(servletRequest,servletResponse); JdbcUtils.commitAndClose();//提交事务 } catch (Exception e) { JdbcUtils.rollbackAndClose();//回滚事务 e.printStackTrace(); throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好的错误页面 } } @Override public void destroy() { } }
新建error/error404.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>error404</title> <%--静态包含 base标签,css样式,jquery文件 --%> <%@ include file="/pages/common/head.jsp"%> </head> <body> 很抱歉,您访问的页面不存在,或已经被删除!!!<br> <a href="index.jsp">返回首页</a> </body> </html>
配置web.xml
<!-- error-page标签配置,服务器出错之后,自动跳转的页面 --> <error-page> <!-- error-code是错误类型 --> <error-code>404</error-code> <!-- location标签表示,要跳转的页面路径--> <location>/pages/error/error404.jsp</location> </error-page>