news 2026/10/27 19:43:46

JSP+Access网上购物系统开发:从ODBC配置到部署迁移全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Access网上购物系统开发:从ODBC配置到部署迁移全攻略

简介:这是一套基于JSP与Access数据库的网上购物系统完整项目,采用浏览器/服务器模式,覆盖商品浏览、购物车、订单生成等主要业务流程,是Java Web课程设计或毕业设计的常见选题。面向需要完整体验项目开发过程、希望获得可运行范例的学生及开发者,尤其适合正在准备相关项目设计、课程作业或答辩的场景。压缩包约1.9MB,内部包含项目全套源码和完整说明文档,主要文件类型有JSP页面、后端Java类、Access数据库文件以及系统设计文档,除运行代码外还提供需求分析、模块说明和数据库设计等材料,目录划分清楚,便于按功能模块查找与复用。目前已有290人学习下载,属于较受欢迎的设计参考。所有源码均经过测试校正,能够稳定运行,文档则对系统结构、数据表设计和关键流程做了整理,可直接用于二次开发或撰写设计报告,能显著节省从零搭建的时间。

1. 为什么 2025 年还有人要做 JSP+Access 的网上购物系统

打开招聘网站,JSP 早不是主流,Access 更不是,但每年毕业设计选题清单里,“基于 JSP+Access 的网上购物系统设计与实现”还是稳稳占着一席。原因很现实:很多高校的 Java Web 课程还在讲 JSP+Servlet,题目直接指定“必须用 Access”,目的就是让学生把精力放在 Java 代码和系统设计上,而不是耗费在数据库运维。这套组合适合两类人:一是需要交付可运行系统加设计文档的在校生,二是接手维护十年老系统的从业者。它的坑也很集中——JDK 版本、ODBC 驱动位数、Access 文件锁,三样里任何一样出错,系统就跑不起来,而且这些报错信息对排查毫无帮助。这篇笔记按调试顺序来写:先环境、再表结构、再功能,最后是避坑和迁移方案,照着做能少走很多弯路。

2. 先跑通数据源:JSP+Access 的环境搭建与 ODBC 配置

2.1 技术选型不是复古:这套组合对课程设计有多友好

JSP+Access 常被选中,不是因为性能,而是因为省事。Access 是文件型数据库,一个 .accdb 文件就是一个完整库,不需要像 MySQL 那样安装服务、配置账号、管理权限。对“能跑、能演示、有文档”就够的课程设计来说,这套组合在机房环境里迁移成本极低:整套项目拷到另一台电脑,改一个数据源名称就能接着演示。文件型数据库的代价是并发能力几乎为零,多个人同时写同一个文件就会锁冲突,所以它适合单机演示,不适合真实上线。

如果题目没有指定数据库,我一般建议直接上 MySQL,半天迁移成本换来后续省心;但如果题目已经写死“必须使用 Access”,那就把精力放在 JSP/Servlet 和 DAO 层的代码组织上,这是老师真正给分的地方。课程设计里常见的另一个误区是试图给 Access 配连接池、配分布式之类的东西,完全没必要,一个 DSN 一个连接就够,把时间花在核心功能上比什么都值。

2.2 JDK、Tomcat、ODBC 驱动:最小环境清单与版本约束

动手写代码之前先把版本对齐,这步能省下大量排错时间。我常用的组合是这样的:

  • JDK 1.8(64 位):JDK 1.8 是 JDBC-ODBC 桥还能完整工作的最后一个主要版本。再往上这个桥被移除,Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") 会直接抛 ClassNotFoundException。
  • Tomcat 8.5 或 9.0:和 JDK 1.8 兼容稳定,对 JSP 2.3 / Servlet 3.1 支持完全够用。不建议在这个项目上追 Tomcat 10,Jakarta 命名空间迁移会让人把时间全耗在改 import 上。
  • Access 2016 或更高版本:用来建表和设计库文件。运行时不需要 Access 常驻,只要 ODBC 驱动和数据文件在就行。机器没装 Office 时,缺的是驱动本身,去微软官方下载 Access Database Engine Redistributable 即可,位数必须和 JDK 一致。
  • ODBC 数据源:64 位 JDK 配 64 位数据源,这是最容易翻车的一环。

很多人配置数据源时习惯在“运行”里敲 odbcad32,但这个命令在不同系统里打开的管理器位数不确定。64 位管理器在 %windir%\System32\odbcad32.exe,32 位在 %windir%\SysWOW64\odbcad32.exe。JDK 是 64 位却配了 32 位数据源,连接时会报“未发现数据源名称”,这句报错对新手完全没有指向性,十有八九会让人怀疑是代码写错了。这种问题我见过太多次,先把位数对齐再谈别的。

提示:JDBC-ODBC 桥在 JDK 1.8 中虽然能用,官方早已标记为废弃。课程设计和维护老系统用它没问题,做企业项目请走 MySQL 或 UCanAccess 这类纯 Java 驱动方案。

2.3 配置 ODBC 数据源并写通第一条查询

环境装好之后,第一步不是写 JSP 页面,而是写一个十行左右的 Java 小程序验证数据库能连通。这样做能把环境问题和代码问题彻底分开,之后所有页面写起来才有底气。

配置数据源的步骤:启动 64 位 ODBC 数据源管理器,切到“系统 DSN”页签。这里建议用系统 DSN 而不是用户 DSN,因为 Tomcat 服务可能运行在不同账户下,用户 DSN 会访问不到。然后点“添加”,选择 “Microsoft Access Driver (*.mdb, *.accdb)”,数据源名填 shop_dsn,数据库路径指向已经建好的 shopping.accdb,测试连接通过后保存。

接着写最小可运行的测试程序:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class AccessTest { public static void main(String[] args) throws Exception { // 加载 JDBC-ODBC 桥驱动,JDK 1.8 内置,无需额外 jar Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // jdbc:odbc: 后面跟的是 ODBC 数据源名(DSN) String url = "jdbc:odbc:shop_dsn"; Connection conn = DriverManager.getConnection(url); Statement stmt = conn.createStatement(); // 查询 Access 系统表,验证驱动真正能读到库结构 ResultSet rs = stmt.executeQuery("SELECT Name FROM MSysObjects WHERE Type=1"); while (rs.next()) { System.out.println(rs.getString(1)); } conn.close(); } }

这段代码有两个关键点:JDBC URL 的格式是 jdbc:odbc: 加数据源名称,不需要用户名密码;通过查询 MSysObjects 表来验证驱动真的能解析库结构。如果这段跑通,说明 JDK、ODBC、Access 文件三个环节都没问题,后面的业务代码出错就纯粹是代码问题。如果跑不通,根据异常信息基本能定位:

  • ClassNotFoundException:JDK 版本高于 1.8,换 JDK 或改用纯 Java 的 Access 驱动。
  • 未发现数据源名称:DSN 没建好,或者位数不匹配,按 2.2 的路径重新建。
  • 文件正在使用:Access 软件里开着同一个文件,关掉再跑。

这个测试类建议保留下来,每次换机器部署时先跑一遍再启动 Tomcat,比反复重启试错要快。如果机房机器装的是 32 位 Office 又必须配 64 位 JDK,装 64 位 Access Database Engine 之前要先卸掉 32 位 Office,这一步很多人会卡住。更省心的方式是直接用 UCanAccess 替代 JDBC-ODBC 桥,它不依赖 ODBC 数据源,连接 URL 直接指向文件路径,位数问题彻底消失,但题目明确要求 JDBC-ODBC 桥时,还是按上面的标准配置走。

3. 数据表设计是成败关键:Access 表结构、自增主键与 DAO 层写法

网上购物系统的核心链路是“用户—商品—购物车—订单”,数据表设计比写页面更能拉开分数差距。很多人翻车不是代码写不出来,而是表结构没设计好:下单时发现缺字段、查订单不知道关联哪张表、改需求时表对不上。课程设计虽然数据量小,但表关系必须完整,这一章把表结构和 DAO 层一次讲透。

3.1 用户、商品、订单、购物车:四张核心表的字段设计

最低限度需要五张表:users、goods、orders、orderdetail,外加可选的 cart 表。购物车数据放 Session 更安全(见 4.2),但如果题目明确要求购物车落库,就留一张 cart 表。基本结构如下:

表名字段说明
usersuserid, username, password, realname, address, phone, regtime用户信息,username 唯一
goodsgoodsid, goodsname, price, number, image, description, addtime商品表,number 为库存
ordersorderid, userid, business_no, totalprice, orderdate, status订单主表,status 用数字
orderdetailid, orderid, goodsid, buyprice, buynumber订单明细,记录下单时价格
cartid, userid, goodsid, number购物车表,可选

主键统一用“自动编号”类型,外键字段用数字类型。Access 的表设计器里“自动编号”和“数字”长得像,很容易把外键也设成自动编号,插入数据时会报“不能将自动编号字段设置为外键”。我的固定写法是:主键字段永远叫 xxxid,类型选自动编号;外键字段叫 userid / goodsid / orderid,类型选“数字(长整型)”。

几个容易忽略的细节。第一,users 表的 username 必须唯一,注册时先查重,否则两个同名账号登录,后台订单都分不清是谁下的。第二,goods 表的 price 用“数字(双精度)”,不要用文本,否则金额排序和结算会出各种怪问题。第三,orders 和 orderdetail 一定要拆两张表,不能把一次购买的所有商品塞进一个文本字段,否则“查订单里的商品”会变成字符串处理地狱,而且第三范式的问题在答辩时一问就露馅。第四,status 状态约定成数字字典:0 待付款、1 已付款、2 已发货、3 已收货、4 已取消,在 Java 里建一个 OrderStatus 常量类统一管理,别把魔法数字散落在 Servlet 里。

3.2 Access 的“自动编号”与主键生成策略

写 DAO 层之前先解决一个关键问题:插入一条记录后,怎么拿到新生成的自动编号主键?MySQL 的驱动支持 getGeneratedKeys(),但 JDBC-ODBC 桥对这个方法支持不完整,很多同学在这里拿到 null,下单流程直接断掉。

常见做法是在 INSERT 之后立刻用同一个连接查询 SELECT @@IDENTITY,它返回当前连接最后一次插入的自动编号值。注意是连接级的,如果中间新建了连接,查到的就是别的值了。

public int insertOrder(Connection conn, Order order) throws SQLException { String sql = "INSERT INTO orders(userid, business_no, totalprice, status) VALUES(?, ?, ?, 0)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, order.getUserid()); ps.setString(2, order.getBusinessNo()); ps.setDouble(3, order.getTotalprice()); ps.executeUpdate(); ps.close(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT @@IDENTITY"); int orderId = 0; if (rs.next()) { orderId = rs.getInt(1); } rs.close(); stmt.close(); return orderId; }

关键点是 executeUpdate 和 SELECT @@IDENTITY 必须共用一个 Connection。很多教程只写前半段插入和后半段按 orderid 插明细,中间新建了连接去查,结果永远拿到 0。另外,如果插入订单后在同一个连接上又插过别的自动编号表,@@IDENTITY 就变成那条记录的值了,所以要在插入订单后立刻查询,中间不要插入任何其他表。

还有一个细节是业务订单号。直接把自增 orderid 当订单号展示,别人能从数字增长猜出你的订单量。常见做法是拼接时间戳和用户 id,例如 SimpleDateFormat("yyyyMMddHHmmss") 加 userId,插入时存进 business_no 字段,对外展示用 business_no,orderid 留在内部当主键。Access 的自动编号删除后不回填,这是正常的,不用想办法让订单号连续。

3.3 DAO 层连接代码与类型映射的细节

DAO 层建议用一个 DBUtil 类统一管理连接。每次从 DriverManager 获取连接,用完即关。这条规范在 Access 场景下是必须做到的——Access 是文件型数据库,连接不释放会直接表现为 .ldb 锁文件残留,后续所有操作全部阻塞。

public class DBUtil { // 数据源名称建议从配置文件读取,这里用常量方便演示 private static String odbcName = "shop_dsn"; public static Connection getConnection() throws SQLException { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch (ClassNotFoundException e) { throw new SQLException("JDBC-ODBC 桥驱动加载失败,请确认 JDK 版本"); } String url = "jdbc:odbc:" + odbcName; return DriverManager.getConnection(url); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException e) {} } if (conn != null) { try { conn.close(); } catch (SQLException e) {} } } }

类型映射上要留心三点。第一,Access 的“是/否”类型底层在 ODBC 里是数字 0 和 -1,不是 0 和 1,用 rs.getInt() 读会读出 -1,建议用 getBoolean() 再按需转换。第二,日期时间类型读取用 getTimestamp() 没问题,插入时必须包成 java.sql.Timestamp,直接传 java.util.Date 会报类型不匹配。第三,image 字段用普通文本存相对路径就够,不要用 Access 的“超级链接”类型,后者带格式,读出来还要解析。

分页方面,Access 没有 LIMIT,常见写法是 TOP 加子查询。比如取第二页十条,先查出第一页的 goodsid,再用 NOT IN 排除。课程设计的数据量通常很小,不分页直接全量展示反而省事,如果老师明确要求分页,按 TOP 子查询方式实现并能在答辩时讲清楚原理就够了。记住一个边界:Access 没有存储过程,业务逻辑全部集中在 Java 层,所以 DAO 层的 SQL 要尽量简洁,复杂查询拆成多次简单查询来做,比写一条超长 SQL 好维护得多。

4. 把核心功能跑起来:登录、购物车、下单与后台管理

表结构定好之后,功能页面其实都是固定套路:JSP 负责表单展示和提交,Servlet 负责接收参数、调用 DAO、返回结果,JavaBean 存数据。课程设计里最容易丢分的地方反而不是代码逻辑,而是跳转关系、权限控制、状态流转这些平时不太注意的细节。下面把三段关键流程完整走一遍。

4.1 登录与权限控制:Session 的用法和页面跳转

登录逻辑本身不难,难在登录成功之后怎么办。很多人直接 sendRedirect 到首页,结果用户从购物车点结算被带到登录页,登录完又回首页,购物车里的东西全丢了,体验很差。我一般会在登录页接收一个 redirect 参数,登录成功跳回来源页面。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("loginUser", user); String redirect = request.getParameter("redirect"); response.sendRedirect(redirect == null ? "goods_list.jsp" : redirect); } else { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }

这里有两个习惯值得保留。第一,登录失败用 forward 而不是 sendRedirect,这样 request 里的 errorMsg 在 login.jsp 里用 ${errorMsg} 能直接取到,地址栏也干净。第二,登录成功后的跳转地址来自 redirect 参数,用户从购物车点去结算时被带到登录页,登录完还能回到购物车。这个参数在课程设计里经常被忽略,但它是 jsp 个人信息展示页面做得顺不顺的关键——登录后的个人中心、购物车、订单页都要读 session 里的 loginUser 来展示,session 里没有值就该统一跳登录页。

权限控制不建议在 JSP 里手写判断。写一个 Filter 拦截 /cart/、/admin/路径,session 里没有 loginUser 就跳登录页,否则放行。

public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(false); if (session == null || session.getAttribute("loginUser") == null) { HttpServletResponse response = (HttpServletResponse) resp; response.sendRedirect("login.jsp?redirect=" + request.getRequestURI()); return; } chain.doFilter(req, resp); } }

在 web.xml 里注册这个 Filter 时,url-pattern 写成 /cart/* 和 /admin/*,不要拦截全部请求。如果拦截了 login.jsp 本身,会出现重复跳转的死循环,这是新手写 Filter 最常见的翻车点。拦截到未登录请求时,把原始 URI 拼到 redirect 参数里,登录后还能回到原来想去的页面。

4.2 购物车与下单:会话级购物车,不落库也能跑

购物车有两种实现:存在 Access 表里,或者存在 Session 里。课程设计场景下我强烈建议用 Session。Access 文件锁扛不住多用户同时写购物车,而且购物车本来就是临时数据,Session 在浏览器关闭时自动清空,正好符合语义。Session 里放一个 Map<Integer, CartItem>,key 是商品 id,value 是商品和数量。

public void addToCart(HttpServletRequest request, int goodsId, int number) { HttpSession session = request.getSession(); // 购物车是 session 级数据,首次访问时初始化 Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<Integer, CartItem>(); session.setAttribute("cart", cart); } CartItem item = cart.get(goodsId); if (item == null) { // 第一次加购,从数据库读商品信息和价格快照 Goods goods = goodsDao.findById(goodsId); item = new CartItem(goods, 0); cart.put(goodsId, item); } item.setNumber(item.getNumber() + number); }

设计上要注意 CartItem 保存的是下单时刻的价格快照。课程设计阶段不一定做改价功能,但建议字段里就带 buyPrice,否则以后加了促销功能,购物车里的老价格会跟着数据库变,结算金额对不上。下单流程是先写 orders 主表拿到 orderid,再循环购物车写 orderdetail 明细,最后减库存。订单表插入和明细插入之间如果失败,会出现只有主表没有明细的脏数据,课程设计里通常不做事务补偿,但要意识到这个瑕疵。Access 没有行级锁,两个用户同时买最后一件商品时,两边都能查到库存大于 0,这是文件型数据库的边界,答辩时能讲清楚就行。

4.3 后台商品管理:图片路径、库存与订单状态

后台管理是老师重点翻的部分。商品管理的核心痛点不在增删改查,而在图片上传和路径处理。文件上传用 commons-fileupload 是常见做法,但上传文件保存路径千万不要写死成 D:/upload,换机器部署立刻失效。我一般保存到项目的 upload 目录,用 request.getServletContext().getRealPath("/upload") 动态获取路径。

image 字段只存文件名,页面展示用相对路径拼接:

<img src="upload/${goods.image}" alt="${goods.goodsname}" width="120" height="120" />

页面里经常需要缩略图,jsp 图片如何对坐标定位这类问题,其实不用去改图片本身。常见做法是用 CSS 的 object-fit 统一裁剪显示区域,或者在后台另存一个缩略图文件,而不是在 JSP 里做坐标计算——后者既难维护又把页面写得很重。编辑商品时如果上传了新图,记得把磁盘上的旧图删掉,否则答辩时老师打开项目目录,看到一堆没人引用的 test1.jpg、test2.jpg,观感很差。

订单管理后台需要订单列表和订单明细。订单列表按下单时间倒序,显示订单号、用户名、总金额、状态。状态操作就两个:发货和取消。发货把 status 从 1(已付款)改成 2(已发货);取消把 status 改成 4(已取消)并恢复库存。最容易被忽略的是“取消订单要恢复库存”,很多人只改了状态,库存没加回去,演示到第三轮就没货可卖。恢复库存的逻辑要独立成方法,比如 goodsDao.increaseStock(goodsId, buynumber),在发货取消、取消订单这些状态流转处统一调用,不要写在某一个 Servlet 里,否则后面加功能时会漏。

后台页面的权限控制同样走 Filter,建议单独写一个 AdminFilter,检查 session 里的 loginUser 是否具有管理员标记。最粗暴但有效的做法是在 users 表加一个 is_admin 字段,管理员账号值为 1,Filter 里读出来判断,不要单独建一张权限表——课程设计不需要为两个角色建一套权限系统,那是给自己找麻烦。

5. 部署运行避坑指南:乱码、驱动丢失、文件锁与路径写死

前四章把系统搭起来了,但真正决定能不能顺利交付的,是部署运行阶段的几个坑。这些坑一半查不到资料,一半是经验换来的,报错信息一个比一个迷惑,不看提示根本不知道错在哪。我按出现频率从高到低整理成五条,每条都按“现象 → 原因 → 解决”来写。

5.1 中文乱码问题:一个字符集配置漏掉就全乱

现象:登录页输入中文用户名,页面上显示正常,但提交后存进数据库变成“???”,或者从数据库读出来再展示变成乱码。

原因:很多人觉得乱码是玄学,其实就是 JSP 页面编码、请求编码、响应编码三层没对齐。常见的是只设置了 JSP 的 pageEncoding,没设置 request 编码,POST 请求里的中文按默认 ISO-8859-1 解码,到 Java 里就乱了。

解决:统一所有环节为 UTF-8。JSP 页面头部写 pageEncoding="UTF-8";Servlet 里在读取任何参数之前调用 request.setCharacterEncoding("UTF-8");更省事的方式是在 web.xml 里配置一个 CharacterEncodingFilter,全局生效,不用在几十个 Servlet 里重复写。

<filter> <filter-name>encoding</filter-name> <filter-class>com.shop.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter>

如果是 GET 请求乱码,光靠 setCharacterEncoding 没用,需要在 Tomcat 的 conf/server.xml 里给 Connector 加 URIEncoding="UTF-8"。Tomcat 8.5 之后默认 URI 编码就是 UTF-8,但如果用的是旧版 Tomcat,建议主动写上。Access 文件本身没有字符集概念,数据进去是 UTF-8 就是 UTF-8,这点比 MySQL 省心,不用改库字符集。

5.2 驱动加载失败:JDK 版本和 ODBC 位数不匹配

现象:Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") 报 ClassNotFoundException;或者驱动加载成功,但 getConnection 报“未发现数据源名称”。

原因:JDBC-ODBC 桥在 JDK 9 之后被移除,版本不对必报 ClassNotFoundException。另一种情况更隐蔽:ODBC 数据源分 32 位和 64 位,JDK 是 64 位,但配置数据源时打开的是 32 位管理器,或者装的是 32 位 Access 驱动。

解决:统一成 64 位。运行 %windir%\System32\odbcad32.exe 打开 64 位管理器,删除旧数据源重新添加。验证方法是:在管理器里点“测试”通过,再用第 2 章的 AccessTest 跑一遍,两步都过就说明环境没问题。如果机器只有 32 位 Office,需要先卸载,再装 64 位 Access Database Engine Redistributable,装完重启 ODBC 管理器。这个坑非常隐蔽,因为 32 位驱动在管理器里也能看到 Microsoft Access Driver,但 64 位 JDK 根本加载不了它。

5.3 Access 文件被锁定:.ldb 并发冲突

现象:系统跑了一会儿,所有页面开始报“文件正在使用中”或“不能锁定文件”,重启 Tomcat 也没用,直到关掉 Access 软件或删除 .ldb 文件才恢复。

原因:Access 是多用户文件型数据库,连接时会生成 .ldb 锁文件(.accdb 对应 .laccdb)。如果某个连接异常退出没有关闭,锁文件一直残留,后续所有操作被阻塞。最常见的触发场景是代码里 getConnection 之后 try 块抛了异常,close 没执行。

解决:DAO 层所有方法按“finally 里关连接”的规范写,异常路径也要关。同时注意调试时不要一边开着 Access 软件看数据一边跑 Tomcat,Access 软件和 ODBC 驱动同时打开同一个文件,锁冲突是必然的。

public Goods findById(int id) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); ps = conn.prepareStatement("SELECT * FROM goods WHERE goodsid=?"); ps.setInt(1, id); rs = ps.executeQuery(); return rs.next() ? mapRow(rs) : null; } catch (SQLException e) { throw new RuntimeException(e); } finally { // 异常路径也要关闭连接,防止锁文件残留 DBUtil.close(conn, ps, rs); } }

锁文件已经残留时,先停掉 Tomcat,删除项目目录下所有 .ldb / .laccdb 文件再重启。注意同一时间只启动一个 Tomcat 实例,多个实例共享同一个 Access 文件必然冲突。

5.4 Access 数据库密码:看着安全,实际是摆设

现象:为了显得专业,给 Access 文件设了数据库密码,结果连接时处理密码很麻烦,偶尔还报“无效的参数字段”之类的奇怪错误。

原因:Access 的数据库密码加密强度很低,用常见的 Access 密码恢复工具几秒钟就能去掉,它防不住任何人拷走文件。设了密码之后,ODBC 数据源里还要额外配置,配置错了连接直接失败,调试成本翻倍。

解决:课程设计项目不建议给 Access 文件加数据库密码。真正要防的是别人直接下载 .accdb 文件就能看到全部表结构和数据,这比猜密码容易多了。更实际的保护是把数据库文件放进 WEB-INF 目录下,浏览器无法直接访问,后台页面用 Filter 拦截。这比数据库密码靠谱得多,而且不会增加连接配置的复杂度。如果老师要求数据必须加密,那是另一个课题,靠 Access 密码根本解决不了。

5.5 路径写死:换机器就找不到文件

现象:自己电脑上一切正常,拷到老师电脑演示时,图片全部加载不出来,日志里全是找不到文件的异常。

原因:代码里有 D:/upload、/root/shop/data/shopping.accdb 这类写死的绝对路径。自己机器能跑,换一台机器立刻失效。最常写死的有两处:图片上传保存路径、Access 数据库文件路径。

解决:图片路径用相对部署目录的方式获取,数据库文件如果打包在项目里,就从 classpath 拼出路径。可变的配置全部抽到一个 config.properties 文件里,换机器只改这一个文件。

odbc.name=shop_dsn upload.dir=/upload page.size=10

DBUtil 里读取配置:

InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("config.properties"); Properties p = new Properties(); p.load(in); odbcName = p.getProperty("odbc.name");

这样换机器部署时,只需要确认 DSN 名称和上传目录是否存在,五分钟能搞定,不会在老师面前手忙脚乱。路径问题属于典型的一次踩坑长期受益的类型,项目里凡是涉及路径的代码,都要养成“绝不写绝对路径”的习惯。

6. 验证与改造:半小时跑通验收 + 从 Access 迁移到 MySQL

6.1 验收清单:手工测试的用例设计

系统做完,最后一步是照清单验一遍,别在答辩现场才发现主链路断了。下面这张表覆盖核心交易链路,每一项都是演示时一定会被点到的:

用例操作预期结果
注册登录新用户注册再登录成功且页面跳转正确
商品浏览打开列表和详情图片正常、价格库存显示
购物车先加 2 件再加 3 件数量累加,总价正确
结算拦截未登录点结算跳登录页,登录后回购物车
下单确认结算生成订单,库存减少
后台发货操作订单状态状态流转正常
越权访问未登录访问 admin 页面被 Filter 拦回登录页

跑清单时用浏览器无痕窗口,清掉旧 session,避免登录态残留造成误判。每个用例最好能说清“做了什么操作、系统返回什么、对应哪段代码”,答辩被追问时才有底气。

6.2 从 Access 迁移到 MySQL 的三个步骤

如果想把这套系统升级成能真实部署的项目,迁移到 MySQL 是性价比最高的改造,核心只要三步。第一步,用 Access 的“外部数据”导出每张表为 SQL 脚本或 CSV,再导入 MySQL。注意类型转换:自动编号对应 AUTO_INCREMENT,是/否对应 TINYINT(1),日期时间对应 DATETIME。第二步,改连接层,把 DBUtil 里的桥驱动换成 MySQL 驱动:

Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"; Connection conn = DriverManager.getConnection(url, "root", "your_password");

驱动、URL、账号密码一变,所有 DAO 调用点都不用动。第三步,改 SQL 方言。Access 的分页用 TOP,MySQL 用 LIMIT;查询自增主键从 SELECT @@IDENTITY 改成 getGeneratedKeys();字符串函数和日期函数也都有差异。改完后把“登录—加购—下单—发货”完整跑一遍,DAO 里返回值类型变了,编译不一定报错但运行时一定暴露。

6.3 安全加固与代码组织建议

代码组织至少拆成 JavaBean、DAO、Servlet 三层,JSP 里不写 JDBC,这是我接手这类项目的第一原则。防注入只有一个原则:所有用户输入走 PreparedStatement 参数绑定,包括登录框、搜索框、后台状态值。Access 对注入的容忍度很低,早期 ASP 时代大量注入案例都出自这类数据库,字符串拼接 SQL 的习惯必须杜绝。后台管理用 Filter 拦截 admin 路径,数据库文件放进 WEB-INF 目录防止下载。

我自己调试这类系统的习惯是:每次改数据库前先复制一份 .accdb 文件,或者直接用 git 管理整个项目目录,改坏了有后悔药。调试顺序上,永远先跑通数据源再写页面,环境问题不解决,后面写再多界面都是白搭。这套流程走了很多遍,最大的教训是多数时间都耗在了环境和激进的优化上,而不是真正的业务逻辑。顺着这条路把基础打牢,剩下的功夫花在把功能做完整上更值得。希望这篇笔记能帮你少踩几个坑,把时间留给真正该写的代码。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 12:03:36

DeepSeek Harness桌面端实测:插件、Skill与内网部署指南

1. 先搞清楚&#xff1a;DeepSeek Harness 桌面端到底是个啥 1.1 一个 Harness&#xff0c;把 DeepSeek 从“API文档”变成了“工作台” 先说结论&#xff1a;DeepSeek Harness 不是 DeepSeek 官方出的全家桶&#xff0c;而是社区里把 DeepSeek 相关能力封装起来的一套本地工具…

作者头像 李华
网站建设 2026/10/14 12:08:27

Claude Code记忆持久化:用claude-mem让AI记住项目决策

1. 一个“失忆”的AI&#xff0c;和把记忆留住的办法1.1 我为什么开始用claude-mem如果你和我一样&#xff0c;把Claude Code当成一个随叫随到的结对程序员&#xff0c;早晚会撞上同一堵墙&#xff1a;昨天聊了三小时&#xff0c;把模块边界、技术选型、踩坑结论都理清楚了&…

作者头像 李华
网站建设 2026/10/14 15:32:25

Next.js + LangGraph.js 实战:打造多节点简历优化 AI Agent

把简历工具从“调用一次大模型返回建议”改成“多节点协作的 AI Agent”&#xff0c;是我最近一个月做的最值的一件事。项目核心就两个东西&#xff1a;前端用 Next.js 承接交互和流式展示&#xff0c;后端用 LangGraph.js 编排一套可暂停、可回溯、可循环的简历分析优化流程。…

作者头像 李华
网站建设 2026/10/14 11:19:51

Godot编辑器移植鸿蒙PC:三层依赖与四级可行性解析

1. 移植这件事&#xff0c;到底在移什么先说实话&#xff1a;把 Godot 游戏编辑器移植到鸿蒙 PC&#xff0c;不是“打开源码、换个编译器、点一下构建”就能完事的事情。它涉及一条完整的工具链适配链条&#xff1a;渲染后端、窗口系统、输入事件、文件访问、动态库加载、插件生…

作者头像 李华
网站建设 2026/10/19 23:41:39

Next.js+LangGraph.js构建简历优化AI Agent

最近这段时间一直在折腾 AI Agent 的实际落地&#xff0c;前前后后试了好几个技术栈组合。这套 Next.js LangGraph.js 简历工具 AI Agent 是我个人完整跑通、并且觉得最值得拿出来分享的一套方案。它解决的不是"做个聊天机器人"这种玩具问题&#xff0c;而是把一份…

作者头像 李华
网站建设 2026/10/13 12:03:38

Transformer机器翻译源码实战:数据流、训练避坑与BLEU评测

简介&#xff1a;Transformer凭借自注意力机制取代循环网络&#xff0c;成为神经机器翻译的主流架构&#xff0c;其长距离依赖建模能力与并行训练优势&#xff0c;让开发者能高效构建翻译系统。理解词表构建、padding与mask的配合&#xff0c;是跑通源码链路的关键&#xff0c;…

作者头像 李华