尧图精选

JavaWeb购物平台开发实战:Servlet+JSP+JDBC全流程解析

🕒 发布时间:2026/9/13 16:31:46 📁 来源:尧图网络
简介基于IDEA的JavaWeb网络购物平台完整项目源码适合高校学生完成期末作业或毕业设计时参考覆盖登录注册、角色区分用户/商家/管理员、购物车、订单结算、收货地址管理、商家改价申请与管理员审核等核心电商流程。资源共1083个文件主要包含前端vue/ts/js/css页面逻辑、后端java/servlet/jsp业务实现以及mysql相关配置压缩包约26.06MB。项目采用典型JavaWeb分层结构并预置了热销商品首页和多种身份跳转逻辑可按角色快速理解权限控制与业务流转。已有1959人学习下载适合想快速搭建可运行购物平台并学习JavaWeb开发思路的同学。1. 从课程设计到可验收项目这套JavaWeb购物平台到底在做什么“IDEA基于javaweb的网络购物平台”这类标题几乎是每年计算机专业课程设计里的常客。但多数跑不起来的原因不在代码而在很多人把Servlet、JSP、Tomcat、IDEA的关系想拧了不装Tomcat就在IDEA里点运行明明配好了连接串却报ClassNotFound或者把商品循环写成了JSP里的Java脚本片段却查不出乱码。这个项目要解决的是一整套链路用IDEA作为开发工具以Servlet处理请求、以JSP渲染页面、以JDBC读写MySQL最终把一个带商品列表、购物车、订单流程的站点从零跑到能演示的状态。这条路径最大的价值在于它把JavaWeb底层的请求-响应模型摊开了适合课程设计、期末验收也适合那些用惯了Spring Boot、想回头弄清楚MVC原始面貌的工程师。2. 先把技术栈选清楚Servlet、JSP、JDBC和IDEA各自负责哪一层2.1 为什么课程设计锁死JavaWebSpring Boot反而帮不上忙很多例子写着“javaweb”但实际会引发争议Spring Boot不是也可以吗问题在于题目明确强调IDEA和JavaWeb通常对应的是Servlet规范的直接使用。Servlet是一个运行在容器里的Java类Tomcat收到HTTP请求后会按web.xml或注解找到对应的Servlet并把HttpServletRequest和HttpServletResponse交给它。JSP本质上会被容器编译成Servlet负责页面模板的产出。JDBC则负责让Java代码越过MySQL协议执行SQL语句并读取结果集。这一套组合下请求的路径非常清楚浏览器请求到达TomcatTomcat按映射规则把请求分发给ServletServlet调用DAO查询数据库再把结果放进request域中最后转发给JSP。JSP拿到数据后渲染成HTMLTomcat把响应回写给浏览器。IDEA在这里承担的是工程的载体创建Maven项目、管理依赖、部署war包到Tomcat。要注意IDEA只是编辑器加构建环境它自己不提供Servlet容器。所以“在IDEA里运行JavaWeb项目”这句话的本质其实是“IDEA调起Tomcat再把工程部署进去”。理解了这个关系后面配置Tomcat时就不会一头雾水。2.2 三层结构划分controller、service、dao应该做到什么程度在动手前要先把包结构定下来不然后面功能一多找文件会浪费大量时间。常见做法是拆成四层controller或叫servlet包——接收请求、解析参数、调用service、决定跳转到哪个JSP这一层不要写SQL也不要打印过多业务日志。service——放置业务规则下单时校验库存、计算订单总金额、把购物车中的商品变成订单明细这一层是事务的边界。dao——单纯的数据访问一个方法对应一条或一组SQL。entity——实体类对应user、product、order表。IDEA里建立Maven项目后src/main/java下按这个结构建包src/main/webapp下放JSP和静态资源。用WebServlet(路径)注解代替web.xml里的servlet配置能少写很多样板代码。课程设计阶段不需要强行套Service接口加实现类直接建一个ProductService类就够用但controller不能绕过service直接操作数据库否则事务和复用会变得很难受。这套划分虽然带点“老式”味道却恰好能回答“前端页面提交的表单怎么变成一条订单记录”这类问题。2.3 数据库表怎么建商品、用户、订单、订单项的最小字段购物平台至少要四张表user、product、orders、order_item。其中orders和order_item是一对多关系order_item通过order_id关联到订单主表。字段设计要克制只留演示和结算必须的列。一个常用的建表语句如下CREATE DATABASE IF NOT EXISTS shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, description VARCHAR(500), image_url VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;价格字段用DECIMAL(10,2)而不是DOUBLE是因为浮点数在结算累加时会出现精度误差订单总金额一旦带出0.30000000000000004这类值演示时会非常难看。utf8mb4远比utf8稳妥它能存储表情符号也不用担心中文变成问号。商品表和订单表都建立了外键这在课设层面能体现设计规范。但如果后端代码没有正确管理事务外键约束反而会在删除商品时阻塞订单表常见的做法是订单产生后不允许删除商品记录只在后台做上架下架。3. 在IDEA里搭起JavaWeb工程从创建Maven项目到Tomcat带着war跑起来3.1 用IDEA安装和社区版判断哪个版本能建JavaWeb项目IDEA社区版是免费的对Maven和Servlet开发本身没有任何限制。但要注意的是社区版对JavaEE的支持和旗舰版有差异新建项目时不带有“Java Enterprise”那种开箱即用的模板需要用手动建Maven项目再加依赖的方式来落地。具体路径是打开IDEA依次选择New Project - Maven在GroupId填com.shopArtifactId填shop-web然后建完后再去Project Structure - Facets里添加Web支持并在src/main/webapp下创建WEB-INF/web.xml。IDEA社区版默认不带Tomcat集成需要安装Smart Tomcat这类插件来辅助运行。这是社区版和旗舰版在JavaWeb场景下最大的差别。如果你在IDEA安装时选了捆绑的JDK也要确认版本。Tomcat的Servlet版本和JDK版本要保持兼容Tomcat 9配JDK 8或JDK 11是没有问题的但Tomcat 10.1以上的默认使用jakarta.*命名空间传统javax.servlet包名会直接编译不过。课程设计最常见、最省心的组合是JDK 8加Tomcat 9代码里用javax.servlet这能避开命名空间迁移带来的各种莫名其妙报错。3.2 加入Servlet、JSP、JSTL、MySQL驱动的pom.xml配置Maven项目的好处是把依赖管理工作交给IDEA你只需要在pom.xml里声明坐标。下面是一份能直接编译的最小配置注意项目打包方式是warproject modelVersion4.0.0/modelVersion groupIdcom.shop/groupId artifactIdshop-web/artifactId version1.0-SNAPSHOT/version packagingwar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjsp-api/artifactId version2.0/version scopeprovided/scope /dependency dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version scoperuntime/scope /dependency /dependencies /projectjavax.servlet-api和jsp-api加上provided作用域是因为这两个包在Tomcat的lib目录里已经存在如果打成war再带上会和容器自带的类冲突部署后可能出现方法签名不一致等问题。mysql-connector-java使用runtime作用域避免在编码时直接引用驱动内部类。版本5.1.49对应的是MySQL 5.x的驱动类名是com.mysql.jdbc.Driver如果你本机装的是MySQL 8.x建议换成com.mysql.cj.jdbc.Driver并且URL里要带时区参数。这个细节是JavaWeb项目最常见的运行时报错来源。3.3 JavaWeb相关的JDBC连接配置参数和易错点在src/main/resources下建一个db.properties把数据库连接信息集中管理。这样Servlet代码里只需要用Properties加载文件而不用在每个DAO里硬编码用户名密码。下面这份配置里的参数都是实测后能稳定运行的jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password123456characterEncodingutf8配合建表时的utf8mb4可以覆盖绝大多数中文乱码场景。useSSLfalse则是关闭MySQL的SSL握手警告MySQL 5.7及以上版本不关这个参数虽然也能连上但控制台会打出一堆警告日志影响排错时的判断。IDEA里如果你的工程使用MySQL 8.x驱动URL还需要追加serverTimezoneAsia/Shanghai否则驱动默认时区和本地时区不一致时会在执行时间类型查询时抛异常。写完这个文件后可以在DAO里写一个静态工具类负责读取配置、创建连接。这里建议把获取连接的方式统一在一个DBUtil里后续换连接池时只需要改这一个类。4. 商品列表到下单结算核心功能的请求流向和代码实现4.1 商品列表页怎么从数据库取数据再渲染到JSP商品列表是购物平台的入口页也是接触Servlet和JSP协作的最短路径。逻辑看起来很简单查product表所有记录放在request域中转发到productList.jsp。但要注意查询出的每一行都要转成Product对象JSP里才能用EL表达式读取属性。下面这段DAO代码展示了这个步骤的开头public ListProduct listAll() { ListProduct list new ArrayList(); String sql SELECT id, name, price, stock, description, image_url FROM product ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); p.setDescription(rs.getString(description)); p.setImageUrl(rs.getString(image_url)); list.add(p); } } catch (SQLException e) { throw new RuntimeException(商品查询失败, e); } return list; }这里用了try-with-resources三个资源都会在方法结束时自动关闭不用写finally块。executeQuery()执行完后返回ResultSet但结果集依赖于它所属的Statement一旦连接关闭结果集里的数据就不能再访问了。所以要在while循环里把每一列的值取出并填充到实体对象中不能把ResultSet传出去再读取。PreparedStatement用占位符代替字符串拼接既能防止SQL注入也能让MySQL复用预编译的执行计划比直接拼接SQL要稳得多。Servlet端把ListProduct放入req属性再转发到JSPWebServlet(/product/list) public class ProductListServlet extends HttpServlet { private ProductService productService new ProductService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setAttribute(productList, productService.listAll()); req.getRequestDispatcher(/WEB-INF/jsp/productList.jsp).forward(req, resp); } }JSP页面里用JSTL的c:forEach遍历这个list% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth商品名/thth价格/thth库存/thth操作/th/tr c:forEach items${productList} varp tr td${p.name}/td td${p.price}/td td${p.stock}/td tda hrefcart?actionaddproductId${p.id}加入购物车/a/td /tr /c:forEach /table${p.name}之所以能取到值是因为Product类必须有对应的getName()方法。EL表达式不是直接访问字段而是按照JavaBeans规范调用getter如果实体类里把属性写成下划线命名或者没有getter页面会出现空值。JSP放在WEB-INF下有个好处客户端不能通过URL直接访问该目录内的文件只能由Servlet转发进去这在演示时能避免别人绕过流程直接打开没有数据的页面。4.2 购物车放在Session里还是数据库里两种方案的取舍购物车设计是个值得纠结的点。如果放入数据库需要额外的cart表每次加购都要写一次库课设演示时操作路径虽然更“真实”但代码量和数据库表都会多不少。如果放入Session购物车数据跟着用户会话走用户关闭浏览器后数据自然消失这符合一般电商“游客也能加购、登录后结算”的常见做法而且是纯内存操作速度更快。课程设计阶段我一般推荐把购物车存在Session里把数据库的读写留给订单和订单明细。购物车的数据结构可以用MapInteger, CartItemkey是商品idvalue是包含商品信息和购买数量的对象。每次加购时先判断Map中是否已有该商品有则数量加1没有则新增一个条目。这个Map放到Session的attribute里。关键代码可以这样写public synchronized void addToCart(HttpServletRequest req, int productId) { HttpSession session req.getSession(); MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } CartItem item cart.get(productId); if (item null) { item new CartItem(); item.setProductId(productId); item.setQuantity(1); cart.put(productId, item); } else { item.setQuantity(item.getQuantity() 1); } }HttpSession在Servlet中是线程安全的容器但对其中存的HashMap并发修改仍然需要保护。虽然一个浏览器对应一个Session在演示环境下并发量极低但方法加上synchronized能避免用户快速双击“加入购物车”按钮时两个线程同时读到同一份购物车副本导致数量只加了1而不是2。这个细节在验收时经常被忽略但它体现的是对并发下数据一致性的基本意识。加购之后商品的数量和价格还需要在结算页重新回显不要依赖放进购物车时计算的价格。因为商品价格可能在用户浏览过程中被后台修改过下单时必须以数据库的实时价格为准。4.3 下单链路一个事务里同时扣库存、生成订单、清空购物车下单是把整个流程串起来的关键动作。用户在购物车页面点击“提交订单”请求到达OrderServlet。此时要做三步操作生成订单主记录、把购物车中每件商品变成order_item明细、扣减product表的库存。这三步必须在同一个数据库连接里完成否则会出现“订单生成了但库存没扣”的不一致状态。下面给出service层中处理下单事务的骨架WebServlet(/order/create) public class OrderCreateServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { resp.sendRedirect(login.jsp); return; } MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null || cart.isEmpty()) { resp.sendRedirect(cart.jsp); return; } OrderService orderService new OrderService(); try { String orderNo orderService.createOrder(user.getId(), cart); session.removeAttribute(cart); resp.sendRedirect(order/success.jsp?orderNo orderNo); } catch (Exception e) { req.setAttribute(errorMsg, e.getMessage()); req.getRequestDispatcher(/WEB-INF/jsp/cart.jsp).forward(req, resp); } } }这个Servlet没有直接写业务逻辑而是把cart传给OrderService。service内部需要控制事务比较直接的做法是让DAO层接受外部传入的Connection对象而不自己获取连接。下面用一个方法来体现这个模式public String createOrder(int userId, MapInteger, CartItem cart) { String orderNo SO System.currentTimeMillis(); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); OrderDAO orderDAO new OrderDAO(); int orderId orderDAO.insertOrder(conn, orderNo, userId); OrderItemDAO itemDAO new OrderItemDAO(); ProductDAO productDAO new ProductDAO(); BigDecimal total BigDecimal.ZERO; for (CartItem item : cart.values()) { Product product productDAO.getById(conn, item.getProductId()); if (product.getStock() item.getQuantity()) { throw new RuntimeException(商品[ product.getName() ]库存不足); } productDAO.deductStock(conn, item.getProductId(), item.getQuantity()); itemDAO.insertItem(conn, orderId, item.getProductId(), product.getPrice(), item.getQuantity()); total total.add(product.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } orderDAO.updateTotalAmount(conn, orderId, total); conn.commit(); return orderNo; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } throw new RuntimeException(下单失败 e.getMessage(), e); } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }setAutoCommit(false)之后所有SQL都在同一个事务中执行直到commit才会真正写入数据库。任何一步抛出异常rollback会让之前的操作全部回滚。这里把Product的库存检查放在扣减之前虽然不能完全解决高并发下的超卖问题但至少能防止常规的“库存不够仍然生成订单”。事务结束的finally里把连接重新置为自动提交再关闭是很多代码会漏掉的细节。如果不在关闭前恢复autoCommit连接回到连接池后下一个使用者会继承false状态导致后续所有操作都不落库这是排查线上问题的经典场景。5. 排错和收尾把JavaWeb项目从“能跑”调到“能演示”5.1 三个高频问题按现象直接定位页面404或启动报错先看Tomcat控制台有没有“Unable to compile class for JSP”之类的提示。多数原因是JSP页面里用了Tomcat版本不支持的标签库比如把JSTL 1.0的uri写错。确认WEB-INF/lib下有没有jstl.jar和standard.jarIDEA里部署时要观察Artifacts输出目录是否带上了这两个包。ClassNotFoundException: com.mysql.jdbc.Driver这个问题在IDEA里最容易迷惑人。pom.xml已经加了依赖但Tomcat运行时不使用IDEA的classpath而是使用WEB-INF/lib下的jar。在pom中把mysql-connector-java的scope从runtime改为compile然后重新构建war确保lib里有驱动。JSP页面显示${product.name}原样字符串说明EL表达式被当成纯文本了。在页面上方加% page isELIgnoredfalse %可以强制开启EL解析。常见的原因是web.xml头声明用的是Servlet 2.3或更老版本那里的EL默认关闭需要把web.xml的schema版本改成3.1以上。5.2 给下单入口补一层幂等控制页面上的“提交订单”按钮如果被双击两次会生成两笔订单。防御办法是在点击按钮时用JavaScript禁用按钮同时在后端用Session令牌做一次校验。具体做法是进入结算页时生成一个randomUUID放入Session把同一个值也放进表单隐藏域提交时比较两者是否一致一旦匹配立即清除Session中的令牌。这样一个表单只能成功提交一次。5.3 演示前的最后几步启动前重置数据库中的演示数据插入不少于8件商品价格不要全是整数带上小数位截图和演示时观感更像真实平台。用Chrome的无痕窗口走一遍“首页-加入购物车-修改数量-下单-查看订单”的完整链路同时打开MySQL命令行日志确认下单时确实执行了插入和更新语句。顺手在地址栏直接访问加购接口cart?actionaddproductId1确认未登录状态下也能加购而不是抛NullPointer这样能提前暴露NullPointerException并验证Session中购物车初始化的边界情况。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →