简介:一份基于JSP+Access的网上购物系统设计与实现源码及配套文档,面向计算机相关专业学生与初级Java Web开发者,常被用于课程设计、毕业设计或BS架构项目练手。压缩包大小约1.9MB,内含完整可运行的源码与配套设计文档,文件以JSP页面、Java源文件、Access数据库及Word文档为主,源码经过测试校正可百分百成功运行,文档包含需求分析、数据库设计及系统测试说明,便于对照学习。已有290人浏览学习,运行环境配置简单,适合快速启动项目。系统围绕用户注册登录、商品分类浏览、购物车管理、订单提交与后台管理等典型模块展开,结构清晰、注释易懂,既能帮助理解网上购物系统的核心业务流程,也可直接在此基础上扩展二次开发,或参考文档完成论文与设计报告的撰写。
1. 老牌JSP+Access购物系统:为什么课程设计还在用这套技术栈,拿到手又该从哪下手
收到一个名为《基于JSP+access的网上购物系统设计与实现(源码+文档).zip》的压缩包,很多人第一反应是“这技术是不是过时了”。但真实情况是,这套组合在高校课程设计和毕业设计里仍然占据重要位置:JSP负责出页面,Access管数据,一台装好Office的Windows电脑就能跑demo,不需要单独装MySQL服务。它适合三类人——想快速交一门Java Web课设的学生、需要一份能讲明白的毕设项目的人、给这类项目做二次开发辅导的从业者。
这套项目的价值不在技术先进,而在于“教学完整”。用户注册登录、商品浏览、购物车、下单结算、后台商品管理、订单管理,整套流程麻雀虽小五脏俱全,配套的源码和文档还能直接支撑开题报告和答辩PPT。但拿到手不是解压就能跑,它有一堆环境配置和经典坑要趟。这篇文章会让你知道这套系统怎么拆、怎么跑、怎么改,以及最容易翻车的地方。
2. 先拆篮子:JSP+Access的选型逻辑与数据表设计,数据库只有6张表也能跑通下单
2.1 JSP+Access能延续到今天的三个理由:演示友好、零服务成本、踩坑面窄
十年前电商系统用JSP+Access很普遍,今天用这套方案的人更多是看中它的“轻”。第一,Access是Office自带组件,Windows环境装好Office就有,不需要单独安装数据库服务端,对实验室和答辩现场更友好。第二,JSP是一种在HTML中嵌Java的服务端页面技术,Tomcat直接编译执行,和经典Java EE课程的知识点贴合紧密。第三,它的表结构天然简单,商品、用户、订单三组关系清晰,适合初学者画E-R图。
很多人纠结为什么不用MySQL,这要看场景。如果这个项目是课程设计,数据库设计文档里写着“Access”就能过关,任意换库反而要重写连接层和SQL语句。我把话放这儿:如果目标是学会Java Web的MVC流程,Access还是MySQL差别不大,差距出现在JDBC驱动和工作量的取舍上。UCanAccess驱动在访问效率和兼容性上已经能胜任中小规模的演示,没必要为了炫技把整个项目拱手改成MySQL。
2.2 数据库表怎么拆:用户、商品、订单三个主流程,最少6张表
一个标准JSP+Access网上购物系统,数据库通常不会少于以下6张表。我会按模块拆开,方便你画表和写数据字典。
用户表(users)是最基础的一张,常见字段包括:user_id(自增主键)、username(登录名,唯一)、password(密码,一般存MD5值或明文,这是历史遗留问题也是埋雷点)、real_name(真实姓名)、email、phone、reg_time(注册时间)。还要有一个role字段,区分普通用户和管理员,这样才能让后台入口和前台首页共用一个登录页。
商品分类表(category)和商品表(products)是一对多关系。products表的关键字段是:product_id、category_id、name、price(货币类型)、stock(库存)、picture(图片路径或二进制字段)、description(描述文本)。这里我要提醒一句,Access里的“货币类型”和Java的BigDecimal对接时容易出现精度问题,建议用数字双精度类型,两条都试一下再定。
订单主表(orders)和订单明细表(order_items)承担交易流程。orders字段有order_id、user_id、order_time、total_price、status(订单状态,例如待发货/已发货/已完成)。order_items字段有item_id、order_id、product_id、quantity、price。这里的设计逻辑是,下单时把商品名称和单价冗余到明细表里,防止商品价格以后调整影响历史订单展示。
购物车表(cart)是给没登录也能逛的设计用的,字段包含cart_id、user_id、product_id、quantity、add_time。如果系统要求先登录再点击购买,购物车表可有可无,很多简版系统直接把它跳过,用Session临时存购物车数据。但这份源码既然是“完整版”,通常会保留cart表,表结构干净利落。
2.3 前后台权限分流:一套页面、两套逻辑的常见做法
JSP网站里最常见的就是在Servlet里从Session取用户,再判断role。如果是1,跳转后台管理;如果是0,跳转个人中心。这样做的好处是权限校验集中在统一入口,坏处是开发容易漏掉页面级权限,直接输入后台URL的人也能看到后台页面。
我一般会在三个地方做校验:一是LoginServlet里登录时把user对象塞进session;二是后台管理的JSP页面顶部加一个抽角色判断的include标签;三是在OrderServlet等核心请求入口再校验一次是否登录。在学校答辩时,老师最爱问“我不登录能不能直接访问后台管理”,如果只靠前端按钮隐藏,那这一问就翻车了。
3. 照着重现:从零跑通项目的最小命令集,不装MySQL也能启动
3.1 环境准备:JDK+Tomcat+Access三件套,版本选错直接挂
拿到源码包第一件事是配环境。JDK建议用1.8,虽然Oracle已经停止免费更新,但Tomcat和旧项目Servlet API兼容性最好。Tomcat建议8.5或9.0,不要用Tomcat 10,因为Tomcat 10把包名从javax.servlet换成了jakarta.servlet,老项目的import全都会报错。
Access这边,用Access 2016或2019都可以,只要系统里装过Office并带Access引擎。如果你只是运行程序,不打开数据库文件改表结构,装一个“Access Database Engine 2016 Redistributable”就够了,它不需要完整Office。
如果你用的是64位Windows,那有一个坑要先知道:老教程里用的JDBC-ODBC桥在JRE 8之后已经被移除,Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")这行代码会直接报ClassNotFoundException。替代方案是用UCanAccess驱动,这是目前跑Access最稳的JDBC驱动。连接URL长这样:
// 使用UCanAccess驱动连接Access数据库,参数格式固定 Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); String url = "jdbc:ucanaccess://D:/shop/shop.mdb;ucanaccessHome=lib;showSchema=false"; Connection conn = DriverManager.getConnection(url, "", "");这段代码的要点在url参数:
- D:/shop/shop.mdb是Access数据库文件的绝对路径,注意路径不要带中文和空格;
- ucanaccessHome=lib用来指定UCanAccess内部依赖包的相对路径,如果直接把所有jar放在web项目的WEB-INF/lib,这个参数可以省略;
- showSchema=false表示不显示系统表,防止查询结果里冒出系统内部表。
驱动依赖需要4个jar包:ucanaccess、commons-lang、commons-logging、hsqldb。缺一个都会在运行时报NoClassDefFoundError,这个我后面讲坑时会再提。
3.2 用IDEA打开传统JSP项目:建项目向导和导入项目的差别
有的人在IDEA里“New Project”选Servlet模板,然后猛抄代码,结果一堆红标。传统JSP项目最简单的打开方式是直接Open或Import。打开zip解压后的目录,IDEA会识别成一个Java Web项目。如果识别不了,手动做三步:
第一,在Project Structure里的Libraries中把WEB-INF/lib下所有jar加进来;第二,在Facets中选择Web资源目录,通常默认指向src/main/webapp或WebContent;第三,配置Artifacts,把项目打成war包或exploded目录,然后挂到Tomcat上启动。
如果你习惯Eclipse,直接File→Import→Existing Projects into Workspace,选解压后的目录就行。Eclipse对旧JSP项目的兼容性较好,但Tomcat版本依然要选对。
还有一种更省事的做法是跳过IDE,直接把整个项目目录复制到Tomcat的webapps文件夹下,改名为shop,再启动Tomcat。这就是传统JSP项目打包war后的运行方式,war包其实就是把项目目录打成压缩包放进webapps,Tomcat启动时会自动解压并部署。这种方式适合赶时间或者验证当前代码能不能跑,拿来做开发调试不方便,因为修改源码后需要重新打包。
3.3 数据库连接配置:统一管理用.properties文件和注册Servlet
很多老项目会把数据库连接信息直接写在每个Servlet类的doGet里,修改数据库路径时要把每个Java文件翻一遍,血亏。我的习惯是先建一个DBUtil或DBHelper类,把所有连接代码集中管理。如果你拿到的源码不是这样,第一件事就是重构成工具类。
配套的db.properties配置项大致如下:
db.driver=net.ucanaccess.jdbc.UcanaccessDriver db.url=jdbc:ucanaccess://D:/shop/shop.mdb db.username= db.password=参数说明里最值得关注的是db.username留空。Access数据库一般不设用户名和密码,但如果你的mdb文件在创建时设置了数据库密码,那UCanAccess的URL要改成jdbc:ucanaccess://D:/shop/shop.mdb;password=你的密码,注意是分号后的连接参数,不是DriverManager里的password参数。这里我把话说明白:网上很多教程把username和password乱传,结果谁也登录不上。
启动Tomcat后访问http://localhost:8080/项目名,看到首页出现商品列表,说明数据库已经通了。如果抛空指针或500错误,回到第5章排查,那里集中了我整理的高频问题。
4. 核心代码怎么改:登录、购物车、结算和注入防护的实操切片
4.1 登录模块怎么写才能防住access注入:PreparedStatement是底线
登录页面是黑客最关注的地方。老教程里常见写法是拼SQL字符串,例如"SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"。这就是经典的SQL注入点。攻击者在用户名框填' or '1'='1,后面条件被恒真绕过。
防注入的做法是用PreparedStatement占位符:
// 用户登录校验,使用PreparedStatement防止access注入 String sql = "SELECT user_id, username, real_name, role FROM users WHERE username=? AND password=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); // 第一个问号替换为用户名 ps.setString(2, md5(password)); // 第二个问号替换为加密后的口令 ResultSet rs = ps.executeQuery(); if (rs.next()) { session.setAttribute("userInfo", rs); response.sendRedirect("index.jsp"); } else { response.sendRedirect("login.jsp?error=1"); }这段代码的逻辑是:先通过占位符把参数交给驱动处理,驱动会对特殊字符做转义拼接,从而让“注入”变成普通字符串。参数说明中,md5(password)是我的个人建议——如果源码明文存储,那么哪怕防住了SQL注入,数据库被人拖走的时候所有账号裸奔。使用MD5或加盐哈希会让你的项目在答辩时更有技术含量。
4.2 购物车和订单模块的数据一致性:什么时候扣库存、什么时候恢复
很多JSP购物系统把库存扣减写在“添加购物车”这一步,这是最大的逻辑错误。用户把商品加进购物车但不会结账,库存却被扣掉了,最后订单取消还得恢复库存。正确的做法是“下单时扣库存、支付成功时再确认、取消订单时恢复库存”。
下面是一个下订单时扣库存的代码模板:
// 根据订单明细更新商品库存 String updateStock = "UPDATE products SET stock = stock - ? WHERE product_id = ? AND stock >= ?"; PreparedStatement ps = conn.prepareStatement(updateStock); ps.setInt(1, item.getQuantity()); // 本次购买数量 ps.setInt(2, item.getProductId()); // 商品ID ps.setInt(3, item.getQuantity()); // 库存足够的前提条件 int rows = ps.executeUpdate(); if (rows == 0) { // 库存不足或商品已下架 throw new Exception("库存不足"); }这里的WHERE条件里带了stock >= ?,这是一次“原子更新”的小技巧。多线程并发时,即使两条请求同时读到库存为1,数据库层面的行锁和条件判断也会保证只有一个能更新成功。很多QPS低的JSP系统做到这个程度就足够应付课设演示了,再往上的分布式锁已经超出技术栈范围。
4.3 JSP页面中文乱码的根因:过滤器和ContentType两处要同时设
中文乱码是JSP新手遇到最多的玄学问题。乱码有三个来源:第一是页面文件本身的保存编码,第二是HTTP响应头的编码,第三是数据库读取时的字符集转换。Access数据库的文本字段默认按Unicode存储,实际更容易踩的是前两个。
正确做法是在web.xml里加一个编码过滤器,对所有请求先转码再进Servlet:
// 编码过滤器:统一处理请求和响应的字符集 public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); // 处理POST请求中的表单中文 response.setCharacterEncoding("UTF-8"); // 让响应输出按UTF-8编码 chain.doFilter(request, response); // 继续执行后续拦截器 } }同时JSP页面顶部要写<%@ page contentType="text/html; charset=UTF-8" language="java" %>。两个地方都做了,中文基本不会乱码。如果还乱,检查Tomcat的server.xml里Connector是否配置了URIEncoding="UTF-8",Tomcat 9默认就是UTF-8,旧版本需要手动加。
4.4 分页查询:Access没有LIMIT关键字,学会用TOP子查询做翻页
MySQL里的LIMMIT 10 OFFSET 20在Access数据库里行不通,直接用会抛SQL语法错误。UCanAccess驱动为了兼容性部分支持LIMIT,但我建议不要依赖它。最稳妥的分页写法是使用SELECT TOP配合NOT IN子查询。
-- 读取第3页数据,每页显示5条,跳过前10条 SELECT TOP 5 product_id, name, price FROM products WHERE product_id NOT IN ( SELECT TOP 10 product_id FROM products );这种写法的逻辑是:内外层都按相同排序读取。内层取出前N条,外层再从前N+M条里筛选出最多N条剩余数据。注意必须保证product_id唯一,否则NOT IN在数学上会产生错误结果,所以主键字段在这里很关键。
这个分页方案在JSP套上Servlet控制器之后就变成完整流程:
// 分页参数:当前页和每页条数 int pageSize = 5; int currentPage = request.getParameter("page") == null ? 1 : Integer.parseInt(request.getParameter("page")); int offset = (currentPage - 1) * pageSize; String sql = "SELECT TOP ? product_id, name, price FROM products WHERE product_id NOT IN (SELECT TOP ? product_id FROM products)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, pageSize); ps.setInt(2, offset);这里限制条件比较微妙:内层TOP后面的参数不能直接绑定为0,如果currentPage=1时offset=0,内层就变成TOP 0,Access不认。所以在实际编码时要判断,如果是第一页,直接走无NOT IN子句的简化SQL。这个细节我见过不少人栽在页面上,第一页显示正常,第二页翻着翻着就空白了。
5. 必踩的五个坑:连接失败、等待超时、订单凭空消失——每个都有后悔药
5.1 “Class not found: sun.jdbc.odbc.JdbcOdbcDriver”或者说JDBC-ODBC桥失效
现象:项目启动后访问首页报ClassNotFoundException,异常信息指向sun.jdbc.odbc.JdbcOdbcDriver。
原因:JDK 8以后,Oracle官方把JDBC-ODBC桥从JRE中移除,传统的Set up数据源再用桥接驱动的方式已经彻底作废。很多网上教程还在抄2009年的老代码,照搬就是翻车。
解决:换用UCanAccess驱动。把ucanaccess、hsqldb、commons-lang、commons-logging四个jar包拷进WEB-INF/lib,把Class.forName改成net.ucanaccess.jdbc.UcanaccessDriver,URL改成jdbc:ucanaccess://绝对路径。改完后重新部署。
5.2 看到“error 1045 (28000): access denied for user 'root'@'localhost'”,别慌,这不是Access的错
现象:控制台或网页报错信息里有“access denied for user root”,很多人一看“access”就以为Access数据库拒绝访问,到处找Access的权限设置。
原因:这是MySQL的报错,跟本项目无关。当项目里混用了MySQL连接驱动或者断点调试时打开了MySQL资源,就会出现。还有一种可能是你下载的源码文档里同时给了MySQL和Access两套SQL脚本,你在连接配置里把UCanAccessURL填成了jdbc:mysql。
解决:检查db.properties数据库连接串。如果确定要跑Access版本,URL必须是jdbc:ucanaccess://。如果这个系统自带MySQL版,那access denied是对用户名或密码错误的经典反馈,需要到MySQL里执行GRANT或重置root密码。
5.3 Access数据库文件被独占锁死,导致多个页面并发访问时提示“Could not lock file”
现象:后台和前台一起打开时,或者Tomcat里多个线程同时访问同一个mdb文件,系统提示文件被锁定,商品列表或订单页刷新失败。
原因:Access采用的是基于文件的锁机制,在多线程并发写入场景下容易出现文件锁竞争。Tomcat默认使用连接池,如果连接没有及时关闭,mdb文件就会一直被占用。
解决:第一,所有JDBC操作都要在finally里关闭ResultSet、Statement、Connection。第二,把数据库文件从项目目录挪到服务器本地磁盘的单独目录,避免webapps目录下文件被Tomcat监测工具占用。第三,如果并发实在严重,给Access文件加一个只读副本作为查询库,写入操作集中到主库。这是老方案里的常见优化策略,能撑到几百个用户同时在线。
5.4 订单提交成功但购物车里的商品数量不对,库存变成负数
现象:学生演示时连续点击两次“提交订单”,生成两条订单记录,库存被减两次。如果商品实际库存为1,多次点击后库存变成负数。
原因:购物车的结算流程里没有做防重复提交,也没有在扣库存前校验剩余库存。一种更隐蔽的情况是,用户操作过程中多个页面共享同一个数据库连接,事务没有提交,读取到的库存是旧值。
解决:在库存更新的SQL中用stock >= ?作为条件,并使用事务包裹“插入订单主表、插入订单明细、扣库存”三个步骤。如果事务中的任何一步失败,执行conn.rollback()回滚,不要让半截数据留在数据库。课程设计阶段,这个写法足以堵住大多数扣错库存的案例。
5.5 页面出现大量问号或乱码,从数据库读出来的中文字段全部变成“???”
现象:页面上的商品名称、订单状态全是英文正常、中文乱码,刷新后还是同样的问号。
原因:最常见的是JSP页面保存编码和数据库字段编码不一致。Access有一套内部Unicode存储,理论上不挑字符集,但JDBC连接参数里没有指定encoding时,UCanAccess默认按本地编码环境读取,Windows中文系统会按GBK转码,与页面UTF-8不匹配。
解决:在JSP页面顶部加上page指令的ContentType和pageEncoding,统一设成UTF-8。同时在UCanAccess连接URL末尾追加;charset=UTF-8(这是UCanAccess较新版本支持的参数),这样从驱动层面就固定了读取编码。改完这两个位置如果还乱码,打开Access数据库文件,检查对应字段里是否本身存的就是乱码,如果是,只能删掉重建数据。
6. 答辩进阶:把老项目讲出新亮点,再用一个并发模拟脚本验证订单系统的健壮性
课程设计答辩时,老师看腻了“增删改查”,最想听到的是你如何解决实际问题。即便只是JSP+Access课设,用“并发场景下库存不超卖”这个点做演示,也有说服力。先准备一个简单的模拟代码,用多线程请求同一个下单Servlet:
// 模拟20个用户同时抢购库存为10的商品 int threadCount = 20; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { latch.countDown(); HttpClient client = new HttpClient(); client.get("http://localhost:8080/shop/order?productId=5&quantity=1"); }).start(); }这段代码演示的是把并发线程同时放行,让它们竞争同一商品。如果库存最终剩余0且订单数量为10,说明事务和条件更新生效;如果出现负数,说明连接池或事务边界没设好。把这两个结果都截图放进报告,比任何空谈都更有说服力。
还有一个小技巧值得花三十分钟做成彩蛋:给登录模块加一个验证码。不需要框架,用Java的Graphics2D画几个字符就行,把这个功能点写进文档,在答辩时演示,这会让老师觉得你是真的理解Web安全,而不是只会抄源码。个人信息展示页面也可以做成MVC模式,JSP只用来渲染数据,Servlet负责查询逻辑,这是现成加分项。
作为一个趟过无数这个技术方案的人,我最大的教训是:拿到源码包不要急着打开IDE敲代码,先花半小时把表结构和配置文档过一遍,再把连接串和驱动核对好。这一套项目跑不顺,十有八九是配置和环境问题,而不是源码bug。这个方向值得投入,因为你会从中摸清Java Web从请求到响应全链路,也能用最廉价的方式体会数据库并发和事务的真实存在。希望帮到你。
本文还有配套的精品资源,点击获取