ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSP+Access网上购物系统:毕设课设实战指南与核心代码拆解

JSP+Access网上购物系统:毕设课设实战指南与核心代码拆解 简介基于JSPAccess的网上购物系统设计与实现项目提供完整可运行的源码与配套文档面向计算机类专业学生及Java Web入门开发者。项目采用BS架构包含用户注册登录、商品浏览、购物车管理、订单提交、后台维护等核心电商功能有助于理解JSP内置对象、数据库连接、分页查询等常见技术点。压缩包为ZIP格式体积约1.9MB内含项目全部源码与完整设计文档虽无单独的文件明细列表但源码与文档两类文件结构规整方便导入开发工具后直接运行并对照学习。已有290人学习下载在同类购物系统设计中具有一定参考价值。通过使用此压缩包读者能得到一套经过测试校正、保证百分百运行的电子商务系统雏形以及配套的设计文档书写范例对完成毕业设计或课程项目有直接帮助。1. 这套 JSPAccess 购物系统是什么:毕设仓库里最实用的一条赛道JSPAccess 网上购物系统,是高校 Java Web 课程设计和毕业设计里流传最广的组合之一。它解决一个非常具体的问题:用 JSP 写页面、用 Servlet 做控制、用 Access 数据库存商品和订单,凑齐一套前台购物、后台管理的完整电商闭环,再配一份设计文档就能答辩。适合正在选基于 JSP 的毕设选题的本科生,也适合刚读完 jsp 入门教程、想找个完整 java 课程设计源码练手的人。反直觉的是,Spring Boot 都已经迭代到 3.x,这套老技术栈仍在课设圈子里大量流传。原因很现实:不少高校还在以 JSPServletJDBC 为主线教学,Access 又不需要单独安装数据库服务端,Office 自带,演示和交作业都方便。把它的运行原理和典型坑摸透,比直接抄一个微服务项目更能应对答辩追问。下面按解压 zip → 配环境 → 跑起来 → 改功能 → 写文档的顺序,把每一步怎么操作、哪里会翻车讲清楚。2. 先把技术栈拆明白:JSP、Servlet 与 Access 是怎么凑到一起的这套系统的选型不是随意的,而是高校 Java Web 教学体系里的一环。理解了为什么这么选,后面改代码、答辩被追问时才不会露怯。2.1 为什么课设还在一遍遍用 JSP:Servlet 写页面有多痛苦JSP 本质上还是 Servlet。Tomcat 在 JSP 文件第一次被请求时,会把它翻译成一个 .java 源文件,再编译成 .class 执行。你可以打开 Tomcat 安装目录下的 work 文件夹看一眼,index.jsp 对应的 .java 里,全是把模板文本包进 out.write() 的代码。换句话说,JSP 是 Servlet 的模板写法,让页面的人写页面,让逻辑的人写逻辑。那为什么不直接写 Servlet?你试一次就明白:用 response.getWriter().println() 输出一个带表格的商品列表,字符串拼接加转义能把人写到怀疑人生。JSP 的价值就是允许你在 HTML 里嵌入 % % 代码块,用 JSTL 标签替代脚本片段,视图层立刻清爽很多。这也是为什么很多学校至今用JSPServletJavaBean讲 MVC——三层边界清楚,又不需要学生先啃 Spring 的一整套注解和依赖注入。回到这套购物系统,典型的 JSP 页面包括:index.jsp 商品列表、login.jsp 登录、register.jsp 注册、userinfo.jsp 个人信息展示页面、cart.jsp 购物车、admin 目录下的商品管理和订单管理页面。每个页面从 request 或 session 里取数据,渲染成 HTML 返回浏览器。这套做法不优雅,但胜在直接,出错时从页面往前端翻代码非常方便。2.2 Access 数据库的定位:文件型数据库的边界在哪Access 是微软 Office 套件里的关系型数据库,数据全在一个 .mdb 或 .accdb 文件里。它没有独立的数据库服务进程,应用程序通过 ODBC 或 JDBC 驱动直接读写这个文件。对课设来说,这有两个直接好处:第一,不需要安装 MySQL 或 SQL Server 这种服务端软件,Office 自带 Access,打开文件就能看表、改数据;第二,数据库文件可以整个复制带走,交作业之前备份一份,改挂了还有后悔药。但边界同样明显。Access 的并发写入能力很弱,它用的是文件级锁,两个以上用户同时写同一个表就可能互相阻塞。购物系统在课设演示场景下只有一两个人操作,完全够用;如果真拿去上线,几个并发下单就会出现数据库已被占用的报错。所以这套组合只适合课程设计和学习项目,不适合生产环境。答辩时老师如果问Access 和 MySQL 有什么区别,你就把这条边界讲清楚,反而加分。2.3 连接 Access 的两条路:JDBC-ODBC 桥与 UCanAccessJava 连 Access 有两条历史路线,很多人在这里翻车,必须先讲清楚。第一条是 JDBC-ODBC 桥。早期 JDK 自带 sun.jdbc.odbc.JdbcOdbcDriver,通过 Windows 的 ODBC 数据源管理器配置一个系统 DSN,Java 代码用 jdbc:odbc:数据源名 连接。老教程里几乎全是这个方案,但它有两个致命问题:一是 JDK 8 起官方标记废弃,JDK 9 直接移除该类;二是 64 位 JDK 必须配 64 位 ODBC 数据源,很多教程演示的是 32 位管理器,对不上就报连接失败。第二条是 UCanAccess,一个纯 Java 实现的 Access 驱动,不依赖 ODBC。连接串写成 jdbc:ucanaccess://文件绝对路径,驱动类是 net.ucanaccess.jdbc.UcanaccessDriver。这是我现在所有 JSPAccess 项目里唯一推荐的方式,绕开了 ODBC 的位数问题,还支持 .accdb 格式。两者对比如下:对比项JDBC-ODBC 桥UCanAccess驱动类sun.jdbc.odbc.JdbcOdbcDrivernet.ucanaccess.jdbc.UcanaccessDriver连接串jdbc:odbc:DSN名称jdbc:ucanaccess://D:/shop/shop.accdbJDK 8可用但已废弃正常JDK 9 及以上类不存在,不可用正常额外依赖Windows ODBC 数据源配置ucanaccess 及其依赖 jar 包数据库格式仅 .mdb.mdb 与 .accdb 都支持如果你用 Maven 管理工程,引入 net.ucanaccess:ucanaccess:5.0.1.0 会把依赖一起带下来。如果是传统手动放 jar 的 Web 工程,需要把 ucanaccess.jar、jackcess、commons-lang、commons-logging、hsqldb 这几个 jar 一起放进 WEB-INF/lib。缺 jar 是最常见的启动报错来源,这一步记下来,后面避坑章节还会提。2.4 三层结构:一个能答辩的模块怎么分层从 zip 解压出来的源码,目录结构大致如下:shop/ ├── src/ │ ├── com/shop/entity/ # 实体类:User、Product、Order、CartItem │ ├── com/shop/dao/ # 数据访问:UserDao、ProductDao、OrderDao │ ├── com/shop/service/ # 业务逻辑:OrderService、CartService │ ├── com/shop/servlet/ # 控制器:LoginServlet、CartServlet、OrderServlet │ └── com/shop/util/ # 工具类:DBUtil、StringUtil ├── web/ │ ├── index.jsp # 商品列表首页 │ ├── login.jsp # 登录页 │ ├── register.jsp # 注册页 │ ├── cart.jsp # 购物车页面 │ ├── order.jsp # 订单确认页面 │ ├── admin/ # 后台商品与订单管理 │ └── WEB-INF/ │ ├── web.xml # 部署描述符 │ └── lib/ # 依赖 jar 包 └── database/ └── shop.accdb # Access 数据库文件理解这个结构比跑通它更重要。entity 对应数据库表结构;dao 封装每一条 SQL 操作;service 放跨表事务这类业务规则;servlet 接收 HTTP 请求、调 service、决定跳转到哪个 JSP;JSP 只做渲染。后面想改任何功能,都顺着页面提交参数 → Servlet 取参 → Service 处理 → DAO 写库 → 跳回页面这条链路走,不会迷路。提示:有些老项目把数据库文件放在 web 目录下,这会导致部署到 Tomcat 后用户能直接下载 .accdb 文件。应该放在项目外或 WEB-INF 目录里,只有程序能访问。3. 把 zip 里的源码跑起来:环境、数据库与第一个页面拿到源码包后第一件事不是打开代码读,而是让项目先跑起来。跑通一次,你对整个系统的信任感就建立起来了。这一章按顺序给到最小可复现步骤。3.1 环境清单:JDK、Tomcat 与数据库文件三件套老 JSP 项目对 JDK 版本有隐性要求。因为 JDK 9 移除了 JDBC-ODBC 桥,而且老代码里可能用了只有 JDK 8 才有的 API,所以最省事的组合是:JDK 8(建议 8u201 或更高,自带完整 javax.servlet 兼容支持)Tomcat 8.5 或 9.0(注意不是 Tomcat 10,后面避坑章节会讲为什么)Access 数据库文件 shop.accdb(如果只是为了运行,UCanAccess 直接读文件即可,机器上不装 Office 也能跑;想编辑表结构才需要装 Office 里的 Access)验证环境的命令:java -version # 期望输出包含 1.8.x,例如 java version 1.8.0_201 catalina.bat version # Windows 下在 Tomcat 的 bin 目录执行,期望显示 Apache Tomcat/8.5.x如果 java -version 显示的是 11 或 17,建议先装一个 JDK 8 并切换 JAVA_HOME,而不是硬着头皮改代码。老课设工程在 JDK 11 上编译,经常会冒出一些跟 javax.xml.bind 相关的 ClassNotFoundException,纯粹是版本问题,不是你的代码问题。3.2 初始化数据库:表结构脚本与演示数据zip 包里的 database 目录一般已经带了一个可用的 shop.accdb,里面建好了用户、商品、订单、订单明细这几张表和少量演示数据。如果数据库文件损坏,或者你想从头建一遍,在 Access 里新建一个空库,把下面这个 SQL 贴进查询设计视图里执行:-- Access SQL 建表脚本 CREATE TABLE users ( id AUTOINCREMENT PRIMARY KEY, username TEXT(50) NOT NULL, password TEXT(32) NOT NULL, realname TEXT(50), phone TEXT(20) ); CREATE TABLE products ( id AUTOINCREMENT PRIMARY KEY, name TEXT(100) NOT NULL, price CURRENCY NOT NULL, stock INTEGER NOT NULL, pic TEXT(200), category TEXT(50) ); CREATE TABLE orders ( id AUTOINCREMENT PRIMARY KEY, orderno TEXT(30) NOT NULL, user_id INTEGER NOT NULL, total CURRENCY NOT NULL, create_time DATETIME, status INTEGER DEFAULT 0 ); CREATE TABLE orderdetails ( id AUTOINCREMENT PRIMARY KEY, order_id INTEGER NOT NULL, product_id INTEGER NOT NULL, price CURRENCY NOT NULL, num INTEGER NOT NULL );这里几个 Access 特有的类型要注意:TEXT(n) 是短文本;INTEGER 是 32 位整数;CURRENCY 是带 4 位小数的货币类型,做金额比 DOUBLE 靠谱,不会出现 0.10.2 的精度问题;AUTOINCREMENT 相当于 MySQL 的 AUTO_INCREMENT 自增主键。建完表之后,往 products 里插几条演示数据,再建一个测试账号 admin / 123456。注意密码字段在课设里经常直接存明文,这确实不安全,但老源码普遍这么写。你可以在文档的改进方向里主动提一句后续应使用 MD5 加盐,老师会觉得你懂工程实践。3.3 改连接配置:DBUtil 是第一个要动的地方解压出的源码里,数据库连接几乎都集中在 DBUtil.java 一个类里。这是整个项目第一个要修改的地方,因为 zip 里的连接串大概率写的是原作者机器的绝对路径。// DBUtil.java package com.shop.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 路径必须改成你本机的实际位置,注意正斜杠或双反斜杠 private static final String DB_URL jdbc:ucanaccess://D:/shop/database/shop.accdb; private static final String DRIVER net.ucanaccess.jdbc.UcanaccessDriver; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError( 缺少 ucanaccess 驱动,请检查 WEB-INF/lib 下的 jar 包); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(DB_URL); } public static void close(Connection conn, Statement st, ResultSet rs) { // 释放顺序:先 ResultSet,再 Statement,最后 Connection // 每个 close 都要单独 try-catch,一个失败不能影响后面的释放 } }参数说明:DB_URL 里的路径分三种写法——jdbc:ucanaccess://D:/shop/shop.accdb(绝对路径)、jdbc:ucanaccess://./database/shop.accdb(相对 Tomcat 启动目录)、以及带参数的形式 jdbc:ucanaccess://D:/shop/shop.accdb;openExclusivefalse。最后这个参数很关键,它告诉驱动不要独占打开数据库文件,后面避坑章节会专门讲。大多数老源码用的是 jdbc:odbc:shop 这种桥接串,如果你拿到的是这种,直接整体替换成上面的 UCanAccess 写法。DAO 层代码一行都不用改,因为 JDBC 的 Connection、Statement、ResultSet 接口没变。这就是换驱动的最大好处——只改一处,全项目生效。3.4 部署到 Tomcat 并启动验证Eclipse 和 IDEA 导入老 Web 工程的方式不一样,但核心动作一致:把项目添加为 Web 项目,关联到 Tomcat 8.5,然后在 Deployment Assembly 里确认 WEB-INF/lib 下的 jar 包会随项目一起发布。如果你不想用 IDE,也可以走命令行验证。先把项目编译,把 web 目录整体复制到 Tomcat 的 webapps 下,重命名为 shop,然后启动:# 切到 Tomcat 的 bin 目录 startup.bat # 或者 Linux / macOS ./startup.sh启动后打开浏览器访问:http://localhost:8080/shop/index.jsp看到商品列表页面说明已经跑通。如果看到 404,先做两个排查:第一,确认访问路径里的 /shop 和 Tomcat webapps 下的目录名一致;第二,看 Tomcat 的 logs/localhost.log 里有没有 Context 启动失败的异常,尤其是 ClassNotFoundException 和 FileNotFoundException。前者是缺 jar,后者是数据库路径不对。这两类问题占了老 JSP 项目首次启动失败的大头。4. 核心代码拆解:登录鉴权、购物车与下单事务项目跑通之后,下一步就是读懂并修改核心功能。这三个模块是答辩时老师最关注的,也是把课设和毕设区分开的工程含量所在。4.1 登录鉴权:setCharacterEncoding 与 PreparedStatement 两条红线登录模块看起来简单,但十个别名的源码里有八个在中文用户名上翻车。先看标准写法:// LoginServlet.java package com.shop.servlet; import com.shop.dao.UserDao; import com.shop.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 必须在读取任何参数之前设置编码,这是中文不乱码的第一道关 request.setCharacterEncoding(GBK); String username request.getParameter(username); String password request.getParameter(password); UserDao userDao new UserDao(); User user userDao.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); // sendRedirect 比转发更适合登录成功后跳转,避免刷新重复提交 response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段代码里有两条线。第一条是 request.setCharacterEncoding(GBK) 必须放在 getParameter 之前,否则表单提交的中文参数进入 JVM 时已经是乱码,后面怎么转都救不回来。第二条是 DAO 里查用户时用 PreparedStatement 而不是 Statement:// UserDao.java 中的 login 方法 public User login(String username, String password) { String sql SELECT * FROM users WHERE username ? AND password ?; // 用 PreparedStatement 占位符,而不是字符串拼接 // 字符串拼接一旦参数里带单引号,就是经典的 access 注入入口 try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRealname(rs.getString(realname)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }注意这里用了 try-with-resources,Connection、PreparedStatement、ResultSet 自动关闭,省掉了手写 finally 的繁琐。Access 场景下,连接不关闭会直接锁库,所以用完即关不是建议而是纪律。4.2 购物车:Session 里的 HashMap 怎么玩购物车实现有两条路:存数据库或存 Session。课设项目里最普遍的做法是 Session 里放一个 HashMap,key 是商品 id,value 是购物车条目对象。原因有两个:购物车是临时数据,用户关浏览器就没了,不值得写库;而且每次增删都操作 Access 文件,并发一高就把数据库锁死了。核心逻辑是这样的:// CartServlet.java 中处理加入购物车的方法 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int productId Integer.parseInt(request.getParameter(id)); int num Integer.parseInt(request.getParameter(num)); ProductDao productDao new ProductDao(); Product product productDao.findById(productId); if (product null) { response.sendRedirect(index.jsp); return; } HttpSession session request.getSession(); // 从 Session 里取出购物车;没有就新建一个 SuppressWarnings(unchecked) MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMapInteger, CartItem(); } // 已存在同一商品则累加数量,否则新增条目 CartItem item cart.get(productId); if (item null) { cart.put(productId, new CartItem(product, num)); } else { item.setNum(item.getNum() num); } // 必须重新放回 Session,因为 JSP 里取的是 session 属性 session.setAttribute(cart, cart); response.sendRedirect(cart.jsp); }两个细节最容易写错。第一,从 Session 取出来的 Map 强转时,要确认存进去和取出来用的是同一个 key(cart 字符串),漏掉任何一处都会拿到 null。第二,CartItem 是可变对象,修改 item.setNum() 之后不需要重新 put 也能生效,因为 Map 里存的是引用;但为了代码可读性,统一在每次修改后调用 session.setAttribute 是更稳妥的习惯。cart.jsp 页面渲染时,遍历这个 Map,把商品名、单价、数量、小计算出来,最后累加 total 显示在页面底部。这块逻辑不复杂,但要注意 EL 表达式里 Map 遍历的写法,老项目里经常能看到嵌套 scriptlet 的笨办法,你能改成 JSTL 的 forEach,本身就是答辩时一个可讲的亮点。4.3 下单事务:库存扣减和订单明细保持一致下单是整个系统里唯一涉及多表写入的环节,也是最容易出现数据不一致的地方。用户点了提交订单,后台要同时做三件事:往 orders 表插一条主记录、往 orderdetails 表插若干条明细、把 products 表里对应商品库存减掉。这三步必须是一个事务,任何一步失败都要全部回滚。// OrderService.java 中的创建订单方法 public boolean createOrder(Order order, MapInteger, CartItem cart) { Connection conn null; try { conn DBUtil.getConnection(); // 关闭自动提交,手动控制事务边界 conn.setAutoCommit(false); // 第一步:插入订单主表,并拿到自增主键 String sqlOrder INSERT INTO orders(orderno, user_id, total, create_time, status) VALUES(?,?,?,?,?); PreparedStatement psOrder conn.prepareStatement( sqlOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, order.getOrderno()); psOrder.setInt(2, order.getUserId()); psOrder.setBigDecimal(3, order.getTotal()); psOrder.setTimestamp(4, new Timestamp(System.currentTimeMillis())); psOrder.setInt(5, 0); psOrder.executeUpdate(); ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } // 第二步:逐条插入明细,同时检查并扣减库存 String sqlDetail INSERT INTO orderdetails(order_id, product_id, price, num) VALUES(?,?,?,?); String sqlCheckStock SELECT stock FROM products WHERE id ?; String sqlReduceStock UPDATE products SET stock stock - ? WHERE id ?; for (CartItem item : cart.values()) { // 先检查库存是否足够,不够直接抛异常触发回滚 PreparedStatement psCheck conn.prepareStatement(sqlCheckStock); psCheck.setInt(1, item.getProduct().getId()); ResultSet rsCheck psCheck.executeQuery(); if (rsCheck.next()) { int stock rsCheck.getInt(stock); if (stock item.getNum()) { throw new RuntimeException(商品库存不足: item.getProduct().getName()); } } // 扣减库存 PreparedStatement psReduce conn.prepareStatement(sqlReduceStock); psReduce.setInt(1, item.getNum()); psReduce.setInt(2, item.getProduct().getId()); psReduce.executeUpdate(); // 插入订单明细 PreparedStatement psDetail conn.prepareStatement(sqlDetail); psDetail.setInt(1, orderId); psDetail.setInt(2, item.getProduct().getId()); psDetail.setBigDecimal(3, item.getProduct().getPrice()); psDetail.setInt(4, item.getNum()); psDetail.executeUpdate(); } // 第三步:全部成功,提交事务 conn.commit(); return true; } catch (Exception e) { // 任意一步失败,回滚全部操作 if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { // 无论成功失败,最终都要关闭连接并恢复自动提交 if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }事务的边界是这门课的核心考点。setAutoCommit(false) 之后,后续三个操作在同一个物理事务里,commit 之前任何一步抛异常,数据库状态都停留在最初。注意 catch 里 throw new RuntimeException 而不是返回错误码,因为 Runtime 异常才能让事务回滚机制生效。一个参数细节:PreparedStatement 的创建放在循环里,每次循环都新建,虽然性能不优,但在 Access 课设场景下完全够用,而且让代码逻辑更直白。你可以在文档里写一句大数据量场景应改为批量提交,体现你知道性能改进方向。提示:下单成功后记得清空 Session 里的购物车,否则用户再进购物车页面会看到空数据但总价还是旧值,这种前后不一致的 bug 在答辩演示时非常尴尬。5. 避坑指南:Access 锁库、中文乱码与兼容性翻车现场这一章是全篇最值钱的实战部分。每一类问题我都按现象 → 原因 → 解决的顺序讲,基本覆盖了 JSPAccess 项目从开发到演示会遇到的绝大多数拦路虎。5.1 Access 文件锁:数据库已被占用的真相现象:项目第一天跑得好好的,第二天打开后台,操作几次后页面抛出 SQLException,提示内容类似 Others have the file open 或 Could not exclusive lock the database,有时还会看到 另一个程序正在使用此文件,进程无法访问。原因:Access 是文件型数据库,每次创建连接都会对 .accdb 文件加锁。常见诱因有三个——DAO 代码里 Connection 没在 finally 中关闭,连接一直占着文件;代码里把 Connection 写成 static 字段做全局连接,多个请求复用同一个连接,互相踩踏;UCanAccess 默认以独占方式打开文件,第二个请求进来就没法再锁文件。解决:第一,所有 DAO 的数据库操作统一用 try-with-resources 或 finally 关连接,UCanAccess 场景下每次请求开短连接比复用长连接更可靠;第二,连接串加参数jdbc:ucanaccess://D:/shop/shop.accdb;openExclusivefalse,让驱动以共享模式打开文件;第三,不要写 static Connection 这种全局黑匣子,Access 不是 Oracle,连接池那套对它不适用。5.2 中文乱码:GBK 与 UTF-8 的三层混战现象:登录时输入张三,存入数据库后显示成寮犱笁或???;后台添加商品,保存后回显乱码;也有相反的情况——Access 里看数据没问题,网页上显示全是问号。原因:编码在三个环节必须一致。第一,Access 文件内部按 Unicode 存储,UCanAccess 读取不会丢字,乱码大多不出在数据库这一层;第二,JSP 页面顶部的 pageEncoding 决定文件怎么被编译,如果页面里写死 GBK 而 Tomcat 按 UTF-8 读,编译时就乱了;第三,request 参数解析,Tomcat 8 之前 GET 参数默认按 ISO-8859-1 解码,页面是 GBK 提交的中文必然乱。解决:整个工程统一编码。老课设模板多为 GBK,你就全项目统一 GBK——在 web.xml 里挂一个编码过滤器,对所有请求设置 request.setCharacterEncoding(GBK),同时检查每个 JSP 顶部的页面指令% page contentTypetext/html;charsetGBK pageEncodingGBK%。如果数据库里已经存了乱码,不要手工改,写一个临时 JSP 页面,以错误编码读出再按正确编码重新写回,这是最干净的清洗方式。!-- web.xml 中的编码过滤器配置 -- filter filter-nameencodingFilter/filter-name filter-classcom.shop.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueGBK/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.3 JDBC-ODBC 桥:位数不对就是连不上现象:按照老教程写了Class.forName(sun.jdbc.odbc.JdbcOdbcDriver),在本地的 JDK 8 上能跑,换到 JDK 11 直接报 ClassNotFoundException;或者在 ODBC 数据源管理器里明明建好了 DSN,程序却报未发现数据源名称并且未指定默认驱动程序。原因:JDK 9 正式移除了 JDBC-ODBC 桥,这条路线在新版 JDK 上彻底断了。退一步说,即使你坚持用 JDK 8,64 位下的坑依然在——Windows 的 ODBC 数据源管理器分 32 位和 64 位两个入口,你在 32 位管理器里建的 DSN,64 位 JVM 根本看不到,反之亦然。这是课设里最典型的按教程操作却翻车场景,属于环境玄学,不是代码逻辑问题。解决:放弃桥接,直接换 UCanAccess。驱动类换成 net.ucanaccess.jdbc.UcanaccessDriver,连接串整体替换成 jdbc:ucanaccess://绝对路径,DAO 层代码一行不用改。JDBC 的 API 层面没有差异,差别只在驱动加载和连接串格式。5.4 打包 war 与 Tomcat 版本:javax 和 jakarta 之间的坎现象:本地 IDE 里跑得顺顺当当,导出 war 丢到服务器 Tomcat 的 webapps 目录,启动报 java.lang.NoClassDefFoundError,或者部署成功但访问所有 JSP 页面都是 404。原因:两个典型诱因。第一,Tomcat 10 起把 javax.servlet 包整体迁移到了 jakarta.servlet,老项目里所有 import javax.servlet.* 的类,在新版容器里加载不到对应类,直接报 NoClassDefFoundError;第二,传统 JSP 项目打包 war 时,IDE 可能没把 WEB-INF/lib 下的 ucanaccess 全家桶 jar 打进去,运行时驱动类缺失。解决:确认目标 Tomcat 版本。老课设项目优先用 Tomcat 8.5 或 9.0,这两个版本仍然使用 javax.servlet 命名空间,兼容性最省心。如果必须用 Tomcat 10,就得批量把 import javax.servlet 替换为 import jakarta.servlet,这不是改几行的事,而是全项目扫描替换,不建议课设阶段折腾。打包后检查一下 war 里的 WEB-INF/lib 是否包含 ucanaccess 及依赖 jar,缺了就用解压工具手动补进去重新压缩。另外明确一点:nginx 本身不解析 jsp 文件,静态资源可以交给它托管,动态请求要转发到 Tomcat 的端口处理,别在 nginx 配置上白费时间。6. 把课设升格成毕设:文档结构与答辩前的验证源码能跑只是及格,真正拿高分靠的是文档和现场演示。这一章讲清楚文档怎么写和演示怎么排两件事。6.1 设计文档:让老师信服的四章结构zip 标题里的文档通常是毕业设计说明书或课程设计报告。很多人的文档是照着代码目录平铺写的,老师读起来像在读源码注释,印象分上不去。我建议按下面四章组织:章节核心内容现场怎么用需求分析角色划分(游客、注册用户、管理员)、功能用例、非功能需求回答你这个系统是给谁用、解决什么问题系统设计三层架构图、数据库 E-R 图、orders 与 orderdetails 的主外键关系说明回答表之间怎么关联、为什么这样设计详细设计与实现按功能模块拆解,每个模块给核心类图、关键代码、运行截图回答购物车怎么实现、下单怎么保证一致测试与部署功能测试用例表、部署环境清单、典型问题与解决方案回答你跑过哪些用例、有没有遇到问题文档结构化解析的核心技巧:不要按源码目录写,要按用户操作流程写。比如用户下单这一个流程,把 index.jsp 加购 → CartServlet 处理 → cart.jsp 展示 → OrderService 事务提交 → orders 表落库,按调用顺序串起来讲,配合截图,老师能直观看到你完整理解了一条业务链路,而不是只会复制粘贴。6.2 答辩演示:三分钟跑完五步,高频追问怎么答演示环节最容易翻车的是现场现点链接,结果页面 404 或数据库锁死。我的建议是把演示固定成一套流程,提前走两遍:第一步,注册一个新用户,展示数据库里多出记录;第二步,浏览商品,往购物车加三件商品;第三步,修改购物车数量,确认总价变化;第四步,提交订单,打开 Access 展示 orders 表和 orderdetails 表,说明订单号和明细对得上;第五步,管理员登录后台,修改一个商品的价格,回到前台刷新验证。五步做完,功能全景和数据库操作都覆盖到了,控制在三分钟以内。老师最爱追问的问题基本落在三个点上。第一,购物车为什么不用数据库存?你要答:购物车是会话级临时数据,存 Session 里随用户会话销毁,减少对 Access 文件型数据库的写压力,真正需要持久化的是订单。第二,Access 和 MySQL 有什么本质区别?你要答:Access 是文件型数据库,无独立服务进程,并发写入弱,适合课设和小型应用,不适合生产环境,如果部署到生产应迁移到 MySQL。第三,怎么防止库存扣成负数?你要答:下单事务里先查库存再扣减,两个操作在同一个事务内,commit 之前其他事务看不到未提交的扣减结果。我自己做课设时最大的教训是:功能写完不截图,文档拖到最后一晚,结果答辩 PPT 里的界面和现场跑出来的不一样,被老师追问是不是换过项目。现在的习惯是每写完一个模块,顺手把界面截图存进 docs/screenshots 目录,所有数据库结构的变更记录在 README 里。这套 JSPAccess 的老组合虽然不新,但把环境、连接、编码、部署这几关都亲自过一遍,你对 Java Web 请求链路的理解会比直接抄 Spring Boot 项目清楚得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表