简介:面向计算机课程设计与毕业设计场景,基于Java/JSP的智能网络教学系统项目源码,采用JSP+Servlet+MySQL技术栈,适合Java Web中高级学习者参考与二次开发。压缩包共672个文件,约3.56MB,核心由JSP页面、Java类、SQL数据库脚本及前端CSS/JS资源组成;其中114个JSP文件承载页面流转与业务展示,39个CSS和31个JS负责界面布局与交互,7个jar包提供第三方依赖,gif截图则用于直观演示系统运行效果,另附docx说明文档。项目文件目录按代码、页面、脚本和素材分类,结构相对清晰,便于快速定位关键模块。系统围绕网络教学管理常见流程,可覆盖用户管理、教学资源发布、学习记录等模块,配合数据库初始化脚本可快速在本机搭建演示环境。已有124人学习下载,对需要完成课程报告或理解传统Web应用分层结构的同学,这套源码具备直接借鉴价值。
1. 智能网络教学系统到底在做什么:一个JSP+MySQL的老牌组合为什么还值得抄作业
“老师,系统白屏了。”这句话我一年能收到几十次,而主角绝大部分是 JSP+MySQL 的老项目。智能网络教学系统,说白了就是一个基于 Java/JSP 的 Web 应用:Servlet 处理请求,JSP 做页面,MySQL 存学生、课程、作业、题库和成绩,最终打包成 mysql.zip 交付。它能解决教学资料分发、在线作业、自动组卷判分这些真实诉求,特别适合课程设计、毕业设计和中小型校内系统。技术虽然老,但代码透明、部署路径固定、答辩好讲,反而是最不容易翻车的选型。
2. 拆解“智能网络教学系统”的功能边界:登录、题库、自动判分与权限模型
拿到一个 mysql.zip,先别急着双击 startup.bat。我一般会先打开里面带的 sql 文件,因为数据库脚本是这个系统最诚实的说明书。一个“智能网络教学系统”的核心功能并不复杂:用户登录注册、课程管理、在线作业、题库考试、成绩统计。把功能边界划清楚,表结构就出来了。
2.1 角色与权限:学生、教师、管理员一张表加一个 role 字段就能撑起来
打开很多以 jsp 为后缀的老项目,会发现权限模型往往不是三张用户表,而是一张 user 表加一个 role 字段。这样设计的好处很明显:登录时只要一条 SQL 就能查出用户身份,然后根据 role 跳转到学生端、教师端或者管理端。如果拆成 student、teacher、admin 三张表,登录时要查三张表,还要处理同一个账号在两个身份里都存在的边界,代码量和出错概率都会翻倍。
我常用的建表语句是这样,后面所有模块都围绕它展开:
USE db_teaching; CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL, `password` VARCHAR(64) NOT NULL COMMENT '建议存MD5或加盐后的密文', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '0学生 1教师 2管理员', `real_name` VARCHAR(50) DEFAULT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;password 用 VARCHAR(64) 而不是 32,是因为 MD5 输出是 32 位十六进制,但未来如果升级成 SHA-256 需要 64 位,一步到位能省一次改表。role 用 TINYINT 比用 VARCHAR 更省空间,而且排序不会出现“admin”和“Admin”这种大小写坑。create_time 用 DEFAULT CURRENT_TIMESTAMP,插入时不用手动 set 时间。唯一索引 uk_username 是很多人会漏的约束,少了它,学生可以注册两个一模一样的用户名,后面所有按 username 查询的逻辑都会错乱。
表里的注释也要写清楚,因为 JSP 项目团队经常是新手接手,注释比代码更救命。字符集我统一用 utf8mb4,不要再用 utf8。utf8 在 MySQL 里最多存 3 字节,学生交作业时带个 emoji 或者生僻字,插入直接报 Incorrect string value。权限判断在 JSP 页面里可以用 JSTL 控制菜单,在 Servlet 里则用 session 里的 role 值做拦截:
HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); if (user == null) { response.sendRedirect("login.jsp"); return; } if (user.getRole() == 2) { // 管理员才有权访问用户管理 }这段代码的逻辑很简单:先判断是不是登录了,再判断角色够不够。JSP 项目里没有 Spring Security 这类框架,手工拦截是主流做法。要注意的是,这个拦截必须放在所有需要权限的 Servlet 的 doGet/doPost 开头,不要只写在主页跳转里,否则直接访问 servlet 路径就能绕过登录。
2.2 核心表结构:课程表、作业表、成绩表的外键怎么挂
权限定完后,核心就是“课程、作业、成绩”这条线。选课表也不能少,因为没有学生选课,作业就不知道该给谁。常见做法是 course、student_course、homework、score 四张表。course 挂在 teacher_id 上,表示老师创建的课程;student_course 把 student_id 和 course_id 关联起来;homework 挂在 course_id 上;score 挂在 homework_id 和 student_id 上。
CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES `user`(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES `user`(id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE homework ( id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, title VARCHAR(200) NOT NULL, content TEXT, deadline DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_homework_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, homework_id INT NOT NULL, student_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL, submit_time DATETIME DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT '0未提交 1已提交 2已批改', CONSTRAINT fk_score_homework FOREIGN KEY (homework_id) REFERENCES homework(id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES `user`(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里我坚持用外键。很多老项目为了省事不用外键,应用层自己维护数据,结果删课程时作业还在,显示的时候关联不到课程,就会出现白屏或空指针。JSP 项目数据量不大,外键对性能影响可以忽略,反而能让数据在数据库这一层就保持干净。
score 表里 status 字段是“作业状态机”的体现:0 未提交、1 已提交、2 已批改。教师批改时把 score 和 status 一起更新,列表页就能显示“待批改”角标。DECIMAL(5,2) 最高 999.99,足够百分制加平时分。外键字段类型一定要和主表一致:student_id 在 user 表是 INT,这里也必须是 INT,不能写成 BIGINT,否则外键创建直接报错。
2.3 “智能”不是 AI:随机组卷、自动判分与学习统计的实现思路
标题里的“智能”很难是 AI,对这类系统来说,能做到随机组卷、自动判分、成绩统计就已经是实用的智能了。随机组卷最直接的一条 SQL:
SELECT id, question, option_a, option_b, option_c, option_d, answer FROM exam_question WHERE category_id = ? ORDER BY RAND() LIMIT ?;ORDER BY RAND() 的写法在题库量小(几百条)的教学系统里完全没问题,代码好懂,答辩也讲得清楚。如果题库涨到十万级,RAND() 会做全表排序,可以先查出 id 列表,再用随机算法选几个 id 去 IN 查询。教学系统一般不需要这一步,但要能在答辩时说清这个演进方向。
自动判分更适合放在 Servlet 里做,因为要拿到用户提交的答案集合:
// 自动判分:遍历用户提交的答案,与标准答案比较 Map<Integer, String> answerMap = parseRequestAnswers(request); int correctCount = 0; for (Map.Entry<Integer, String> entry : answerMap.entrySet()) { String userAnswer = entry.getValue(); String standardAnswer = questionDao.findAnswer(entry.getKey()); if (standardAnswer != null && standardAnswer.trim().equalsIgnoreCase(userAnswer.trim())) { correctCount++; } } double finalScore = 100.0 * correctCount / answerMap.size();这里要提醒一点:选择题答案建议在页面渲染时就统一成大写 A/B/C/D,判分时再用 trim 和 equalsIgnoreCase,否则学生提交“a ”带空格会被误判。如果题目有多选,数据库里存“ACD”这种组合,判分时要先 split 再排序比对,直接 equals 会翻车。
学习统计这块,我一般会给教师主页放一个班级成绩汇总:
SELECT h.title AS 作业标题, COUNT(s.id) AS 提交人数, ROUND(AVG(s.score), 2) AS 平均分, SUM(CASE WHEN s.score >= 60 THEN 1 ELSE 0 END) / COUNT(s.id) * 100 AS 及格率 FROM homework h LEFT JOIN score s ON s.homework_id = h.id WHERE h.course_id = ? GROUP BY h.id;LEFT JOIN 而不是 INNER JOIN,才能把未提交作业的学生也计入提交人数。COUNT(s.id) 不会计入 NULL,所以未提交的不会算进去。及格率的分母必须是 COUNT(s.id),不能是 COUNT(*),否则没提交的人会拉低及格率。ROUND 保留两位小数。这个 SQL 写完之后,表的边界就清楚了,后面写 DAO 和 Servlet 只是在翻译这一套模型。
到这里,系统的数据库骨架就搭好了,下一步是把它装进 Tomcat 跑起来。
3. 把 mysql.zip 变成能跑的项目:JDK/Tomcat/MySQL 版本对齐与导入全步骤
解压之后先别想着看代码,把运行环境对齐才是第一步。JSP+MySQL 的老项目,最怕的不是业务逻辑,而是版本错位。JDK 8、Tomcat 8.5、MySQL 5.7 这三件套,是我处理过多个 mysql.zip 交付包之后固定下来的组合。网上 mysql 安装教程很多,但教程不会告诉你:装错版本才是后面所有报错的根源。
3.1 环境版本组合:JDK 1.8、Tomcat 8.5、MySQL 5.7 为什么是黄金三角
| 组件 | 推荐版本 | 备选 | 注意 |
|---|---|---|---|
| JDK | 1.8 | 11 | 老 JSP 项目用 javax.* 包,JDK 8 兼容性最好 |
| Tomcat | 8.5 | 9.0 | Servlet 3.1/JSP 2.3,老项目直接跑 |
| MySQL | 5.7 | 8.0 | 驱动类名简单,连接串不用配 serverTimezone |
选 JDK 1.8 不是守旧,而是老项目里的 lib 目录基本都是 mysql-connector-java 5.1.x,它用 javax.sql 那套 API,在 JDK 11 之后有些模块被移除了,运行时会报 ClassNotFoundException。Tomcat 8.5 对应 Servlet 3.1,JSP 2.3,这正好是绝大多数 JSP 课程设计项目的 web.xml 版本。Tomcat 9 开始把 javax.servlet 换成 jakarta.servlet,老项目直接跑会报 NoClassDefFoundError。MySQL 5.7 则避开了 8.0 的 caching_sha2_password 认证问题,mysql-connector-java 5.1.49 连接 5.7 不用额外参数就能认账。
如果你的机器上已经装了 MySQL 8.0 或者更高版本,不代表不能跑,但需要注意两处改动:驱动类名换成 com.mysql.cj.jdbc.Driver,jdbc.url 后面加上 serverTimezone=Asia/Shanghai。这是升级代价最小的路径,我建议先把项目用 5.7 跑通,再考虑要不要踩 8.0 的坑。
3.2 导入项目:从 zip 解压到 Eclipse/IDEA 跑的完整步骤
拿到 mysql.zip 后,先解压再观察目录。
unzip mysql.zip -d teaching-system cd teaching-system # 列出两层目录,确认是 Eclipse 工程还是 Maven 工程 find . -maxdepth 2 -type d | sort常见解压结果有两种:一种是完整的 Eclipse 工程,里面能看到 .settings、.classpath、WebRoot 或 WebContent;另一种是 Maven 结构,有 pom.xml 和 src/main/webapp。看到 WebRoot 或 webapp 目录,就知道这是要部署到 Tomcat 的 Web 项目。Windows 下如果没有 find 命令,用 Git Bash 或 WSL 执行也一样;想快速确认也可以直接看根目录下有没有 pom.xml。
接着检查 WEB-INF/lib 里有没有 MySQL 驱动:
ls -lh WebRoot/WEB-INF/lib/ | grep mysql如果没有 mysql-connector 的 jar 包,后面一定会报 ClassNotFoundException: com.mysql.jdbc.Driver。解决办法不是去改代码,而是下载一个 5.1.49 版本的驱动放进去。有些 mysql.zip 会自带 sql 目录和说明文件,解压后最好先读说明,因为原作者可能故意改了数据库名或端口。
Eclipse 里导入:File -> Import -> Existing Projects into Workspace,选择解压目录,等构建完成。IDEA 里导入:File -> Open,选到包含 pom.xml 或 .iml 的目录;如果是 WebRoot 结构,IDEA 会问是否识别为 Web 项目,要选 Yes 并补一个 Web Facet。我个人的经验是,老 JSP 项目用 Eclipse for Java EE 比 IDEA 更省心,IDEA 对动态 Web 项目的 Facet 配置经常要手动改,一个对不上,启动时就报 Artifact 未部署。但如果你团队都用 IDEA,也完全能跑,关键是 Project Structure 里要指定好 Tomcat 作为 Application Server。
3.3 初始化数据库:执行 SQL 脚本并修改 JDBC 连接配置
mysql.zip 里通常会带一份 db_teaching.sql 或类似名字的文件。导入数据库用命令行最省事,这也是 mysql 数据库常用命令的典型场景:
mysql -uroot -p Enter password: ****** mysql> source D:/teaching-system/sql/db_teaching.sql; mysql> show tables;也可以一步到位:
mysql -uroot -p < sql/db_teaching.sql导入后至少要看到 course、homework、score 这类表。如果 show tables 输出为空,说明 .sql 文件里可能没有 USE 语句,脚本建表时没指定数据库。此时先执行 CREATE DATABASE IF NOT EXISTS db_teaching DEFAULT CHARSET utf8mb4; 再 USE db_teaching; 然后重新 source。
数据库就绪后,打开 src 下的 jdbc.properties 或 db.properties,改连接信息:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_teaching?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=你的密码这里有几个参数要说明:useUnicode=true 和 characterEncoding=utf8 解决中文写入乱码,useSSL=false 是为了避免开发环境证书告警。密码是明文,别提交到公共仓库,课程设计可以接受,生产系统至少要加密或用环境变量注入。改完后在 IDE 里把 Tomcat 8.5 配置好,启动,访问 http://localhost:8080/项目名/。如果浏览器出现登录页,说明部署链路走通了。
项目名不一定等于目录名,可以在 Eclipse 的 Web Project Settings 里看 Context Root,或者在 IDEA 的 Artifacts 配置里看 Application context。访问时用这个上下文根,否则会 404。
4. 从 JSP 页面到 MySQL 查询:连接池、编码和分页的关键配置
数据库导入成功后,JSP 项目能不能本地跑顺,就看连接、编码、分页这三个点。它们都不难,但一个没对齐,页面就会用白屏和乱码来报复你。这一章我把三个高频点的配置和取舍一次讲清楚。
4.1 三种数据库连接方式:JDBC 直连、C3P0、Druid 怎么选
JSP 项目访问 MySQL 的方式,我见过最多的是直接在 DAO 里写 DriverManager。好处是代码少、好理解,坏处是每次都新建物理连接,在高并发下会把数据库连接耗尽。教学系统用户量不大,直连能跑;如果你想在答辩时显得专业,换成 Druid 连接池是成本最低的升级。
直连代码一般长这样:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/db_teaching?useUnicode=true&characterEncoding=utf8&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }getConnection 每次调用都会新建连接。改连接池时,不需要改 DAO 里的调用,只要把 DBUtil 的实现换成连接池获取方式即可。Druid 的初始化代码:
DruidDataSource ds = new DruidDataSource(); ds.setUrl(URL); ds.setUsername(USER); ds.setPassword(PASSWORD); ds.setInitialSize(5); ds.setMaxActive(20); ds.setMinIdle(5); ds.setMaxWait(60000);核心参数:initialSize 是启动时预建的连接数,maxActive 是最大活跃连接数,minIdle 是最小空闲数。一个 Tomcat 下跑的教学系统,5 到 20 就够,给到 100 反而容易让 MySQL 线程数被占满。maxWait 是拿连接的最大等待时间,单位毫秒,超过会抛获取连接超时异常。用完后在 finally 里 conn.close(),连接池会把这句理解成“归还连接”,不是真关闭。如果不还,maxActive 很快耗尽,页面就会出现 wait millis 超时。
4.2 中文乱码的源头:POST、响应、连接串三处必须统一
JSP 系统的乱码问题几乎是必坑。乱码的规律是:后端读到的中文正常、页面显示问号;或者数据库里正常、页面显示乱码。要一次性解决,三条链路都设 UTF-8。
第一处是 JSP 页面本身,文件顶部要写:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>第二处是 Servlet 的 request 和 response:
request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");setCharacterEncoding 必须放在第一次 getParameter 之前,否则对 POST 请求的 body 无效。这里有个老坑:GET 请求的中文由 Tomcat 的 URIEncoding 控制,要改 Tomcat 的 conf/server.xml,在 Connector 上加上 URIEncoding="UTF-8"。有些 mysql.zip 自带的 Tomcat 配置是 ISO-8859-1,就算页面再对,地址栏参数中文照样乱。
第三处是 MySQL 连接串,前面已经写过:
jdbc.url=jdbc:mysql://localhost:3306/db_teaching?useUnicode=true&characterEncoding=utf8&useSSL=false如果这三处统一了还乱,去查 MySQL 的 character_set_server 是不是 utf8mb4。执行 SHOW VARIABLES LIKE 'character_set%';,如果是 latin1,建库时要指定 DEFAULT CHARACTER SET utf8mb4。另外要注意,JSP 文件本身的物理编码必须和 pageEncoding 一致。用记事本另存为 ANSI,即使页面里写了 UTF-8,Tomcat 也会读成乱码。
还有一个细节:如果用了 utf8mb4,连接串里的 characterEncoding 还是写 utf8,MySQL 驱动会自己映射成 utf8mb4,这是老驱动的默认行为。不要改成 utf8mb4,否则 5.1.49 驱动会抛 Unsupported character encoding。
4.3 分页查询:手写 LIMIT 还是 PageHelper,JSP 列表页不卡的写法
JSP 教学系统里最容易卡死的是作业列表页,因为有人直接 SELECT * FROM homework,然后在一个 JSP 里套两层迭代,几千条数据就不行了。分页是必选项。MyBatis 项目能用 PageHelper,但 JSP+Servlet 项目没有 MyBatis,没必要为了分页硬塞框架。手写分页只有三步:算偏移、查当前页、查总数。
int page = Integer.parseInt(request.getParameter("page")); int size = 10; int offset = (page - 1) * size; String sql = "SELECT id, title, deadline FROM homework ORDER BY id DESC LIMIT ?, ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, offset); ps.setInt(2, size); ResultSet rs = ps.executeQuery();总数查询:
SELECT COUNT(*) FROM homework;页数计算:
int total = countDao.count(); int pages = (total + size - 1) / size;这里注意 LIMIT 的参数不能直接拼进 SQL,要用 PreparedStatement 的 setInt。MySQL 的 LIMIT 两个参数分别对应 offset 和 rowcount,page=1 时 offset=0,page=2 时 offset=10,公式是 (page-1)*size。排序用 ORDER BY id DESC,新作业排在前面,这也是 mysql 排序最常见的写法。
分页导航在 JSP 里用 <c:forEach> 遍历页码即可。如果数据量大,ORDER BY id DESC LIMIT 的偏移越大越慢,但教学系统几千条,LIMIT 足够,不必上游标分页。count 查询可以单独放一个 DAO 方法,不要在列表 SQL 里直接 COUNT(*),那会把分页 SQL 变复杂还影响缓存。最后,JSP 页面里不要放业务逻辑,把数据在 Servlet 里装好再 forward 到 JSP,不然分页参数容易被标签库的变量覆盖。
5. 避坑/常见问题排查:mysql.zip 导入后白屏、500、乱码的5个实战案例
这一章的每个 case 都是我自己在 JSP+MySQL 项目里踩过的坑。有些问题一搜一大把,但没有一条告诉你“原来问题不在代码里”。下面按现象、原因、解决三步说。
5.1 白屏:Tomcat 起来了但浏览器连请求都发不出去
现象:双击 startup.bat,控制台一闪而过或者停在启动日志,浏览器打开 localhost:8080 显示无法访问。原因八成不是代码,而是 Tomcat 根本没起来。我遇到过最多次的情况是 JAVA_HOME 没配到 JDK,而不是 JRE;或者端口 8080 被占用。解决:先配置 JAVA_HOME 指向 JDK 安装根目录,比如 C:\Program Files\Java\jdk1.8.0_211,PATH 里加 %JAVA_HOME%\bin。再用命令行看真实输出:运行 catalina.bat run,不是 startup.bat,这样日志会直接打在控制台。端口被占用时,修改 conf/server.xml 里的 为 8081,访问时用新端口。
还有一个隐藏原因:webapps 下如果有损坏的 war 包,Tomcat 启动时会在解压阶段卡住,表现也是白屏。解决是把 webapps 清空,只留自己的项目,再启动。这里别急着怀疑代码,先确认 URL 是 http://localhost:8080/项目名/ 而不是 http://localhost:8080/,Tomcat 根路径默认显示的是自身的管理页面。
5.2 数据库连不上:Communications link failure
现象:登录页能开,输入账号后跳 500,后台异常里有 CommunicationsException: Communications link failure。原因:MySQL 服务没启动,或者 jdbc.properties 里端口/账号密码不对。解决:先确认 MySQL 是否在运行,Windows 下 net start mysql,或者任务管理器看 mysqld 进程。然后在命令行 mysql -uroot -p 测试能不能进。能进还报错,就检查 jdbc.properties 的 url 是否是 localhost:3306。如果本地 MySQL 装在 3307,就要同步改。另外 MySQL 8 项目要把驱动类名改成 com.mysql.cj.jdbc.Driver,并在 url 后加 serverTimezone=Asia/Shanghai。注意加 timezone 时,要用 Asia/Shanghai 而不是 GMT+8,避免夏令时问题。
还有一种情况:mysql 服务在运行,但 root 密码和 jdbc.properties 里的不一致。可以在命令行里用同样的密码登录试试,不要凭记忆改配置。连接报错时优先看原始异常堆栈,不要只看浏览器页面,Tomcat 的 localhost.log 会详细记录哪个类哪一行抛的 SQLException。
5.3 中文乱码:问号、火星文、登录名变成????
现象:注册的中文用户名存进 MySQL 后变成 ????,或者页面上显示一堆乱码。原因:字符集不统一。解决:按照第 4 章的三个位置检查。这里补一个隐藏点:MySQL 的 my.ini 里如果是 latin1,那表建得是 utf8mb4 也会被客户端转换坑。改 my.ini 的 character-set-server=utf8mb4 后必须重启 MySQL 服务。修改后对旧的库要执行 ALTER DATABASE db_teaching CHARACTER SET utf8mb4; 再重导数据。不要只在连接串里加 characterEncoding=utf8 就认为完事,那是给 JDBC 驱动的提示,数据库服务端不对照样乱。
另外,如果只在某个 Servlet 里设置了 setCharacterEncoding,而其他 Servlet 没设,就会出现“这个页面正常、那个页面乱码”的情况。最省事的做法是写一个 CharacterEncodingFilter,实现 Filter 接口,在 doFilter 里统一设置 request 和 response 编码,然后在 web.xml 里配置为所有 URL 都走这个过滤器。
5.4 导入 SQL 脚本报错:Unknown column 或语法错误
现象:source db_teaching.sql 时,抛 ERROR 1054 Unknown column 'xxx' in 'field list'。原因:最常见的是 .sql 文件是从高版本 MySQL 导出的,比如 8.0 导出的表结构带了生成列、JSON 类型或 check 约束,5.7 不认;或者 .sql 文件包含了 CREATE DATABASE,但当前用户没有权限。解决:先看报错行号,用文本编辑器打开对应行。如果是 utf8mb4_0900_ai_ci 这种 8.0 专属排序规则,批量替换成 utf8mb4_general_ci。如果脚本里没 CREATE DATABASE,自己先建库再 USE。执行完脚本后马上 SHOW CREATE TABLE course; 确认表结构,能早发现问题。
有时还会遇到 MySQL server has gone away 错误,这通常是脚本太大,超过 max_allowed_packet。解决是在 mysql 命令行先执行 SET GLOBAL max_allowed_packet=64M; 再 source。这个坑在导入导出的教学系统里不常见,但一旦遇到,卡住的时间会很长。
5.5 Tomcat 启动闪退:没有日志,双击就消失
现象:Windows 下双击 startup.bat,黑窗口一闪而过,什么信息都没留下。原因:startup.bat 调用 catalina.bat,启动失败会 return,但因为窗口结束太快看不到。解决:不要在资源管理器双击,在 CMD 里执行 catalina.bat run,错误会留在当前窗口。最常见错误是 JAVA_HOME 或 JRE_HOME 配置成了 jre 路径,Tomcat 需要 JDK 里的编译器。另一个是缺少 tools.jar,如果用的是 OpenJDK 8 没有 tools.jar,要设 JRE_HOME。还有可能是 webapps 下面某个项目的 web.xml 格式错误,导致 Tomcat 启动时解析失败,这时日志会提示 SEVERE: Error configuring application listener。处理方法是暂时把 webapps 里的项目移走,启动成功后一个个加回来。
启动闪退还有一个很不起眼的原因:CATALINA_BASE 和 CATALINA_HOME 环境变量如果指到别的 Tomcat 实例,会造成冲突。echo %CATALINA_HOME% 检查一下,如果两个变量指向不同版本,Tomcat 会加载错误的库文件,启动直接崩。把这两个变量清掉,或者统一指向当前这个 Tomcat 目录,再试。
6. 从“能跑”到“能答辩”:用一条测试主线验证系统,并顺手做3个加分优化
系统部署好后,我会用一条完整链路做冒烟:教师登录创建课程→学生登录选课→教师布置作业→学生提交作业→教师批改打分→查看统计。这条链路上任何一个环节断了,系统都不能交付。期间我通常会直接跑一条汇总 SQL 验证数据是否正确落库。
SELECT c.course_name, COUNT(DISTINCT sc.student_id) AS 选课人数 FROM course c LEFT JOIN student_course sc ON sc.course_id = c.id GROUP BY c.id;查询没错的话,说明 SQL 脚本和 DAO 基本对齐。接下来要加三个低成本优化:第一,登录密码不要明文存,注册时 MD5 加盐,登录时用同样规则去比较;第二,所有 SQL 都改成 PreparedStatement 预编译,参数用 setString 传入,从根上堵住 SQL 注入;第三,成绩列表增加一个“导出 Excel”按钮,实际是输出 csv 文件,用 Servlet 设置 Content-Disposition 即可。这三件事代码量不大,但答辩时能绕开“为什么不用 MyBatis”“有没有考虑安全”这类提问。
String sql = "SELECT * FROM user WHERE username=? AND password=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, md5Password); ResultSet rs = ps.executeQuery();经常看我部署流程的朋友会发现,我每次拿到一个 mysql.zip,第一件事不是打开 IDE,而是先检查数据库脚本和 jdbc.properties,这两处对不上才是一天白干的开始。在这儿栽过几次之后,我已经养成了先改配置再启动的习惯。希望这些踩坑记录能帮你把这类 JSP+MySQL 项目跑通,并能在答辩时把每个设计决策说清楚。
本文还有配套的精品资源,点击获取