ARTICLE DETAIL

建站实战干货

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

从JSP到Oracle:Struts+Hibernate在线考试系统部署与避坑全记录

2026/10/8 16:23:18 拓冰建站 浏览量
从JSP到Oracle:Struts+Hibernate在线考试系统部署与避坑全记录 简介一份基于jspStrutsHibernateOracle构建的通用在线考试系统源码面向Java Web初学者、课程设计与毕业设计开发者有助于理解MVC分层、ORM持久化映射及企业级数据库交互的完整流程。系统涵盖用户注册登录、选择考试、在线作答、提交试卷、查看成绩等核心功能Struts承担请求分发Hibernate负责Oracle表结构的对象关系映射JSP用于呈现页面层次清晰便于逐层研读。压缩包共342个文件包括71个Java源文件、71个class文件、34个JSP页面、22个XML配置文件含Struts与Hibernate映射、9个依赖jar包以及SQL脚本、数据库备份和界面相关gif/jpg图片整体约3.1MB目录结构完整适合直接导入IDE进行调试和二次开发。已有95人学习下载这份源码从数据库设计、后端逻辑到前端交互均有现成实现是快速搭建在线考试系统或巩固SSH框架整合技能的实用参考资料。1. 一台跑了近十年的在线考试系统为什么我还留着它期末前一周机房里的在线考试系统突然卡到登录页都打不开。接手时我发现代码是典型的 jspstrutshibernateoracle 组合放在今天看哪哪都老但这类系统在课程设计、毕业设计里出镜率极高很多单位机房里也还躺着同款。它解决的问题很实在Struts 管页面请求分发Hibernate 管 Java 对象和表之间的映射Oracle 负责把题库、试卷、答题记录稳稳存住。适合读这篇的人有三类手里刚拿到类似源码包要复现的打算拿它改成自己毕设的以及单位里那套老系统出了问题只能硬着头皮维护的。这篇笔记把我拆包、部署、改库、排查的全过程写下来重点讲文档里不会写的参数、报错和边界。2. 把 Struts、Hibernate、Oracle 串起来这套系统的运行逻辑和部署前准备2.1 一次考试请求是怎么走完的从 JSP 到 Action 再到数据库在线考试系统最典型的一条链路是考生打开 JSP 页面提交登录表单Struts 的 ActionServlet 根据配置文件找到对应 ActionAction 调 DAODAO 里用 Hibernate 的 Session 操作 Oracle。很多人拿到源码第一反应是找index.jsp但真正决定能不能跑起来的是struts-config.xmlStruts 1或struts.xmlStruts 2里的映射关系。下面是一段老项目里常见的 Struts 映射配置作用是让登录请求落到loginAction上action path/login typecom.exam.web.action.LoginAction nameloginForm scoperequest validatefalse forward namesuccess path/examList.jsp redirectfalse/ forward namefailed path/login.jsp redirectfalse/ /action这里path是浏览器访问的路径type是对应的 Action 全类名forward决定登录成功后跳转到哪个页面。注意validatefalse老系统为了省事通常关掉 Form 校验后面出诡异问题要先检查这里。逻辑说明浏览器发/login.do请求ActionServlet 拦截后按path找到LoginActionAction 里拿到表单用户名密码调 DAO 查库返回成功后跳转。如果在你机器上点击登录没反应先看控制台有没有 “No action mapped for namespace / and action name login” 之类的报错——这是映射没配对。参数说明redirectfalse表示服务端转发URL 不变改成true就是重定向能避免刷新页面重复提交表单但老项目里频繁用转发改的时候要小心 Action 里request域的数据丢没丢。2.2 运行环境怎么搭JDK、Tomcat、Oracle 驱动和连接池参数这套系统不是双击就能跑的环境版本比代码本身更影响成败。我一般按下面这套组合来配兼容性最稳组件推荐版本说明JDK1.832 位或 64 位均可老项目用高版本 JDK 编译常报反射或权限错误Tomcat6/7/89 以上可能因 Servlet API 变更报错Oracle11g 或 12c本资源以 Oracle 为主库19c 也可跑但需注意驱动JDBC 驱动ojdbc6 或 ojdbc8驱动版本要和 Oracle 服务端大版本匹配构建方式直接复制 war 包或导入 Eclipse老项目多为 Eclipse 工程依赖在 WebContent/WEB-INF/lib连接池是老系统最容易出隐性故障的地方。Hibernate 老版本经常直接集成 C3P0核心参数在hibernate.cfg.xml里property namehibernate.c3p0.max_size20/property property namehibernate.c3p0.min_size5/property property namehibernate.c3p0.timeout1800/property property namehibernate.c3p0.max_statements50/property property namehibernate.c3p0.idle_test_period120/property逻辑说明max_size是连接池最大连接数考试高峰期几百人同时交卷时20 个连接很容易打满。timeout是连接空闲多少秒后被回收设太短会导致频繁创建连接设太长又会让废弃连接占着名额。idle_test_period是空闲连接健康检查间隔。参数说明如果你单位机器内存只有 2G把max_size调到 10 左右就行别贪大。出现Connection is not available, request timed out这类报错八成是max_size太小或连接没释放后面避坑章会展开说。2.3 部署步骤建库建用户、放 war 包、启动验证拿到资源后我习惯先建一个干净的 Oracle 用户再部署避免和现有库冲突。老项目的建库脚本一般是.sql或.dmp如果是 SQL 脚本在 SQL*Plus 里执行sqlplus sys as sysdba create user exam identified by exam123 default tablespace users quota unlimited on users; grant connect, resource to exam; commit; exit;逻辑说明创建名为exam的用户密码exam123默认表空间用users并授予connect和resource两个基本角色。课程设计级别的系统一般不需要dba权限给多了反而有安全隐患。参数说明如果你本机 Oracle 装了多个实例连接前要确认服务名例如ORCL或XE。quota unlimited on users是允许用户在该表空间里建表不加这一句后面导入脚本会报“无法通过 8192 (在表空间 USERS 中) 扩展”。导完脚本后把 war 包复制到 Tomcat 的webapps目录启动cp exam.war /path/to/tomcat/webapps/ cd /path/to/tomcat/bin ./startup.sh tail -f /path/to/tomcat/logs/catalina.out重点看 catalina.out 里有没有Exception常见的是 ClassNotFoundException缺 jar 包去WEB-INF/lib检查 ojdbc 和 struts 相关依赖或 ORA-01017用户名密码不对。看到Server startup in ... ms且没有异常堆栈再访问http://localhost:8080/exam/才算部署完成。3. 把考试业务沉到 Oracle 与 Hibernate核心表、映射和组卷逻辑3.1 核心表怎么设计用户、题库、试卷、答题记录各司其职拿到资源包先别急着跑先打开 SQL 脚本看表结构。在线考试系统的表再多核心也就四五张。老项目最常见的命名是T_USER、T_QUESTION、T_PAPER、T_ANSWER外加一张T_SCORE或直接查答题记录统计成绩。CREATE TABLE T_QUESTION ( QID NUMBER(10) NOT NULL, QTYPE NUMBER(1) DEFAULT 0, QCONTENT VARCHAR2(1000), OPTION_A VARCHAR2(500), OPTION_B VARCHAR2(500), OPTION_C VARCHAR2(500), OPTION_D VARCHAR2(500), ANSWER VARCHAR2(10), POINT NUMBER(4) DEFAULT 2, CONSTRAINT PK_QUESTION PRIMARY KEY (QID) );逻辑说明QID是题目主键QTYPE区分单选、多选、判断OPTION_A到OPTION_D存放选项内容ANSWER存标准答案POINT是每题分值。这种设计把题目选项拆成四个字段简单直观缺点是扩充第五个选项时得加列但课程设计级别够了。参数说明VARCHAR2(1000)是 Oracle 里较安全的长度如果题干里贴了长段落或代码块建议改成CLOB。注意 Oracle 的VARCHAR2在 11g 中最大 4000 字节在 12c 中可以调到 32767 字节但需要设置别上来就写VARCHAR2(5000)。再比如用户表CREATE TABLE T_USER ( UID NUMBER(10) NOT NULL, UNAME VARCHAR2(50) NOT NULL, UPWD VARCHAR2(50), UROLE NUMBER(1) DEFAULT 0, CTIME DATE DEFAULT SYSDATE, CONSTRAINT PK_USER PRIMARY KEY (UID) );UROLE用 0 表示学生、1 表示教师、2 表示管理员这是老项目里最省事的角色方案。CTIME用DEFAULT SYSDATE记录创建时间导入数据时如果发现时间对不上多半是数据库时区和应用服务器时区不一致后面会在避坑里专门讲。3.2 Hibernate 映射与懒加载为什么一打开成绩页面就报错Hibernate 在系统里的角色是把T_USER这类表映射成 Java 对象。老项目常用.hbm.xml文件做映射比如hibernate-mapping packagecom.exam.entity class namePaper tableT_PAPER id namepid columnPID generator classsequence param namesequenceSEQ_PAPER/param /generator /id property nametitle columnTITLE/ set namequestions tableT_PAPER_QUESTION lazytrue key columnPAPER_ID/ many-to-many classQuestion/ /set /class /hibernate-mapping逻辑说明class里的table指定数据库表名id定义主键生成方式这里用 Oracle 序列SEQ_PAPER。重点在set的lazytrue这是 Hibernate 的延迟加载加载试卷对象时不立即查试卷包含的题目等到代码里真正访问paper.getQuestions()时才发 SQL。很多人报LazyInitializationException原因就是在 Action 里加载了Paper等 JSP 页面渲染时 Session 已经关闭再访问questions集合就找不到可用的 Session 了。解决有三个层次最省事的是把lazy改成false不推荐SQL 全跑出来很慢规范做法是在业务方法里用Hibernate.initialize(paper.getQuestions())提前初始化老项目里最实用的做法是直接在查询 HQL 里写join fetchString hql select p from Paper p join fetch p.questions where p.pid ?; ListPaper list session.createQuery(hql) .setParameter(0, pid) .list();join fetch会一次性把试卷和题目查出来避免了懒加载异常也避免了后续 N1 查询慢的问题。参数说明setParameter(0, pid)是 Hibernate 老版本按位置传参的写法新版本建议用setParameter(pid, pid)命名参数不然改 SQL 里问号顺序时很容易出错。3.3 随机组卷和考试计时在 SQL 里做还是在内存里做在线考试系统里最容易被学生钻空子也最容易写崩的功能是随机组卷。老项目里常见做法是把题库数据list()出来在 Java 里用Math.random()抽题数据量小的时候无所谓题库过千后每次组卷都卡几秒。我在这种系统里一般直接把随机抽题下推到 OracleSELECT * FROM ( SELECT * FROM T_QUESTION WHERE QTYPE 1 ORDER BY DBMS_RANDOM.VALUE ) WHERE ROWNUM 10;逻辑说明内层查询把单选题按随机值排序外层用ROWNUM 10取前 10 条。这里必须用两层嵌套不能直接写成WHERE ROWNUM 10 ORDER BY DBMS_RANDOM.VALUE因为 Oracle 先取 ROWNUM 再排序那样抽出来的不是随机题。参数说明DBMS_RANDOM.VALUE每次调用都生成随机数适合抽题场景。如果并发量大可以加一个seed参数让同一次考试的学生用同一套随机序列避免每个人看到的题都不一样导致争议。考试计时的问题更隐蔽。前端页面通常用 JavaScript 倒计时到点自动提交但学生改本地时间就能绕过。老系统的防作弊基本靠服务端兜底交卷时后端校验startTime duration是否小于当前时间而不是信前端的倒计时。如果你是改造这套系统建议在ExamAction里加一段Date now new Date(); long expire exam.getStartTime().getTime() exam.getDuration().longValue() * 60 * 1000; if (now.getTime() expire) { // 强制交卷 }这里的duration单位是分钟getTime()返回毫秒所以乘60 * 1000。这层校验加了之后前端倒计时只是用户体验不是安全边界。4. 避坑记录五条让新手半夜抓狂的血泪经验4.1 ORA-01428 与 ORA-00942一个参数越界一个表名大小写现象导入 SQL 脚本时报ORA-00942: table or view does not exist或者在页面查数据时报ORA-01428: argument x is out of range但表明明已经建好了。原因ORA-00942八成是 Hibernate 生成的 SQL 用了大写表名而建表脚本里给表名加了双引号导致 Oracle 存了小写表名。ORA-01428多数是日期参数算越界比如用TO_DATE(2023-02-30, yyyy-mm-dd)生成不存在的日期或者SUBSTR的起始位置超了字符串长度。解决建表时统一不加双引号让 Oracle 存大写日期参数先用SELECT TO_DATE(2023-02-28,yyyy-mm-dd) FROM dual验证再拼进 SQL。老系统里如果已经建了小写表用ALTER TABLE t_user RENAME TO T_USER改回来。另外检查TO_DATE格式串里的月份天数是不是写死考试日期最好从配置表读取。4.2 监听服务起不来listener.ora、日志膨胀和端口残留现象Tomcat 能启动但页面一登录就报ORA-12541: TNS:no listener或者lsnrctl status超时。更邪门的是昨天还好好的今天监听就是起不来。原因最常见三种情况。第一listener.ora里的HOST写成了localhost局域网内其他机器访问不到。第二Oracle 监听日志listener.log长期不清理文件膨胀到几个 G导致监听响应极慢甚至崩溃。第三机器上装过多个 Oracle 版本旧版本卸载不干净1521 端口被残留进程占用。解决先执行lsnrctl stop停掉监听把$ORACLE_HOME/network/log/listener.log改名备份或清空再lsnrctl start。检查监听配置lsnrctl status netstat -ano | grep 1521 tnsping ORCLnetstat看端口被哪个进程占用如果 PID 是残留的 oracle 进程先确认没在用的库再关。tnsping能区分是监听问题还是服务名问题如果 tnsping 通但 JDBC 连不上检查tnsnames.ora里的SERVICE_NAME是否和实例名一致。4.3 中文乱码从 JSP 到数据库的字符集链路现象页面上表单输入的中文保存后变成???或者页面显示乱码但库里数据正常。这问题在在线考试系统中尤其致命——学生姓名、题目内容全是中文一个题乱码整个考试就没法看。原因乱码链路有三段任何一段不一致都出事。JSP 页面本身编码不对、Tomcat 接收 GET 请求参数时用了默认编码、Oracle 客户端和数据库字符集不一致。解决按顺序检查。JSP 第一行确认有pageEncodingUTF-8Tomcat 的server.xml里给Connector加URIEncodingUTF-8JDBC URL 写成jdbc:oracle:thin:localhost:1521:ORCL?useUnicodetruecharacterEncodingUTF-8然后查库SELECT USERENV(language) FROM dual; SELECT value FROM nls_database_parameters WHERE parameter NLS_CHARACTERSET;如果数据库是AL32UTF8客户端NLS_LANG也设成AMERICAN_AMERICA.AL32UTF8基本就通了。这里有个细节改完这些要重启 Tomcat 和 SQL*Plus乱码是连接建立时确定的热刷新不生效。4.4 N1 查询拖垮成绩统计页一条 SQL 变成几百条现象考试结束后教师端打开成绩统计页面转圈 5 秒以上才显示。打开 Hibernate SQL 日志一看页面加载过程打印了几百条SELECT。原因这是 Hibernate 最经典的 N1 问题。代码里先查出所有考试记录然后循环每条记录去查对应的学生姓名、试卷标题、每题得分每循环一次发一条 SQL。10 条考试记录还好几百条就卡死了。解决把循环查库改成一条 HQL 批量查。比如统计每题正确率时不要先取题再循环查答题记录直接用聚合String hql select answer.questionId, count(*), sum(case when answer.iscorrect 1 then 1 else 0 end) from AnswerRecord answer where answer.examId :examId group by answer.questionId;这里group by在数据库端就把每题正确次数算好了应用层只拿结果集。case when是 Oracle 支持的聚合写法比在 Java 里一遍遍 if 判断快一个数量级。改完后如果 SQL 日志里还有循环查询建议把 Hibernate 的show_sql打开看一眼是不是真的只发了一条。4.5 Session 没关闭导致连接池耗尽ORA-12519 是最后通牒现象系统早上正常到了十点以后越用越慢最终页面报ORA-12519: TNS:no appropriate service handler found。数据库还能连但新连接进不来。原因老项目里最常见的写法是在 Action 或 DAO 里sessionFactory.openSession()用完不close()。每开一个会话就占一个连接连接池超过maxPoolSize后Oracle 直接拒绝新连接。这个错误可不止是程序 bug它会连累同一个数据库上的其他业务。解决统一用getCurrentSession()替代openSession()或者在 Filter 里把 Session 的生命周期管起来。老项目快速补救可以加一个最简过滤器public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { chain.doFilter(request, response); } finally { Session session sessionFactory.getCurrentSession(); if (session ! null session.isOpen()) { session.close(); } } }注意getCurrentSession()需要事务边界如果项目里没有 Spring 管理事务就在 Filter 里手动beginTransaction和commit否则会报Transaction not successfully started。配置hibernate.current_session_context_classthread让 Session 绑定到当前线程。5. 把老系统搬到新机器两个真正派得上用场的技巧5.1 老 SQL 脚本在新版本 Oracle 上执行先做这三步网上下载的老项目脚本生成环境可能是 Oracle 10g 或 11g直接拿到 19c 上跑经常翻车。我现在的习惯是三步预处理打开脚本看头部有没有CREATE SEQUENCE如果有就确认序列名和表里的主键引用一致全局搜索有没有符号比如题干里的“AB”在 SQL*Plus 里执行会被当成变量替换运行前先执行SET DEFINE OFF最后确认脚本末尾有没有COMMIT没有就补一句否则数据导入一半断掉时很难排查。另外老脚本里如果用了CONNECT角色里的权限新版 Oracle 对CONNECT角色默认权限做了一些收紧报ORA-01031: insufficient privileges时用grant create session, create table, create sequence to exam;精确授权比直接怼一个grant dba干净得多。5.2 上线前把链路完整走一遍登录、组卷、答题、计时、交卷接手这类资源后在真正提交给老师或上线给学生用之前我每次强制自己按用户视角点一遍不是随意点点而是固定顺序用学生账号登录 → 参加一场模拟考试 → 确认随机抽题不重复 → 故意不答题直接交卷 → 教师端查成绩和统计。每个节点都对应一个验收点操作预期结果异常时查哪里学生登录跳到考试列表无乱码struts-config.xml映射、字符集开始考试10 道题不重复计时开始DBMS_RANDOM抽取逻辑提交答案自动判分返回得分ANSWER字段与判分代码教师端统计页面 2 秒内出结果Hibernate SQL 日志、N1 查询强制交卷超时后服务端拒绝提交startTime duration时间戳计算这个清单看起来简单但每一条都对应一种我最常踩的坑。以前我以为把 war 包丢进 Tomcat 能打开登录页就算部署完直到有一次考试当天发现计时器根本不生效——前端倒计时走了 120 分钟后端没有超时保护学生改一下系统时间就能多考半小时。从那以后我再接任何老系统第一件事就是把这五步完整走一遍确认“登录、组卷、判分、统计、超时”五个环节都正常再碰其他业务改造。这套按用户链路验收的习惯比看十遍源码都管用。希望帮到你。本文还有配套的精品资源点击获取