ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JavaWeb购物商城项目拆解:从Servlet到动态代理事务管理

2026/10/7 4:34:36 拓冰建站 浏览量
JavaWeb购物商城项目拆解:从Servlet到动态代理事务管理 简介一套基于MVC设计模式与动态代理模式实现的JavaWeb购物商城完整源码项目包含前端商城与后台管理两大部分面向刚掌握JavaWeb基础、希望以完整项目串联所学知识的开发者可有效解决学完不知如何应用的常见问题。压缩包共613个文件大小16.85MB主要构成是Java源码、JSP页面、CSS/JS前端样式与脚本、SQL数据库脚本以及运行所需的JAR依赖和商品图片素材文件类型丰富、目录结构清晰便于直接导入开发工具对照学习。目前已有21951人浏览学习在同类JavaWeb练手项目中具有一定热度适合用于课程设计或毕业设计参考。项目功能覆盖主页热销展示、商品搜索与详情、商品评价评分、库存校验、立即购买、购物车增减与删除、确认订单、防重复提交、地址选择与新增以及后台会员管理、商品批量上下架、库存维护和订单发货等完整电商链路对照源码可以学习MVC分层、动态代理、参数校验和数据库操作的落地写法是边读边练的优质素材。1. JavaWeb购物商城MVC动态代理把基础语法变成完整项目一个JavaWeb购物商城项目听起来像课程设计标配但它背后覆盖的Servlet、JSP、JDBC、MVC分层、动态代理这些内容几乎就是JavaWeb从入门到能干活的全过程。这个项目自带MySQL数据库脚本前台展示、搜索、购物车、下单、后台发货管理是一条完整业务链路。我拆过不少JavaWeb源码这份的好处是不依赖SSM框架只用原生Servlet和JDBC就完成一个真实商城适合学完JavaWeb基础但不知道怎么组织项目的阶段也适合拿去改造成课设或简历项目。下面的拆解会让新手能跟着跑起来也会让熟手直接看到几个容易翻车的位置。2. 项目结构先看懂MVC三层、数据库表和动态代理事务2.1 六张核心表购物商城的字段怎么设计这个项目把功能描述集中在业务上但真正支撑业务的是MySQL里那几张表。按购物商城的常规设计这套源码至少包含六张核心表用户表t_user、商品表t_product、地址表t_address、购物车表t_cart、订单表t_order、订单明细表t_orderitem。我在拆项目时习惯先看SQL脚本因为表的字段设计直接决定代码怎么写。下表是这套源码里核心表的字段作用字段名可能因版本有出入但业务含义一致表名核心字段承担的业务t_useruid、username、password、status登录、会员启用/禁用t_productpid、pname、price、stock、is_hot、pflag商品展示、搜索、库存校验t_addressaid、uid、address、phone、receiver确认订单页的地址选择与新增t_cartcid、uid、pid、count购物车数量维护t_orderoid、uid、aid、total_price、ordertime、status订单生成与状态流转t_orderitemitemid、oid、pid、count、subtotal订单明细保证历史订单金额不被商品改价影响这里最容易混淆的是status字段的双重含义。在t_user表里status表示账户状态1为启用、0为禁用在t_order表里status表示订单状态常见划分是1未发货、2已发货、3已删除。pflag则是商品上下架标识1上架、0下架。同一个0和1在不同表里代表完全不同的业务含义这是读源码时最容易看晕的地方我见过有人把删除订单的SQL直接写成delete from t_order其实正规做法是更新status为3做逻辑删除。表之间的关系也值得理一遍。t_cart通过uid关联t_user、通过pid关联t_productt_order通过uid关联用户、通过aid关联地址t_orderitem通过oid关联订单、通过pid关联商品。订单明细表的存在是为了保存下单那一刻的商品快照价格、数量都冗余在明细里这样商品改价不会影响历史订单的金额计算。画清楚这几条关系前台链路在代码里走一遍就不会迷路。2.2 MVC分层Servlet、service、dao各自的边界这个项目的包结构基本是MVC教科书式划分controller层放Servlet负责接收请求、解析参数、转发或重定向service层放业务逻辑比如计算订单金额、校验库存dao层放JDBC访问代码只负责SQL和结果集封装domain或entity包放JavaBeanutils包放JDBCUtils、代理工厂这类通用工具。判断一个分层是否合理我常用的标准是看service层有没有被DAO代码污染。如果service里出现Connection、PreparedStatement说明事务边界没有收拢。这个项目用了动态代理来处理事务所以service实现类里基本不会出现连接管理代码事务统一交给代理层。2.3 动态代理统一事务一段代码省掉所有提交回滚这是摘要里点名的一个技术点也是这个项目值得看懂的地方。常见做法是service实现类都实现同名接口Servlet从代理工厂拿到的不是实现类对象而是实现类的代理对象。代理对象拦截每个业务方法在方法执行前关掉连接自动提交、在方法正常结束后提交、在方法抛异常时回滚。public class ServiceProxyFactory { public static Object getService(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) - { Connection conn JDBCUtils.getConnection(); try { conn.setAutoCommit(false); Object result method.invoke(target, args); conn.commit(); return result; } catch (Exception e) { conn.rollback(); throw new RuntimeException(业务执行失败事务已回滚, e); } finally { JDBCUtils.close(conn); } } ); } }这段代码有三个参数要知道。第一个target.getClass().getClassLoader()是类加载器JDK动态代理要靠它生成代理类字节码第二个getInterfaces()是接口数组代理对象只能转换成接口类型使用所以service实现类必须写接口否则这一步直接抛ClassCastException第三个lambda是InvocationHandler的invoke方法代理对象每次调用接口方法都会先进这个方法。方法体里setAutoCommit(false)是事务生效的前提如果漏掉这行MySQL默认每执行一条SQL就自动提交下单时扣库存和插订单明细就会出现扣了库存但订单没生成的情况。动态代理解决了每个业务方法都写一遍开关事务的重复代码但要注意它管理的是JDBC事务不是MySQL的事务隔离级别。这两层是独立的概念代理层控制提交和回滚MySQL的隔离级别由数据库配置决定。能在简历项目里把这两个概念分开讲清楚面试官基本会认定你是真做过而不是背了几道八股。3. 前台购物链路拆解从搜索商品到提交订单的完整流程3.1 商品列表与搜索分页SQL和like参数是两处细节前台入口是这三块首页热销商品、所有商品展示、商品搜索。它们的SQL本质上是同一个查询的不同条件组合热销是is_hot1商品列表是pflag1只显示上架商品搜索是在pflag1的基础上加pname like %关键字%。我用一段典型的dao层查询来拆这个逻辑public ListProduct findProductByPage(int currentPage, int pageSize, String keyword) { String sql select * from t_product where pflag 1 ; ListObject params new ArrayList(); int offset (currentPage - 1) * pageSize; if (keyword ! null !.equals(keyword.trim())) { sql and pname like ? limit ?, ?; params.add(% keyword.trim() %); params.add(offset); params.add(pageSize); } else { sql limit ?, ?; params.add(offset); params.add(pageSize); } return queryList(sql, params); }这里有两个高频翻车点。第一个是limit的起始索引MySQL的limit第一个参数是偏移量第二个是条数offset必须等于(currentPage - 1) * pageSize写反了会出现第一页数据重复写漏了第二页会直接报SQL语法错误。第二个是like的参数不能直接传keyword要拼成%关键字%否则模糊查询退化成精确匹配搜手只能命中一个字搜不到手机。我一般建议点击分页按钮时把当前页和关键字一起回传不然翻到第二页搜索条件就丢了。这一步在三层架构里的数据流向是JSP页面的表单或链接携带keyword参数到ProductServletServlet从request中拿参数、做非空校验再调service层最终落到dao层这个方法。结果集返回后servlet把list和pageBean放进request域转发到product_list.jsp渲染。这里要提醒一件事转发用request.getRequestDispatcher().forward()重定向用sendRedirect()带数据到页面必须用forward用重定向request里的list会丢。3.2 购物车数量变更前端手输与后端校验的配合摘要描述里专门提到可增减购买商品数量亦可手动输入同时验证库存对应到实现上是购物车页面的加减按钮和input输入框各自触发一个请求后端在更新数量前先查一次商品表的最新库存。常见做法是前端把商品id和最新数量一起提交到购物车servletpublic void updateCount(HttpServletRequest request, HttpServletResponse response) { int cid Integer.parseInt(request.getParameter(cid)); int pid Integer.parseInt(request.getParameter(pid)); int count Integer.parseInt(request.getParameter(count)); Product product productDao.findById(pid); if (product.getStock() count) { response.getWriter().write(stock_not_enough); return; } cartDao.updateCount(cid, count); response.getWriter().write(ok); }这段代码的关键在提交之后再查一次库存而不是信任购物车页面上显示的数字。原因很简单用户把页面开着不动库存可能已经被别人买走或者用户直接改了前端input的value绕过加减按钮直接提交一个999如果后端不查库存下单阶段就会出问题。前端部分牵涉到离开购物车后的几个跳转选择了多个商品后点结算servlet要接收选中的购物车条目id列表常见做法是checkbox的name值相同value是cid后面用request.getParameterValues(cid)拿到数组注意这个方法拿不到值时返回null要先判空再遍历。3.3 提交订单防重复提交与库存校验的双重防护下单是整个项目业务复杂度最高的位置因为这一串操作里至少有四件要一起完成的事校验库存、插入订单主表、插入订单明细、扣减商品库存。四件事必须在一个事务里任何一步失败都要回滚。动态代理在这里的价值就体现出来了只要订单service方法内部完成这四步事务边界由代理统一管理不需要手动加回滚逻辑。防重复提交是这个功能点里的经典考点。实际场景通常是用户连点了两次提交订单按钮或者提交后网络慢用户又刷新了一次。解决方式常用的是session token方案String token (String) request.getSession().getAttribute(orderToken); String formToken request.getParameter(orderToken); if (token null || !token.equals(formToken)) { request.setAttribute(msg, 订单已提交请不要重复操作); request.getRequestDispatcher(/order_result.jsp).forward(request, response); return; } // token校验通过后立即移除保证一次token只能用一次 request.getSession().removeAttribute(orderToken);这个方案的约束条件有两个确认订单页生成token时要同时放进session和表单隐藏域校验通过后必须立即remove否则用户刷新后session里还是旧token第二次提交还能通过。订单提交成功后页面要展示订单号、总金额、收货信息这些可以放在订单对象里带到结果页。库存不足和商品下架的处理则在订单service里如果库存不足直接抛出带提示信息的异常代理捕获后回滚前台页面用try-catch或全局异常处理把提示显示出来。要特别说的是这里校验的不只是库存数字。商品下架pflag变为0时也要在订单service里做一次状态检查否则后台把商品下架了用户从长期打开的购物车页面还能继续下单。摘要里写的库存不足或商品下架给予响应就是这个意思它是两个独立的校验分支不能合并成一句SQL。4. 后台管理模块会员、商品、订单三个入口的状态设计4.1 会员管理启用禁用账户与密码修改的实现方式后台会员管理的核心操作是启用/禁用账户、修改密码。启用禁用对应t_user表的status字段1为启用、0为禁用。这个开关生效体现在登录service里用户输入用户名和密码校验通过后还要检查status是否为1为0则提示账户已被禁用。修改密码的常见做法是管理员输入新密码servlet接收后先做一次MD5加密再更新数据库public void resetPassword(HttpServletRequest request, HttpServletResponse response) { int uid Integer.parseInt(request.getParameter(uid)); String newPwd request.getParameter(newPwd); String encrypted MD5Utils.md5(newPwd); userDao.updatePassword(uid, encrypted); response.sendRedirect(admin/user_list.jsp); }这里有个细节要注意加密只能做一次不能在多个地方重复加密导致密码变成二次MD5。有些新手会把MD5结果明文存在数据库里稍好一点的加盐但这个项目定位是练手级MD5加密一次是够用的如果能顺手加个固定盐值就更稳。真正要强调的是一致性——管理员重置密码后用户用原密码登录会失败这是预期行为但如果项目里同时存在注册功能注册时的密码加密规则必须和这里完全一致否则注册后永远登不进去。4.2 商品管理批量添加、上下架与库存维护的实现思路商品管理模块常见的功能是商品批量添加、上下架、库存维护。批量添加的典型做法是servlet接收多个商品参数循环调用dao的insert方法或用批量SQL一次插入多条。上下架则是对应t_product表的pflag字段上架置1、下架置0。库存维护的常见实现是提供一个表单让管理员直接修改库存数量但更稳妥的做法是记录增加/减少操作而不是直接覆盖数值。直接覆盖的风险在于管理员如果不小心清空了输入框再提交库存会变成0或null。我倾向于把库存调整做成一个增量字段servlet里用当前库存加上调整值并校验调整后的结果不能为负数。商品管理会直接影响前台三个地方商品下架后列表页不再显示详情页直接访问要提示商品不存在购物车里已下架的商品在结算时要提示。这三个位置的联调是测试商品模块是否完整的重要指标只改了列表页的查询条件、不管购物车和详情页就会出现前台列表看不到但购物车还能下单的矛盾状态。4.3 订单管理发货与删除的状态流转约束订单管理主要是发货和删除两个操作对应t_order表的status字段。发货是把status从1改成2删除是把status改成3做逻辑删除。public void shipOrder(HttpServletRequest request, HttpServletResponse response) { int oid Integer.parseInt(request.getParameter(oid)); Order order orderDao.findById(oid); if (order null || order.getStatus() ! 1) { request.setAttribute(msg, 订单状态已变化无法发货); request.getRequestDispatcher(/admin/order_list.jsp).forward(request, response); return; } orderDao.updateStatus(oid, 2); response.sendRedirect(admin/order_list?flagnotShipped); }这段代码的约束顺序是有讲究的先查订单、再判断状态、再更新。如果不查订单直接执行update容易出现重复发货的记录错乱也拿不到旧状态去做业务判断。删除订单同理要先判断status是否等于2已发货已发货的订单理论上要先取消发货才能删除或者直接禁止删除已发货订单这个约束由业务规则决定。后台三个管理模块加起来其实是同一件事围绕status字段做状态机。用户表一个status商品表一个pflag订单表一个status每一处状态变更都要校验前置状态。理解了这个后台模块的代码读起来会快很多。5. 避坑指南Idea运行配置、MySQL连接与乱码排查运行JavaWeb项目时最容易让人烦躁的不是业务代码而是环境配置。下面这几条都是我在用IDEA跑这类项目时真实遇到过的按现象→原因→解决写清楚。5.1 现象一Tomcat启动成功但访问404现象IDEA里Tomcat显示启动成功浏览器访问项目路径却404甚至访问Tomcat首页也404。原因最常见的是部署描述里Application context配置不对。IDEA的Tomcat配置中Deployment选项卡里Application context填的值决定你访问项目时要带的路径。默认填/项目名很多人填成了全限定路径或者写错大小写就找不到资源。解决在IDEA的Run Configuration里找到Tomcat Server切到Deployment页确认Application context写的是/你的项目名并且Deployment里已经添加了带exploded的artifact。访问地址就是http://localhost:8080/项目名/首页。如果访问后是Tomcat的默认页面而不是项目页面说明项目根本没部署成功回到Deployment选项卡重新点一下加号选择artifact。5.2 现象二MySQL 8.x连接报Public Key Retrieval错误现象项目启动后第一次访问数据库相关页面控制台抛Public Key Retrieval is not allowed页面直接500。原因MySQL 8.0版本默认使用caching_sha2_password认证插件客户端连接时需要先获取服务器的公钥JDBC驱动默认不允许自动获取。解决JDBC连接URL里加上allowPublicKeyRetrievaltrueuseSSLfalse同时确认驱动版本和数据库版本匹配。我一般建议直接用mysql-connector-java 8.0.x不要用旧版5.x驱动连8.x库会出现认证插件不支持的错误。另外如果你的MySQL是解压版通过命令行安装的先确认服务真的启动了net start mysql不行就检查data目录是不是初始化过。5.3 现象三页面中文全部变成问号现象JSP页面显示正常但从数据库读出来的中文、表单提交的中文全变成问号。原因乱码涉及三个环节——数据库连接URL缺characterEncodingutf8、JSP页面没设置pageEncoding、Tomcat接收POST请求时没有统一编码过滤。三个环节任何一个漏掉中文就会在链路上某一段变成问号。解决在JDBC的URL里追加characterEncodingutf8JSP头部设置% page contentTypetext/html;charsetUTF-8 %在web.xml里配置一个CharacterEncodingFilter强制把request和response都设置成UTF-8。配置完这三处后重启Tomcat如果还是乱码检查MySQL数据库和表的字符集命令行执行show variables like character%确认不是数据库侧用了latin1。5.4 现象四动态代理报ClassCastException现象调用ServiceFactory.getService()后把返回值强转成实现类运行时报ClassCastException。原因JDK动态代理生成的代理类只实现了传入的接口没有继承原始实现类。把代理对象强转成实现类类型当然报错只能转成接口类型。解决所有使用代理对象的地方声明类型都用接口。比如UserService userService (UserService) ServiceProxyFactory.getService(new UserServiceImpl());绝不能写成UserServiceImpl userService ...。这是一个很隐蔽的坑因为编译期不报错运行到首次调用才会炸排查时先看强转的类型是不是接口。5.5 现象五下单成功却查不到订单记录现象页面提示下单成功跳转到了成功页但数据库里t_order表没有新记录或者有订单但t_product的库存没扣。原因事务没有真正开启或者Connection被多个线程共用。常见于没有走动态代理、直接new了service实现类调用方法导致dao层的每条SQL独立执行insert成功但后续扣库存失败时没有回滚。解决确认Servlet里获取的是代理对象而不是原始对象。这是见真章的地方——这个项目强调动态代理就是为了让下单这种多步操作在一个事务里完成。反复测试的方法很简单在下单service里故意抛一个异常如果库存和订单都能查回原值说明事务生效如果出现脏数据说明事务没包住。6. 上线前的验证用五个备查项确认购物商城真的跑通了项目能启动不等于功能是对的。我会用五个备查项把这套JavaWeb购物商城过一遍每个都是黑匣子级别的高频业务路径跑通就能放心去演示或写进简历。第一个备查项是前台链路登录账号首页看到热销商品搜索手机能出结果进详情页加购两件购物车改成三件结算新增一个地址提交订单在我的订单里看到这条订单。中间任何一个环节断掉优先查对应servlet的转发路径和JSP里表单的action地址。第二个备查项是库存边界把商品库存故意改成1购物车添加2件提交订单时应该被拦截并提示库存不足再把库存改成1连续对同一商品下两单第二单应该失败或提示库存不足。这两个场景验证的就是第3.3节说的双防护逻辑。第三个备查项是防重复提交在确认订单页面连续快速点击两次提交按钮第二次应该被session token拦截。这个测试我在演示前一定会做因为现场网络一旦慢一拍评委或同事大概率会习惯性多点一次。第四个备查项是后台状态变化对前台的影响后台把商品下架前台的列表页、详情页、购物车结算三个位置都应该有响应。后台给订单发货后用户端订单状态应该从未发货变已发货。这两组联动验证的是状态字段在前后台的传递是否完整。第五个备查项是数据一致性下单后打开MySQL命令行执行select * from t_order where oid订单号再执行select * from t_orderitem where oid订单号确认主表和明细都在再查一次t_product的库存和页面显示的剩余库存一致。mysql数据库常用命令里select和update是最高频的两条排查数据对不对基本就靠它们。从那以后我每次接手这类JavaWeb源码都会先在数据库里做一次下单前库存—下单后库存—订单明细的三连查确认事务是闭环的再继续改代码。这套验证习惯帮我挡掉了不少演示现场的尴尬希望帮到你。本文还有配套的精品资源点击获取