Javaweb学习笔记day13---项目五阶段

本文详细记录了JavaWeb项目第五阶段的学习笔记,包括项目准备、Filter全局编码解决请求乱码问题、登录状态校验、订单模型创建、订单业务结算流程以及事务管理。特别探讨了在订单结算中遇到的事务问题,如如何使用ThreadLocal进行事务管理,以及CloseConnectionFilter过滤器的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目五阶段准备

  • 项目搭建
    • v4 -> v5
    • 直接复制

Filter全局编码

  • 需求
    • 用户登录功能,将请求方式修改为post,会有请求参数中文乱码问题,可以使用Filter过滤器来统一解决。
  • 代码实现
public class EncodingFilter implements Filter {

    private String encoding ;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding(encoding);
        chain.doFilter(request, response);
    }
}
<filter>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>com.atguigu.filter.EncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

登录状态校验

  • 需求
    • 把项目中一些功能保护起来,没有登录不允许访问,比如:购物车功能、订单功能、图书管理功能等等。
  • 代码实现
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //校验登录状态
        Object existUser = request.getSession().getAttribute(BookstoreConstant.SESSION_KEY_USER);
        if (existUser == null) {
            //没有登录 , 跳转到login.html
            try {
                response.sendRedirect(request.getContextPath() + "/user?method=toLoginPage");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            //有登录,直接放行
            chain.doFilter(request, response);

        }
    }
}
<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>com.atguigu.filter.LoginFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>LoginFilter</filter-name>
    <url-pattern>/protected/*</url-pattern>
</filter-mapping>
**注意事项**

- LoginFilter只过滤受保护的资源("/protected/*"),否则会有死循环。

订单模型

  • 需求
    • ①创建Order类
    • ②创建OrderItem类
    • ③引入lombok
      • ①settings -> plugins -> lombok -> 安装 ->重启idea
      • ②settings -> build,execution,deployement -> compiler -> annotation processors -> 勾选 enable annotation processing
      • ③当前项目引入lombok.jar
      • ④使用lombok的注解
  • ①创建Order类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Order {


    private Integer orderId;//订单id
    private String orderSequence;//订单序列号
    private String createTime;//订单创建时间
    private Integer totalCount;//总数量
    private Double totalAmount;//总金额
    private Integer orderStatus;//订单状态(0:未完成 , 1:已完成)
    private Integer userId;//用户id



}

②创建OrderItem类

@NoArgsConstructor
@AllArgsConstructor
@Data
public class OrderItem {

    private Integer itemId;//订单项id
    private String bookName;//图书名称
    private Double price;//图书价格
    private String imgPath;//图书图片
    private Integer itemCount;//订单项数量 (购物车项数量)
    private Double itemAmount; //订单项金额(购物车项金额)
    private Integer orderId;//订单id

}

③引入lombok

订单业务结算流程

  • 业务流程
    • image-20211214102458082

订单结算之生成订单

代码实现

<a class="pay" th:href="@{/protected/orderClient(method=checkout)}">去结账</a>
@WebServlet("/protected/orderClient")
public class ProtectedOrderServlet extends ModelBaseServlet {

    /**
     * 订单结算
     * @param request
     * @param response
     */
    public void checkout(HttpServletRequest request, HttpServletResponse response){
        Cart existCart = (Cart) request.getSession().getAttribute(BookstoreConstant.SESSION_KEY_CART);
        User existUser = (User) request.getSession().getAttribute(BookstoreConstant.SESSION_KEY_USER);
        OrderService orderService = new OrderServiceImpl();
        try {
            orderService.checkout(existCart,existUser);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
public class OrderServiceImpl implements OrderService {
    @Override
    public void checkout(Cart existCart, User existUser) throws Exception {
        //----------------------------------①生成订单----------------------------------
        OrderDao orderDao = new OrderDaoImpl();
        Order order = new Order();
        //3.1,订单id自增
        order.setOrderId(null);
        //3.2,设置订单序列号
        String orderSequence = UUID.randomUUID().toString().replace("-", "");
        order.setOrderSequence(orderSequence);
        //3.3,设置订单创建时间
        order.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        //3.4,设置订单总金额
        order.setTotalAmount(existCart.getTotalAmount());
        //3.5,设置订单总数量
        order.setTotalCount(existCart.getTotalCount());
        //3.6,订单订单的用户id
        order.setUserId(existUser.getUserId());
        orderDao.addOrder(order);//当orderDao.addOrder()方法执行完成之后,order对象的orderId就有值啦!
        System.out.println("order = " + order);
        //----------------------------------②生成订单项----------------------------------
        //使用orderId
        //----------------------------------③修改图书销量和库存----------------------------------
    }
}
public class OrderDaoImpl implements OrderDao {
    @Override
    public void addOrder(Order order) throws Exception {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "insert into t_order values(null,?,?,?,?,?,?)";
            statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            statement.setString(1, order.getOrderSequence());
            statement.setString(2, order.getCreateTime());
            statement.setInt(3, order.getTotalCount());
            statement.setDouble(4, order.getTotalAmount());
            statement.setInt(5, BookstoreConstant.ORDER_UNCOMPLETED);
            statement.setInt(6, order.getUserId());
            int addOrder = statement.executeUpdate();
            if (addOrder >= 1) {
                //添加记录成功 , 获取已经生成记录的主键值
                resultSet = statement.getGeneratedKeys();
                while (resultSet.next()) {
                    int orderId = resultSet.getInt(1);
                    order.setOrderId(orderId);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(connection, statement, resultSet);
        }
    }
}

订单结算之生成订单项

代码测试

public class Demo03 {


    public static void main(String[] args) throws Exception {

        //method1();

        method2();

    }

    private static void method1() throws Exception {
        List<User> userList = new ArrayList<>();
        userList.add(new User(null, "zhangsan", "zhangsan", "zs@qq.com"));
        userList.add(new User(null, "lisi", "lisi", "ls@qq.com"));

        UserDao userDao = new UserDaoImpl();
        for (User user : userList) {
            userDao.addUser(user);
        }
    }

    private static void method2() {
        List<User> userList = new ArrayList<>();
        userList.add(new User(null, "zhangsan2", "zhangsan2", "zs2@qq.com"));
        userList.add(new User(null, "lisi2", "lisi2", "ls2@qq.com"));

        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "insert into t_user values(null,?,?,?)";
            //第一个参数:需要进行批量操作的记录的个数
            //第二个参数:所操作的记录的属性个数
            Object[][] params = new Object[userList.size()][3];
            for (int i = 0; i < userList.size(); i++) {
                //i=0 , 循环第一次,获取到批量操作的第一条记录
                //i=1, 循环第二次,获取到批量操作的第二条记录
                User user = userList.get(i);
                params[i][0] = user.getUserName();
                params[i][1] = user.getUserPwd();
                params[i][2] = user.getEmail();
            }
            new QueryRunner().batch(connection, sql, params);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


}

生成订单项

public class OrderServiceImpl implements OrderService {
    @Override
    public void checkout(Cart existCart, User existUser) throws Exception {
        //----------------------------------①生成订单----------------------------------
        
        //----------------------------------②生成订单项----------------------------------
        OrderItemDao orderItemDao = new OrderItemDaoImpl();
        //2.1,获取所有购物车项
        Map<Integer, CartItem> cartItemMap = existCart.getCartItemMap();
        //2.2,数组:第一个参数:要批量添加的订单项数量;第二个参数:操作订单项的属性数量
        Object[][] orderItemParams = new Object[cartItemMap.size()][6];
        List<CartItem> cartItems = new ArrayList<>(cartItemMap.values());
        for (int i = 0; i < cartItems.size(); i++) {
            //2.3,获取每一个购物车项
            CartItem cartItem = cartItems.get(i);
            orderItemParams[i][0] = cartItem.getBookName();
            orderItemParams[i][1] = cartItem.getPrice();
            orderItemParams[i][2] = cartItem.getImgPath();
            orderItemParams[i][3] = cartItem.getCount();
            orderItemParams[i][4] = cartItem.getAmount();
            orderItemParams[i][5] = order.getOrderId();
        }
        orderItemDao.addOrderItems(orderItemParams);
        //----------------------------------③修改图书销量和库存----------------------------------
    }
}
public class OrderItemDaoImpl implements OrderItemDao {
    @Override
    public void addOrderItems(Object[][] orderItemParams) throws Exception {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            new QueryRunner().batch(
                    connection,
                    "insert into t_order_item values(null,?,?,?,?,?,?)",
                    orderItemParams
            );
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(connection, null);
        }

    }
}

订单结算之修改图书库存和销量

代码测试

public class Demo04 {


    public static void main(String[] args) throws Exception {

        //method1();
        method2();

    }

    private static void method1() throws Exception {
        List<User> userList = new ArrayList<>();
        userList.add(new User(4, "zhangsan", "zhangsan", "1@qq.com"));
        userList.add(new User(7, "lisi", "lisi", "2@qq.com"));

        UserDao userDao = new UserDaoImpl();
        for (User user : userList) {
            userDao.updateUserById(user);
        }
    }

    private static void method2() {
        List<User> userList = new ArrayList<>();
        userList.add(new User(4, "zhangsan3", "zhangsan3", "3@qq.com"));
        userList.add(new User(7, "lisi4", "lisi4", "4@qq.com"));

        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "update t_user set user_name = ? , user_pwd = ? , email = ? where user_id = ?";
            //第一个参数:需要进行批量操作的记录的个数
            //第二个参数:所操作的记录的属性个数
            Object[][] params = new Object[userList.size()][4];
            for (int i = 0; i < userList.size(); i++) {
                //i=0 , 循环第一次,获取到批量操作的第一条记录
                //i=1, 循环第二次,获取到批量操作的第二条记录
                User user = userList.get(i);
                params[i][0] = user.getUserName();
                params[i][1] = user.getUserPwd();
                params[i][2] = user.getEmail();
                params[i][3] = user.getUserId();
            }
            new QueryRunner().batch(connection, sql, params);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


}

修改图书库存和销量

public class OrderServiceImpl implements OrderService {
    @Override
    public void checkout(Cart existCart, User existUser) throws Exception {
        //----------------------------------①生成订单----------------------------------

        //----------------------------------②生成订单项----------------------------------

        //----------------------------------③修改图书销量和库存----------------------------------
        BookDao bookDao = new BookDaoImpl();
        Object[][] bookParams = new Object[cartItemMap.size()][3];
        for (int i = 0; i < cartItems.size(); i++) {
            CartItem cartItem = cartItems.get(i);
            bookParams[i][0] = cartItem.getCount();
            bookParams[i][1] = cartItem.getCount();
            bookParams[i][2] = cartItem.getBookId();
        }
        bookDao.updateBooks(bookParams);


    }
}
@Override
public void updateBooks(Object[][] bookParams) throws Exception {
    Connection connection = null;
    try {
        connection = JDBCUtils.getConnection();
        new QueryRunner().batch(
                connection,
                "update t_book set sales = sales + ? , stock = stock - ? where book_id = ?",
                bookParams
        );
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.release(connection, null);
    }
}

清空购物车

代码实现

@WebServlet("/protected/orderClient")
public class ProtectedOrderServlet extends ModelBaseServlet {


    /**
     * 转发到checkout.html
     *
     * @param request
     * @param response
     */
    public void toCheckoutPage(HttpServletRequest request, HttpServletResponse response) {
        try {
            processTemplate("pages/cart/checkout", request, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 订单结算
     *
     * @param request
     * @param response
     */
    public void checkout(HttpServletRequest request, HttpServletResponse response) {
        Cart existCart = (Cart) request.getSession().getAttribute(BookstoreConstant.SESSION_KEY_CART);
        User existUser = (User) request.getSession().getAttribute(BookstoreConstant.SESSION_KEY_USER);
        OrderService orderService = new OrderServiceImpl();
        try {
            String orderSequence = orderService.checkout(existCart, existUser);
            //订单结算成功 , 清空购物车
            request.getSession().removeAttribute(BookstoreConstant.SESSION_KEY_CART);

            request.getSession().setAttribute("orderSequence", orderSequence);
            //重定向到ProtectedOrderServlet类的toCheckoutPage方法
            response.sendRedirect(request.getContextPath() + "/protected/orderClient?method=toCheckoutPage");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

订单结算的存在事物问题

  • 概述
    • 在一个业务中,将多个sql操作看作一个整体,遵守原子性,要么一起成功,要么一起失败。
    • 有一个订单结算的业务,包含多个sql操作:添加订单、批量添加订单项、批量修改图书销量和库存
  • 问题代码
public class OrderServiceImpl implements OrderService {
    @Override
    public String checkout(Cart existCart, User existUser) throws Exception {
        //----------------------------------①生成订单----------------------------------

        System.out.println(1 / 0);

        //----------------------------------②生成订单项----------------------------------

        //----------------------------------③修改图书销量和库存----------------------------------


    }
}

订单结算事务管理初级方案

代码实现

public class OrderServiceImpl implements OrderService {
    @Override
    public String checkout(Cart existCart, User existUser) throws Exception {

        String orderSequence = null;
        Connection connection = null;
        try {
            //开启事务(设置事务手动提交)
            connection = JDBCUtils.getConnection();
            connection.setAutoCommit(false);

            //----------------------------------①生成订单----------------------------------


            System.out.println(1 / 0);

            //----------------------------------②生成订单项----------------------------------

            //----------------------------------③修改图书销量和库存----------------------------------

            //没错,提交事务
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //有错,回滚事务
            connection.rollback();
        } finally {
            JDBCUtils.release(connection, null);
        }
        return orderSequence;

    }
}

存在问题

  • 在OrderServiceImpl的checkout方法中获取了一个Connection对象
  • 在OrderDaoImpl的addOrder方法中获取了一个Connection对象
  • 在OrderItemDaoImpl的addOrderItems方法中获取了一个Connection对象
  • 在BookDaoImpl的updateBooks方法中获取了一个Connection对象
  • 以上四个Connection对象是四个不同的对象,所以无法保证它们在同一个事务中

订单结算事务管理ThreadLocal

  • 概述
    • OrderServiceImpl的checkout方法、OrderDaoImpl的addOrder方法、OrderItemDaoImpl的addOrderItems方法、BookDaoImpl的updateBooks方法,这四个方法在同一个线程中,那么,就可以使用和线程相关容器技术ThreadLocal来解决保证同一个连接的问题。
  • 代码实现
public class JDBCUtils {


    private static DataSource dataSource;

    static {
        Properties properties = new Properties();
        try {
            properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static DataSource getDataSource() {
        return dataSource;
    }


    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static Connection getConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if (null == connection) {
            connection = dataSource.getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }
}
public class OrderServiceImpl implements OrderService {
    @Override
    public String checkout(Cart existCart, User existUser) throws Exception {

        String orderSequence = null;
        Connection connection = null;
        try {
            //开启事务(设置事务手动提交)

            System.out.println("OrderServiceImpl checkout : " + Thread.currentThread().getName());
            connection = JDBCUtils.getConnection();
            connection.setAutoCommit(false);
            System.out.println("OrderServiceImpl checkout : " + connection);

            //----------------------------------①生成订单----------------------------------
            OrderDao orderDao = new OrderDaoImpl();
            Order order = new Order();
            //3.1,订单id自增
            order.setOrderId(null);
            //3.2,设置订单序列号
            orderSequence = UUID.randomUUID().toString().replace("-", "");
            order.setOrderSequence(orderSequence);
            //3.3,设置订单创建时间
            order.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            //3.4,设置订单总金额
            order.setTotalAmount(existCart.getTotalAmount());
            //3.5,设置订单总数量
            order.setTotalCount(existCart.getTotalCount());
            //3.6,订单订单的用户id
            order.setUserId(existUser.getUserId());
            orderDao.addOrder(order);//当orderDao.addOrder()方法执行完成之后,order对象的orderId就有值啦!
            System.out.println("order = " + order);

            System.out.println(1 / 0);

            //----------------------------------②生成订单项----------------------------------
            OrderItemDao orderItemDao = new OrderItemDaoImpl();
            //2.1,获取所有购物车项
            Map<Integer, CartItem> cartItemMap = existCart.getCartItemMap();
            //2.2,数组:第一个参数:要批量添加的订单项数量;第二个参数:操作订单项的属性数量
            Object[][] orderItemParams = new Object[cartItemMap.size()][6];
            List<CartItem> cartItems = new ArrayList<>(cartItemMap.values());
            for (int i = 0; i < cartItems.size(); i++) {
                //2.3,获取每一个购物车项
                CartItem cartItem = cartItems.get(i);
                orderItemParams[i][0] = cartItem.getBookName();
                orderItemParams[i][1] = cartItem.getPrice();
                orderItemParams[i][2] = cartItem.getImgPath();
                orderItemParams[i][3] = cartItem.getCount();
                orderItemParams[i][4] = cartItem.getAmount();
                orderItemParams[i][5] = order.getOrderId();
            }
            orderItemDao.addOrderItems(orderItemParams);
            //----------------------------------③修改图书销量和库存----------------------------------
            BookDao bookDao = new BookDaoImpl();
            Object[][] bookParams = new Object[cartItemMap.size()][3];
            for (int i = 0; i < cartItems.size(); i++) {
                CartItem cartItem = cartItems.get(i);
                bookParams[i][0] = cartItem.getCount();
                bookParams[i][1] = cartItem.getCount();
                bookParams[i][2] = cartItem.getBookId();
            }
            bookDao.updateBooks(bookParams);
            //没错,提交事务
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //有错,回滚事务
            connection.rollback();
        } finally {
            //业务结束
            JDBCUtils.release(connection, null);
        }
        return orderSequence;

    }
}
  • 注意事项
    • 订单结算业务中,连接的关闭,应该在业务结束之后。
  • 存在问题
    • ①事务管理比较冗余,可以独立抽取公共方法;
    • ②如果仅仅只是将connection.close是不够的,这只是将connection对象归还到了连接池,因为使用ThreadLocal容器,导致归还到了连接池之后,connection对象状态依然为closed。

订单结算事务管理ThreadLocal

代码实现

public class JDBCUtils {


    /**
     * 开启事务
     */
    public static void startTransaction() throws SQLException {
        getConnection().setAutoCommit(false);
    }

    /**
     * 回滚事务
     * @throws SQLException
     */
    public static void rollback() throws SQLException {
        getConnection().rollback();
    }


    /**
     * 提交事务
     * @throws SQLException
     */
    public static void commit() throws SQLException {
        getConnection().commit();
    }

    public static void closeConnection() throws SQLException {
        Connection connection = getConnection();
        if (null == connection) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
            //重置状态
            threadLocal.remove();
        }
    }

    private static DataSource dataSource;

    static {
        Properties properties = new Properties();
        try {
            properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static DataSource getDataSource() {
        return dataSource;
    }


    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static Connection getConnection() throws SQLException {
        System.out.println(Thread.currentThread().getName());
        Connection connection = threadLocal.get();
        if (null == connection) {
            connection = dataSource.getConnection();
            threadLocal.set(connection);
        }
        System.out.println("JDBCUtils getConnection : " + connection);
        return connection;
    }
}

CloseConnectionFilter过滤器

  • 代码实现
public class CloseConnectionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //资源执行之后
            try {
                JDBCUtils.closeConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }
}
<filter>
    <filter-name>CloseConnectionFilter</filter-name>
    <filter-class>com.atguigu.filter.CloseConnectionFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>CloseConnectionFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值