项目实战系列三: 家居购项目 第五部分

news2024/11/25 12:55:41

🌳显示订单[订单管理]

🌳暂时缺货

需求分析
1.如果某家居库存为0, 首页的"Add to Cart" 按钮显示为"暂时缺货"
2.后台也加上校验. 只有在 库存>0 时, 才能添加到购物车

在这里插入图片描述

代码实现
1.修改web/views/customer/index.jsp

<c:if test="${furn.inventory <= 0}">
    <button disabled title="Add To Cart" class="add-to-cart">Add
        To Cart[缺货]
    </button>
</c:if>
<c:if test="${furn.inventory > 0}">
    <button furnId="${furn.id}" title="Add To Cart" class="add-to-cart">Add
        To Cart
    </button>
</c:if>

2.修改src/com/zzw/furns/web/CartServlet.java, 当添加购物车时, 要保证商品的库存>购物车中的已有的商品数量, 不然库存不够没法继续添加

//添加一个添加家居到购物车的方法
protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    int id = DataUtils.parseInt(request.getParameter("id"), 1);//家居id
    //根据id获取对应的家居信息
    Furn furn = furnService.queryFurnById(id);
    //先把正常的逻辑走完, 再处理异常的情况
    if (furn != null && furn.getInventory() <= 0) {
        response.sendRedirect(request.getHeader("Referer"));
        return;
    }

    HttpSession session = request.getSession();
    Cart cart = (Cart) session.getAttribute("cart");
    //得到购物车 有可能是空的,也有可能是上次的
    if (cart == null) {
        cart = new Cart();
        session.setAttribute("cart", cart);
    }
    Map<Integer, CartItem> items = cart.getItems();
    if (cart.isEmpty()) {
        CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice());
        cart.addItem(cartItem, furn.getInventory());
    }
    //构建一条家居明细: id,家居名,数量, 单价, 总价
    //count类型为Integer, 不赋值默认值为null
    CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice());
    //将家居明细加入到购物车中. 如果家居id相同,数量+1;如果是一条新的商品,那么就新增
    cart.addItem(cartItem, furn.getInventory());
    System.out.println("cart= " + cart);

    String referer = request.getHeader("referer");
    response.sendRedirect(referer);
}

3…修改src/com/zzw/furns/entity/Cart.java

//添加购物车, 只有在结账的时候库存变化.
public void addItem(CartItem cartItem, Integer inventory) {
    //检查要添加的商品是否已存在购物车中
    //如果没有, 则添加; 如果有, 则增加购物车中商品的数量.
    //这里默认添加的数量为1
    CartItem item = items.get(cartItem.getId());
    if (item == null) {
        items.put(cartItem.getId(), cartItem);
    } else if (item.getCount() < inventory){//添加购物车时, 要保证商品的库存>购物车中商品数量, 不然没法下单发货
        item.setCount(item.getCount() + 1);
        //修改总价,新的数量✖单价
        item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
    }
}

需求分析
1.购物车里, 更新家居数量时,前台加以限制.
2.后台也加上校验. 只有在 库存>0 时, 才能更新家居数量

代码实现
1.修改src/com/zzw/furns/web/CartServlet.java

//修改指定cartItem的数量和总价
protected void updateCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //传 商品id, 商品count
    Integer id = DataUtils.parseInt(request.getParameter("id"), 1);
    Integer count = DataUtils.parseInt(request.getParameter("count"), 0);

    //得到商品库存
    Furn furn = furnService.queryFurnById(id);
    if (furn != null && !(furn.getInventory() > 0 && count <= furn.getInventory())) {
        response.sendRedirect(request.getHeader("Referer"));
        return;
    }

    //得到购物车
    HttpSession session = request.getSession();
    Cart cart = (Cart) session.getAttribute("cart");
    if (cart != null) {
        cart.updateCount(id, count);
    }
    response.sendRedirect(request.getHeader("Referer"));
}

🌳管理订单

需求分析
1.完成订单管理-查看
2.具体流程参考显示家居
3.静态页面order.html 和 order_detail.html 已提供

程序框架图

在这里插入图片描述

代码实现
1.修改com.zzw.furns.OrderDAO

//根据memberId返回Order对象
public List<Order> queryOrderByMemberId(Integer memberId);

2.修改com.zzw.furns.impl.OrderDaoImpl

@Override
public List<Order> queryOrderByMemberId(Integer memberId) {
    String sql = "SELECT id, `create_time` as createTime, price, `count`, " +
            "`status`, member_id as memberId FROM `order` WHERE member_Id=?" +
            " order by createTime desc";
    List<Order> orderList = queryMany(sql, Order.class, memberId);
    return orderList;
}

3.测试, 修改OrderDaoTest

@Test
public void queryOrderByMemberId() {
    List<Order> orderList = orderDAO.queryOrderByMemberId(9);
    for (Order order : orderList) {
        System.out.println(order);
    }
}

4.修改com.zzw.furns.OrderService

//显示订单
public List<Order> queryOrderByMemberId(Integer memberId);

5.修改com.zzw.furns.impl.OrderServiceImpl

@Override
public List<Order> queryOrderByMemberId(Integer memberId) {
    List<Order> list = orderDAO.queryOrderByMemberId(memberId);
    return list;
}

6.测试,修改com.zzw.furns.impl.OrderServiceTest

@Test
public void queryOrderByMemberId() {
    List<Order> orders = orderService.queryOrderByMemberId(9);
    for (Order order : orders) {
        System.out.println(order);
    }
}

7.web层 - 修改src/com/zzw/furns/web/OrderServlet.java, 增加listByMemberId方法

protected void listByMemberId(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    //获取到登陆的member对象
    Member member = (Member) req.getSession().getAttribute("member");
    if (member == null) {//说明用户没有登录
        //重定向到登陆页面
        req.getRequestDispatcher("/views/member/login.jsp")
                .forward(req, resp);
        return;//直接返回
    }

    List<Order> orders = orderService.queryOrderByMemberId(member.getId());
    //把订单集合放入到request域中
    req.setAttribute("orders", orders);
    //请求转发到order.jsp
    req.getRequestDispatcher("/views/order/order.jsp")
            .forward(req, resp);
}

8.新增web/views/order/order.jsp

<c:forEach items="${requestScope.orders}" var="order">
    <tr>
        <td class="product-name">${order.id}</td>
        <td class="product-name">${order.createTime}</td>
        <td class="product-price-cart"><span class="amount">${order.price}</span></td>
        <td class="product-name"><a href="#">
            <c:choose>
                <c:when test="${order.status == 1}">未发货</c:when>
                <c:when test="${order.status == 2}">已发货</c:when>
                <c:when test="${order.status == 3}">未结账</c:when>
                <c:otherwise>错误</c:otherwise>
            </c:choose>
        </a></td>
        <td class="product-remove">
            <a href="#"><i class="icon-eye"></i></a>
        </td>
    </tr>
</c:forEach>

🌳管理订单项

程序框架图

在这里插入图片描述

代码实现
1.修改com.zzw.furns.OrderDAO

//根据id返回Order对象
public Order queryOrderById(String id);

2.修改com.zzw.furns.impl.OrderDaoImpl

@Override
public Order queryOrderById(String id) {
    String sql = "SELECT id, `create_time` AS createTime, price, `count`, `status`, " +
            "member_id AS memberId FROM `order` WHERE id= ?";
    Order order = querySingle(sql, Order.class, id);
    return order;
}

3.测试, 修改OrderDaoTest

@Test
public void queryOrderById() {
    Order order = orderDAO.queryOrderById("968d9002-a92e-445f-b77e-7654d7a7598e");
    System.out.println(order);
}

4.修改com.zzw.furns.OrderItemDAO

//返回所有OrderItem对象
public List<OrderItem> queryOrderItemByOrderId(String orderId);

5.修改com.zzw.furns.impl.OrderItemDaoImpl

@Override
public List<OrderItem> queryOrderItemByOrderId(String orderId) {
    String sql = "SELECT id, `name`, `count`, price, total_price as totalPrice, " +
            "order_id as orderId FROM order_item WHERE order_id = ?";
    List<OrderItem> orderItemList = queryMany(sql, OrderItem.class, orderId);
    return orderItemList;
}

6.测试, 修改OrderItemDaoTest

@Test
public void queryOrderItemByOrderId() {
    String orderId = "54062db9-e514-4218-b784-a0f5db91ef41";
    List<OrderItem> orderItemList = orderItemDAO.queryOrderItemByOrderId(orderId);
    for (OrderItem orderItem : orderItemList) {
        System.out.println(orderItem);
    }
}

7.修改com.zzw.furns.OrderService

//根据id返回Order对象
public Order queryOrderById(String id);

8.修改com.zzw.furns.impl.OrderServiceImpl

@Override
public Order queryOrderById(String id) {
    Order order = orderDao.queryOrderById(id);
    List<OrderItem> orderItems = orderItemDao.queryOrderItemByOrderId(id);
    order.setItems(orderItems);
    return order;
}

9.测试,修改com.zzw.furns.impl.OrderServiceTest

@Test
public void queryOrderById() {
    Order order = orderService.queryOrderById("968d9002-a92e-445f-b77e-7654d7a7598e");
    System.out.println(order);
}

10.web层 - 修改src/com/zzw/furns/web/OrderServlet.java, 增加listOrderItemByOrderId方法

protected void listOrderItemByOrderId(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String orderId = req.getParameter("orderId");
    orderId = (orderId == null) ? "" : orderId;

    Order order = orderService.queryOrderById(orderId);

    req.setAttribute("order", order);
    req.getRequestDispatcher("/views/order/order_detail.jsp").forward(req, resp);
}

11.修改前端order.jsp

<td class="product-remove">
    <a href="orderServlet?action=listOrderItemByOrderId&orderId=${order.id}"><i class="icon-eye"></i></a>
</td>

12.新增并修改order_detail.jsp

<c:forEach items="${requestScope.order.items}" var="orderItem">
    <tr>
        <td class="product-name"><a href="#">${orderItem.name}</a></td>
        <td class="product-price-cart"><span class="amount">$${orderItem.price}</span></td>
        <td class="product-quantity">${orderItem.count}</td>
        <td class="product-subtotal">$${orderItem.totalPrice}</td>
    </tr>
</c:forEach>
<div class="cart-shiping-update-wrapper">
    <h4>共${requestScope.order.totalCount}件商品 总价 ${requestScope.order.price}元</h4>
    <div class="cart-clear">
        <a href="#">继 续 购 物</a>
    </div>
</div>

🌈过滤器权限验证

需求分析
1.加入过滤器权限验证
2.如果没有登陆, 查看购物车和添加到购物车, 就会自动转到会员登陆页面

代码实现
1.修改web.xml, 配置过滤器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--过滤器一般我们配置在上面-->
    <filter>
        <filter-name>AuthFilter</filter-name>
        <filter-class>com.zzw.furns.filter.AuthFilter</filter-class>
        <init-param>
        	   <!--这里配置了后, 还需要在过滤器中处理-->
            <param-name>excludedUrls</param-name>
            <param-value>/views/manage/manage_login.jsp,/views/member/login.jsp</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>AuthFilter</filter-name>
        <!--这里配置要验证的url
            1.在filter-mapping中的url-pattern配置 要拦截/验证的url
            2.对于我们不去拦截的url, 就不配置
            3.对于要拦截的目录中的某些要放行的资源, 再通过配置指定
        -->
        <url-pattern>/views/cart/*</url-pattern>
        <url-pattern>/views/manage/*</url-pattern>
        <url-pattern>/views/member/*</url-pattern>
        <url-pattern>/views/order/*</url-pattern>
        <url-pattern>/cartServlet</url-pattern>
        <url-pattern>/manage/furnServlet</url-pattern>
        <url-pattern>/orderServlet</url-pattern>
    </filter-mapping>
</web-app>

2.新建src/com/zzw/furns/filter/AuthFilter.java,过滤器逻辑判断

/**
 * 这是用于权限验证的过滤器, 对指定的url进行验证
 * 如果登陆过, 就放行; 如果没有登陆, 就回到登陆页面
 *
 * @author 赵志伟
 * @version 1.0
 */
public class AuthFilter implements Filter {

    private List<String excludedUrls;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //获取到配置的excludedUrls
        String strExcludedUrls = filterConfig.getInitParameter("excludedUrls");
        //分割 /views/manage/manage_login.jsp,/views/member/login.jsp
        String[] split = strExcludedUrls.split(",");
        //将 splitUrl 转成 list
        excludedUrls = Arrays.asList(split);
        System.out.println("excludedUrls= " + excludedUrls);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("请求/cartServlet 被拦截...");
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //得到请求的url
        String url = request.getServletPath();
        System.out.println("url= " + url);

        //判断是否要验证
        if (!excludedUrls.contains(url)) {
            //获取到登陆的member对象
            Member member = (Member) request.getSession().getAttribute("member");
            if (member == null) {//说明用户没有登录
                //转发到登陆页面, 转发不走过滤器
                servletRequest.getRequestDispatcher("/views/member/login.jsp")
                        .forward(servletRequest, servletResponse);

                重定向-拦截-重定向-拦截-重定向-拦截
                //((HttpServletResponse) servletResponse)
                //        .sendRedirect(request.getContextPath() + "/views/member/login.jsp");
                return;//直接返回
            }

        }
        //验证通过, 放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("请求/cartServlet验证通过, 放行");
    }

    @Override
    public void destroy() {

    }
}

🌈事务管理

数据不一致问题

1.将FurnDAOImpl.java的updateFurn方法的sql故意写错. [furnDAO.updateFurn(furn);由ctrl+alt+b定位到updateFurn的实现方法]
2.在OrderServiceImpl的saveOrder()方法内捕获一下异常, 目的是保证程序能够继续执行
3.查看数据库里的数据会有什么结果. 会出现数据不一致的问题.

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我在首页购买了一个小台灯, 数据库中生成了对应的订单和订单项, 但家居表里该小台灯的销量和库存没有变化, 纹丝不动. 相当于客户下单了, 但没有给人家发货.

在这里插入图片描述

在这里插入图片描述

程序框架图

思路分析
1.使用 Filter + ThreadLocal 来进行事务管理
2.说明: 在一次http请求中, servlet-service-dao 的调用过程, 始终是一个线程, 这是使用ThreadLocal的前提
3.使用ThreadLocal来确保所有dao操作都在同一个Connection内

程序框架图

在这里插入图片描述

1.修改src/com/zzw/furns/utils/JdbcUtilsByDruid.java工具类

public class JdbcUtilsByDruid {
    private static DataSource dataSource;
    //定义属性ThreadLocal, 这里存放一个Connection
    private static ThreadLocal<Connection> threadlocalConn = new ThreadLocal<>();

    /**
     * 从ThreadLocal获取connection, 从而保证在一个线程中
     * 获取的是同一个Connection
     */
    public static Connection getConnection() {
        Connection connection = threadlocalConn.get();
        if (connection == null) {//说明当前的threadlocal没有这个连接
            try {
                //就从数据库连接池中取出连接放入threadlocal
                connection = dataSource.getConnection();
                //将连接设置为手动提交, 既不要让它自动提交
                connection.setAutoCommit(false);
                threadlocalConn.set(connection);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return connection;
    }

    /**
     * 提交事务
     */
    public static void commit() {
        Connection connection = threadlocalConn.get();
        if (connection != null) {
            try {
                connection.commit();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            //1.当提交后, 需要把connection从threadlocalConn中清除掉
            //2.不然会造成threadlocalConn长时间持有该连接, 会影响效率
            //3.也因为Tomcat底层使用的是线程池技术
            threadlocalConn.remove();
        }
    }

    /**
     * 说明: 所谓回滚是 回滚/撤销 和connection管理的操作 删除/修改/添加
     */
    public static void rollback() {
        Connection connection = threadlocalConn.get();
        if (connection != null) {
            try {
                connection.rollback();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            threadlocalConn.remove();
        }
    }

    //public static Connection getConnection() {
    //    try {
    //        return dataSource.getConnection();
    //    } catch (SQLException e) {
    //        throw new RuntimeException(e);
    //    }
    //}

    static {
        try {
            Properties properties = new Properties();
            //properties.load(new FileInputStream("src/druid.properties"));
            //因为我们是web项目, 它的工作目录在out. 文件的加载,需要使用类加载器
            //需要找到我们的工作目录
            properties.load(JdbcUtilsByDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    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);
        }
    }
}

2.修改src/com/zzw/furns/dao/BasicDAO.java
删掉各个方法finally代码块里的close方法. 只有在事务结束后才实施关闭连接的操作. 一是提交事务后关闭连接; 二是增删改出错后, 回滚关闭连接.

public class BasicDAO<T> {

    private QueryRunner queryRunner = new QueryRunner();

    /**
     * @param sql sql语句, 可以有 占位符?
     * @param clazz 传入一个类的Class对象, 例如User.class
     * @param objects 传入具体的值, 对应sql中的占位符?, 可以有多个
     * @return 根据对应的User.class, preparedStatement->resultSet->ArrayList, 返回最终的ArrayList集合
     */
    public List<T> queryMany(String sql, Class<T> clazz, Object... objects) {
        Connection connection = null;
        try {
            connection = JdbcUtilsByDruid.getConnection();
            List<T> tList =
                    queryRunner.query(connection, sql, new BeanListHandler<>(clazz), objects);
            return tList;
        } catch (SQLException e) {
            throw new RuntimeException(e);//编译异常->运行异常抛出
        }
    }

    //查询单行, 返回的是一个对象
    public T querySingle(String sql, Class<T> clazz, Object... objects) {
        Connection connection = null;
        try {
            connection = JdbcUtilsByDruid.getConnection();
            T object
                    = queryRunner.query(connection, sql, new BeanHandler<>(clazz), objects);
            return object;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //查询某一字段
    public Object queryScalar(String sql, Object... objects) {
        Connection connection = null;
        try {
            connection = JdbcUtilsByDruid.getConnection();
            Object query = queryRunner.query(connection, sql, new ScalarHandler(), objects);
            return query;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int update(String sql, Object... objects) {
        Connection connection = null;
        try {
            //这里是从数据库连接池获取connection
            //注意:每次从连接池中取出connection, 不能保证是同一个
            //1.我们目前已经是从和当前线程关联的ThreadLocal获取的connection
            //2.所以可以保证是同一个连接[在同一个线程中/在同一个请求中 => 因为一个请求对应一个线程]
            connection = JdbcUtilsByDruid.getConnection();
            return queryRunner.update(connection, sql, objects);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3.修改控制层src/com/zzw/furns/web/OrderServlet.java, 进行事务管理
前提OrderServiceImpl里报错的代码取消try-catch, 在OrderServlet控制层捕获

protected void saveOrder(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    HttpSession session = request.getSession();
    Cart cart = (Cart) session.getAttribute("cart");
    //如果购物车为空,或者购物车在session,没有家居信息
    if (cart == null || cart.isEmpty()) {
        response.sendRedirect(request.getHeader("Referer"));
        return;
    }

    //获取登录到的member
    Member member = (Member) session.getAttribute("member");
    if (member == null) {
        //用户未登录,不可点击订单管理,重定向到登陆页面
        request.getRequestDispatcher("/views/member/login.jsp").forward(request, response);
        return;
    }
    String orderId = "";
    try {
        orderId = orderService.saveOrder(cart, member.getId());
        JdbcUtilsByDruid.commit();
    } catch (Exception e) {
        JdbcUtilsByDruid.rollback();
        e.printStackTrace();
    }
    session.setAttribute("orderId", orderId);
    response.sendRedirect(request.getContextPath() + "/views/order/checkout.jsp");
}

Transaction过滤器

程序框架图

在这里插入图片描述

体会: 异常机制是可以参与业务逻辑的

在这里插入图片描述
在这里插入图片描述

代码实现
1.在OrderService控制层里取消捕获异常, 将代码重新改回下述模样
String orderId = orderService.saveOrder(cart, member.getId());

2.同时BasicServlet模板里也取消异常捕获, 或者将异常抛出, 代码如下

String action = req.getParameter("action");
System.out.println("action= " + action);//login register page
try {
    Method declaredMethod =
            this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
    System.out.println("declaredMethod= " + declaredMethod);
    System.out.println("this = " + this);//com.zzw.furns.web.MemberServlet@38f54ed7
    declaredMethod.invoke(this, req, resp);
    System.out.println("this.getClass() = " + this.getClass());
} catch (Exception e) {
    //将发生的异常, 继续throw
    throw new RuntimeException(e);
}

3.新建src/com/zzw/furns/filter/TransactionFilter.java.
在代码执行完毕后, 会运行到Transaction过滤器的后置代码, 在这里进行异常捕获, 如果发生异常, 则回滚.

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) {
        try {
            //放行
            chain.doFilter(request, response);
            JdbcUtilsByDruid.commit();//统一提交
        } catch (Exception e) {//出现了异常
            JdbcUtilsByDruid.rollback();//回滚
            e.printStackTrace();
        }
    }
}

配置web.xml

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

🌈统一错误页面

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

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

1.在/views/error引入404.html, 500.html, 修改成jsp文件

在这里插入图片描述

2.将跳转链接改成index.jsp

<a class="active" href="index.jsp">
    <h4 style="color: darkblue">您访问的页面不存在 返回首页</h4>
</a>
<a class="active" href="index.jsp">
    <h4 style="color: darkblue">sorry,您访问的页面出现了错误 返回首页</h4>
</a>

3.配置web.xml

<!--错误提示的配置一般写在web.xml的下面-->

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

4…修改事务过滤器, 将异常抛给tomcat

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);
            JdbcUtilsByDruid.commit();//统一提交
        } catch (Exception e) {//出现了异常
            //只有在try{}中出现了异常, 才会进行catch{}
            //才会进行回滚
            JdbcUtilsByDruid.rollback();//回滚
            //抛出异常, 给tomcat. tomcat会根据error-page来显示对应页面
            throw new RuntimeException(e);
            //e.printStackTrace();
        }
    }

    @Override
    public void destroy() {

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络层 VIII(网络层设备——路由器)【★★★★★★】

一、冲突域与广播域 这里的“域”表示冲突或广播在其中发生并传播的区域。 1. 冲突域 冲突域是指连接到同一物理介质上的所有结点的集合&#xff0c;这些结点之间存在介质争用的现象&#xff08;能产生冲突的所有设备的集合&#xff09;。也就是说&#xff0c;若这些设备同时发…

ABeam德硕 | 海立集团BI项目正式启动,ABeam中国助力实现以数据之力驱动经营管理

9月2日&#xff0c;由德硕管理咨询&#xff08;上海&#xff09;有限公司作为实施合作伙伴的海立集团BI项目正式启动&#xff0c;海立集团党委书记、总经理缪骏、ABeam大中华区董事长兼总经理中野洋辅出席项目启动仪式。 ABeam大中华区董事长兼总经理中野洋辅 在致辞中表示&am…

【vue+el-table】表格操作列宽度跟随按钮个数自适应, 方法封装全局使用

效果图 以上图片分别代表不同用户权限下所能看到的按钮个数, 操作列宽度也会自适应宽度, 就不会一直处于最大宽度, 导致其他权限用户看到的页面出现大量留白问题. 目录 解决方法解决过程中可能出现的问题width赋值时为什么不放update()中btnDom为什么不能直接调用forEach为…

【网络原理】❤️Tcp 连接管理机制❤️ “三次握手” “四次挥手”的深度理解, 面试最热门的话题,没有之一, 保姆式教学 !!!

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

杰发科技Bootloader(3)—— 基于7801的APP切到Boot

为了方便在APP中跳转到Boot重新进行升级&#xff0c;有两种办法&#xff0c;7840同样可以使用。 1. 调用reset接口进行复位&#xff0c;复位后会先进Boot&#xff0c;再自动跳转到App。 NVIC_SystemReset(); 2. 直接使用跳转指令&#xff0c;参考Boot跳转到App代码&#xff0…

有哪些常用的企业统一门户?为何选择移动应用管理平台WorkPlus

企业统一门户是为了解决企业内部应用分散、管理繁琐的问题&#xff0c;提供集成化的应用平台&#xff0c;旨在提高员工的工作效率和便利性。而在众多的企业统一门户中&#xff0c;作为一款领先的移动应用管理平台&#xff0c;WorkPlus备受青睐。本文将介绍一些常用的企业统一门…

Sentinel 控制界面

一、下载 可视化jar 二、cmd 启动 jar java -Dserver.port8718 -Dcsp.sentinel.dashboard.server127.0.0.1:8718 -Dproject.namesentinel-dashboard -jar sentinel-dashboard-1.8.2.jar 页面访问&#xff1a; http://localhost:8718/#/dashboard/

Elasticsearch Suggesters 自动补全长度设置

问题&#xff1a;在用户输入之后联想词返回长度默认为50&#xff0c;导致返回结果不完全 原因&#xff1a;completion 字段索引时允许存储的文本长度受 max_input_length 参数控制&#xff08;默认为 50 字符&#xff09;。可以在 mapping 中调整这个值 解决&#xff1a;“max…

【西电电装实习】焊接台组、焊接技巧

前言 工训中心 - 电装实习&#xff0c;进行实操的笔记 --- 一、焊台 二、焊枪大功率的握法&#xff08;反握法&#xff09; 三、余锡擦拭 四、pcb板与焊盘 五、焊接技巧 先加热焊盘&#xff0c;再上锡&#xff08;不能不加热焊盘&#xff0c;让锡往下漏&#xff09;上锡的时…

首届世界风筝创意大赛专家委员会成立 艺术大咖共话“风筝文化”

9月6日上午&#xff0c;世界风筝公园首届世界风筝创意大赛专家委员会成立暨座谈会在世界风筝公园召开。来自清华大学、中央音乐学院、中国美术学院等院校的专家学者及国际知名艺术家、设计师齐聚一堂&#xff0c;共同成立大赛专家委员会&#xff0c;并探讨以风筝为主题的艺术创…

简单的springboot log4j2日志配置

简单的springboot log4j2日志配置 1.简介 Log4j2 是 Apache Software Foundation 开发的一个日志记录工具&#xff0c;它是 Log4j 的后续版本&#xff0c;并且在多个方面进行了改进。以下是 Log4j2 的一些关键特性&#xff1a; 性能提升&#xff1a;Log4j2 在设计上做了很多优…

内裤洗衣机哪个牌子好又实惠?诚意盘点五款绝佳内衣洗衣机!

在当今繁忙的生活中&#xff0c;内衣洗衣机已成为我们日常生活中不可或缺的家电。但是&#xff0c;面对市场上众多品牌的内衣洗衣机&#xff0c;那么&#xff0c;到底内衣洗衣机哪个牌子好&#xff1f;本次我将在这篇文章中探讨内衣洗衣机的选购策略&#xff0c;以帮助大家找到…

Java并发编程实战 10 | 线程安全问题

什么是线程安全&#xff1f; 《Java并发实践》的作者 Brian Goetz 对线程安全的定义是&#xff1a;当多个线程访问同一个对象时&#xff0c;如果无需考虑这些线程在运行时的调度策略和交替执行顺序&#xff0c;也不需要进行额外的同步处理&#xff0c;仍然能够得到正确的结果&…

C语言--12字符串处理函数

函数strstr 函数strchr与strrchr 注意&#xff1a; 这两个函数的功能&#xff0c;都是在指定的字符串 s 中&#xff0c;试图找到字符 c。strchr() 从左往右找第一个&#xff0c;strrchr() 从左往右找最后一个。字符串结束标记 ‘\0’ 被认为是字符串的一部分。 函数strlen 示例…

一款免费开源功能丰富的看图软件NeeView

NeeView 是一款功能丰富的图像查看软件&#xff0c;它以其独特的浏览体验和广泛的支持格式受到用户的欢迎。NeeView 不仅可以浏览普通的图像文件&#xff0c;还能够查看压缩包内的图片、预览PDF文档甚至播放视频文件。 NeeView 的主要特点&#xff1a; 多格式支持&#xff1a…

《人工智能安全治理框架》1.0版

人工智能是人类发展新领域&#xff0c;给世界带来巨大机遇&#xff0c;也带来各类风险挑战落实《全球人工智能治理倡议》&#xff0c;遵循“以人为本、智能向善”的发展方向&#xff0c;为推动政府、国际组织、企业、科研院所、民间机构和社会公众等各方&#xff0c;就人工智能…

无人机之穿越机的类型

穿越机&#xff0c;即FPV Drone或Racing Drone&#xff0c;是一种主要通过第一人称视角&#xff08;FPV&#xff09;进行操作的无人机。这种无人机通常配备有四个电机和相应的飞控系统&#xff0c;使其具有极高的飞行自由度和速度。穿越机的类型多样&#xff0c;可以从不同角度…

GD32E230程序烧录和开发环境使用介绍

GD32E230程序烧录和开发环境使用介绍 从GD32提供的资料来看&#xff0c;支持IAR、Keil、EmbeddedBuilder&#xff1b;目前该软件还是比较粗糙&#xff0c;个人上手体验不佳&#xff0c;面板菜单按键烧操作一下&#xff0c;动不动就卡死&#xff0c;仅支持gdlink调试器。 Embed…

第100+24步 ChatGPT学习:概率校准 Beta Calibration

基于Python 3.9版本演示 一、写在前面 最近看了一篇在Lancet子刊《eClinicalMedicine》上发表的机器学习分类的文章&#xff1a;《Development of a novel dementia risk prediction model in the general population: A large, longitudinal, population-based machine-learn…

元宇宙的崛起:重塑2024年游戏行业的新趋势

最早可以追溯到1994年&#xff0c;当时出现了世界上第一个轴测图界面的多人互动社交游戏《Web World》。‌这个游戏允许用户实时聊天、旅游、改造场景&#xff0c;开启了游戏中的UGC模式&#xff0c;可以视为元宇宙游戏的雏形。 2021年Roblox元宇宙的概念股上市&#xff0c;Fac…