ARTICLE DETAIL

资讯详情

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

JavaEE健康管理系统SSH+Oracle架构解析与部署实践

JavaEE健康管理系统SSH+Oracle架构解析与部署实践 简介一套基于SSH框架与Oracle数据库的Java EE健康管理系统完整资料包面向Java Web方向的课程设计、毕业设计及需要参考完整业务闭环的初学者。系统功能覆盖教师登录与注册、饮食管理按个人信息计算每日所需热量并生成一周推荐菜单、健康日志记录睡眠、饮酒、吸烟、血压、体重等、体检结果查询与趋势曲线展示以及针对糖尿病和高血压的患病概率线性回归预测后台管理端支持教师信息维护、体检记录增删改查和健康新闻发布。压缩包整体约143.16MB包含源码工程、Oracle数据库SQL脚本、论文文档和配套视频SQL脚本含建库建表与初始数据便于快速部署和二次开发。项目按用户、饮食、日志、体检、预测、后台管理等模块划分结构清晰。已有106人学习下载可作为SSHOracle整合开发的完整参考。1. 找 javaEE 健康管理系统 SSH-oracle 这个包的人到底在找什么找 javaEE 健康管理系统 SSH-oracle 这个包的人多半是要交课程设计或毕业设计。这里要先说清一个最容易混淆的点标题里的 SSH 不是远程连接工具而是 Struts2、Spring、Hibernate 三件套的组合它是 javaEE 里一套历史悠久的分层框架。包里一般带着源码目录、数据库 sql 脚本、论文文档和演示视频核心目标是让一个学生拿到手后能跑通、能看懂、能改写成自己的东西。适合读这篇笔记的人有三类手上已经有包但跑不起来的学生想快速参考 SSH 标准写法的开发者以及打算把健康管理方向源码改造成商用系统的从业者。下面按我实际跑通这类项目的顺序把框架结构、Oracle 库表设计、核心调用链和高频踩坑现场一次讲完。2. 先把 SSH 三层架构立住Struts2、Spring、Hibernate 在健康系统里各管什么拿到压缩包先别急着解压运行。这类项目的技术栈是固定的只要你能在源码里准确找到三个框架的配置文件后面所有的排查都能顺着链路走下去。SSH 是一个典型的分层组合每一层只干一件事层与层之间靠接口和配置松耦合这也是它在课程设计答辩里最常被问到的点。2.1 三个框架的职责边界为什么用 SSH 而不是 ServletJDBC直接讲职责看下面这张表就够了。层次框架在健康系统里管什么源码里对应的位置表现层Struts2拦截登录请求、校验表单、决定跳转到哪个 JSPcom/health/action 包业务层Spring管理 Service 对象、声明事务边界、处理用户与档案的关联com/health/service 包 applicationContext.xml持久层Hibernate把 User、HealthProfile 等实体映射成 Oracle 表负责增删改查com/health/dao 和 com/health/entity 包为什么不用原生 ServletJDBC 写不是不行而是课程设计的评分标准里通常有分层清晰和架构规范这两项。SSH 的 IoC 容器让对象创建和依赖注入全部交出去写代码的人不需要到处 new ServiceHibernate 又把 JDBC 的重复代码吸收掉分页、事务、对象关系映射都有现成方案。对一个健康管理系统来说用户模块、健康档案模块、体检记录模块之间天然存在一对多的对象关系ORM 的表达效率比手写 SQL 高很多。用 SSH 的代价也很明显配置文件多版本匹配敏感出问题的时候像在拆黑匣子。常见的包大概率是 Spring 3.x/4.x 配 Hibernate 3/4Struts2 用 2.3 或 2.5这个组合在 JDK 8 下最稳定。你要是拿 JDK 17 直接跑老代码大概率第一轮反射初始化就会翻车所以环境版本先按老组合来别一上来就升级。2.2 从 zip 包到可运行工程先认目录再点运行解压之后先对照源码目录找到下面这几个关键位置health-manager/ ├── src │ ├── com/health/action/ # Struts2 Action 类登录、档案新增都在这里 │ ├── com/health/service/ # 业务接口与实现类 │ ├── com/health/dao/ # Hibernate 数据访问层 │ ├── com/health/entity/ # 和 Oracle 表对应的实体类 │ ├── applicationContext.xml # Spring 核心配置数据源、SessionFactory、事务 │ └── struts.xml # Struts2 路由配置URL 到 Action 的映射 ├── WebRoot │ ├── WEB-INF │ │ ├── web.xml # 项目整合入口过滤器与监听器 │ │ └── lib/ # 所有框架 jar 包注意查 ojdbc │ ├── index.jsp │ ├── login.jsp │ └── health/ # 档案管理、体检记录的页面 └── sql ├── health.sql # 建表、序列、触发器 └── init_data.sql # 初始用户和测试数据这个目录结构有两点要提醒。第一很多包的 WEB-INF/lib 下会漏掉 Oracle 驱动 ojdbc 系列 jar因为文件太大、作者上传时省掉了你需要在本地 Oracle 安装目录的 jdbc/lib 下找到 ojdbc6.jar 或 ojdbc8.jar 补进去否则后面必报 ClassNotFoundException。第二有的包把 applicationContext.xml 放在 src 下有的放在 WebRoot/WEB-INF/classes 下部署方式不一样。建议统一按 classpath 根目录来理解web.xml 里写了 classpath:applicationContext.xml那就意味着这个文件必须出现在编译后的 classes 根目录里。2.3 Spring 与 Struts2 的耦合点applicationContext.xml、struts.xml、web.xml 的关键参数三个配置文件决定了项目能不能启动。先从 Spring 的数据源看起这段配置是连接 Oracle 的核心bean iddataSource classorg.apache.commons.dbcp.BasicDataSource destroy-methodclose property namedriverClassName valueoracle.jdbc.OracleDriver/ property nameurl valuejdbc:oracle:thin:localhost:1521:ORCL/ property nameusername valuehealth/ property namepassword valuehealth123/ /bean bean idsessionFactory classorg.springframework.orm.hibernate4.LocalSessionFactoryBean property namedataSource refdataSource/ property namepackagesToScan valuecom.health.entity/ property namehibernateProperties props prop keyhibernate.dialectorg.hibernate.dialect.Oracle10gDialect/prop prop keyhibernate.show_sqltrue/prop /props /property /bean注意 url 里的 ORCL 是 Oracle 的服务名或者说 SID不是随便写的。你要先确认本机 Oracle 实例叫什么用sqlplus / as sysdba查 instance_name对不上就连不上。dialect 参数也有讲究Hibernate 4 时代用 Oracle10gDialect 覆盖 10g、11g 基本没问题如果你的包是 Hibernate 5要改成org.hibernate.dialect.Oracle12cDialect否则分页 SQL 生成语法会出兼容性问题。再看 Struts2 的路由配置它决定你在浏览器里输入什么地址能打开登录页struts package namehealth extendsstruts-default action namelogin classcom.health.action.LoginAction result namesuccess/main.jsp/result result nameinput/login.jsp/result /action action namelistHealth classcom.health.action.HealthAction methodlist result namesuccess/health/list.jsp/result /action /package /struts这段配置的关键点是 class 属性必须写类的全限定名漏写包名会直接 404。还有 Struts2 的 method 参数对应 Action 类里的 public 方法名listHealth 会调用 HealthAction 的 list() 方法这个方法必须要存在且返回 String否则 Web 层到业务层的链路就断了。最后是 web.xml它把 Spring 与 Struts2 挂到同一个 Web 容器里filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter/filter-class /filter filter-mapping filter-namestruts2/filter-name url-pattern/*/url-pattern /filter-mapping listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listenerStruts2 的过滤器类名随版本不同差别很大老项目经常用org.apache.struts2.dispatcher.FilterDispatcher这个类在 2.3 之后被移除。看到启动报错说找不到 FilterDispatcher就先查 lib 里的 struts2-core 版本按实际版本改过滤器类名。url-pattern 用/*如果写成了/Struts2 的所有请求都进不来页面会一直 404。Spring 监听器负责把 applicationContext.xml 里的 dataSource 和 sessionFactory 初始化好它必须在过滤器之前加载这个顺序在 web.xml 里是固定的不要调换。3. 从数据库 sql 脚本开始Oracle 表结构设计与 Hibernate 映射匹配健康管理系统跑不跑得起来一半看代码一半看数据库。我拿到项目的第一件事是打开 sql 脚本在 Oracle 里执行干净再回到源码看实体类。不要先启动 Tomcat不要让系统在缺表少列的报错里反复折腾。先把表结构立起来后面所有功能都是在这几张表上做增删改查。3.1 健康管理系统的四张核心表用户、档案、体检、医生怎么设计绝大多数课程设计论文里的健康管理系统功能不会少于用户管理、健康档案、体检记录、医生信息这几块。对应到数据库最少需要四张主表。表名字段类型与约束说明USERSUSER_IDNUMBER(10) PK主键序列生成USERNAMEVARCHAR2(30) UNIQUE登录名PASSWORDVARCHAR2(64) NOT NULL登录密码ROLE_IDNUMBER(10) DEFAULT 11 普通用户2 医生3 管理员REAL_NAMEVARCHAR2(50)真实姓名HEALTH_PROFILEPROFILE_IDNUMBER(10) PK健康档案主键USER_IDNUMBER(10) FK关联 USERSHEIGHTNUMBER(5,2)身高单位 cmWEIGHTNUMBER(5,2)体重单位 kgBLOOD_PRESSUREVARCHAR2(10)血压值如 120/80HEART_RATENUMBER(6)心率HISTORY_DISEASEVARCHAR2(500)既往病史CREATE_TIMEDATE建档时间CHECK_RECORDRECORD_IDNUMBER(10) PK体检记录主键USER_IDNUMBER(10) FK关联用户CHECK_DATEDATE体检日期ITEM_NAMEVARCHAR2(100)体检项目名称ITEM_RESULTVARCHAR2(200)体检结果描述DOCTOR_IDNUMBER(10) FK关联医生DOCTORDOCTOR_IDNUMBER(10) PK医生主键DOCTOR_NAMEVARCHAR2(50)医生姓名DEPT_NAMEVARCHAR2(100)科室名称TITLEVARCHAR2(50)职称这套表设计是课程设计里的标准答案用户表和档案表是一对一关系用户表和体检记录表是一对多关系医生表和体检记录表也是一对多。答辩时老师大概率会问你为什么 HEALTH_PROFILE 和 USERS 不做成一张表你要能答出来——档案属于扩展信息登录只需要 USERS 里的用户名密码拆开可以减少登录查询的字段范围而且一个用户可能有多份历史档案。虽然实际上一对一居多但这个理由在答辩里够用。注意 BLOOD_PRESSURE 用 VARCHAR2 而不是用两个数字字段这是为了省事前端一个输入框直接存字符串展示也不用拼。如果你想把系统做得更严谨可以拆成 SYSTOLIC 和 DIASTOLIC 两个 NUMBER 字段但这会牵动实体类和 JSP 页面的改动看个人时间和答辩需求取舍。3.2 sql 脚本落地建表、序列、触发器与初始数据一次执行完Oracle 和 MySQL 最大的习惯差异是主键自增。MySQL 写 AUTO_INCREMENT 就行Oracle 没有这个关键字要用序列加触发器配合实现。下面这段是标准的 Oracle 写法-- 用户表 CREATE TABLE USERS ( USER_ID NUMBER(10) NOT NULL, USERNAME VARCHAR2(30) NOT NULL, PASSWORD VARCHAR2(64) NOT NULL, ROLE_ID NUMBER(10) DEFAULT 1, REAL_NAME VARCHAR2(50), PRIMARY KEY (USER_ID), CONSTRAINT UK_USERS_USERNAME UNIQUE (USERNAME) ); -- 主键从 1 开始每次加 1 CREATE SEQUENCE SEQ_USERS START WITH 1 INCREMENT BY 1 NOCACHE; -- 插入时自动生成主键 CREATE OR REPLACE TRIGGER TRG_USERS_BI BEFORE INSERT ON USERS FOR EACH ROW BEGIN SELECT SEQ_USERS.NEXTVAL INTO :NEW.USER_ID FROM DUAL; END; / -- 初始管理员账号 INSERT INTO USERS(USERNAME, PASSWORD, ROLE_ID, REAL_NAME) VALUES (admin, admin123, 3, 系统管理员); COMMIT;序列的 NOCACHE 参数要特别说明。默认情况下 Oracle 序列会缓存一批序号如果数据库重启缓存的序号会丢失导致主键出现跳跃。虽然不影响使用但论文里如果写的是主键连续自增答辩时容易被挑刺。NOCACHE 会让性能略降但对一个课程设计系统来说可以忽略保证主键按预期连续增长。触发器里的:NEW.USER_ID是 Oracle 触发器特有的行级变量表示新插入行的 USER_ID 字段。在 BEFORE INSERT 时机把它赋值成序列的下一个值应用层的 INSERT 语句就完全不用传主键了。这套写法在 11g、19c 上都通用。如果你用的是 Oracle 12c 及以上版本还有GENERATED BY DEFAULT AS IDENTITY的现代写法但老代码基本还是序列加触发器建议按原样执行别改语法避免 Hibernate 主键生成策略对不上。初始数据部分用户名密码基本是明文。课程设计内部系统可以用明文演示但论文的安全分析章节里要主动写一句生产环境必须使用加盐哈希这是加分的点。如果你有时间把密码改成 MD5 或 SHA-256 再存登录验证同步修改这个改进本身就够写一篇三千字的改造说明。3.3 Hibernate 实体与 Oracle 的映射要点主键策略、字段类型、方言表建好之后回到源码里看实体类。Hibernate 的实体映射有两种写法老项目多用 .hbm.xml新一点用注解。不管哪种Oracle 环境下主键生成策略是最容易配错的地方。Entity Table(name HEALTH_PROFILE) public class HealthProfile { Id GeneratedValue(strategy GenerationType.SEQUENCE, generator seq_profile) SequenceGenerator(name seq_profile, sequenceName SEQ_HEALTH_PROFILE, allocationSize 1) Column(name PROFILE_ID) private Long profileId; ManyToOne(fetch FetchType.LAZY) JoinColumn(name USER_ID) private User user; Column(name HEIGHT) private Double height; Column(name WEIGHT) private Double weight; Column(name BLOOD_PRESSURE) private String bloodPressure; Column(name CREATE_TIME) private Date createTime; // getter / setter 省略 }这里最值得记住的是allocationSize 1。Hibernate 默认按 50 的步长预取序列号如果你在数据库里用 SEQ_HEALTH_PROFILE 手工插入过几条测试数据再通过 Hibernate 插入就会遇到主键冲突。设置成 1 之后每次插入只取一个序列号双方行为一致不跳号。这是我在多个项目里踩过的坑跑着跑着 ORA-00001 唯一约束冲突十有八九就是这里没设对。字段类型映射也要按 Oracle 的习惯来。Oracle 没有 mysql 里的 TINYINT 和 BITHibernate 实体里的 Boolean 字段映射到 Oracle 用 NUMBER(1) 最安全。Date 类型对应 Oracle 的 DATE如果你要存精确到秒的时间确保实体里用的是 java.util.Date 而不是 java.sql.Date否则时分秒会被截断。方言的选择直接影响这些类型转换Hibernate 4 用Oracle10gDialectHibernate 5 用Oracle12cDialect如果你在日志里看到 SQL 语句里出现limit这种 MySQL 关键字说明方言配错了Oracle 根本不认识 limit。4. 跑通核心功能链路登录鉴权、档案维护与 Oracle 分页的标准写法数据库就绪、Spring 容器能初始化之后核心功能就是走通三层调用。很多新手在这一步最大的障碍是不理解一个 HTTP 请求到底经历了什么。你在浏览器输入地址Struts2 的过滤器拦截到找到对应的 Action 类Action 里调 ServiceService 里调 DaoDao 里用 Hibernate 的 Session 操作 Oracle 表。接下来我用三个最常见的功能把这条链路拆开。4.1 一次登录的完整调用链Action - Service - Dao - Oracle先看表现层的 LoginAction它负责接收请求参数、调用业务方法、按结果决定返回哪个页面public class LoginAction extends ActionSupport { // Spring 注入不用手动 new private UserService userService; private String username; private String password; public String execute() { User user userService.login(username, password); if (user ! null) { // 登录成功把用户对象放 session ActionContext.getContext().getSession().put(loginUser, user); return SUCCESS; } // 登录失败回到登录页并提示 addFieldError(username, 用户名或密码错误); return INPUT; } public void setUserService(UserService userService) { this.userService userService; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } // getter / setter 省略 }注意这里的 userService 不是 new 出来的而是靠 Spring 的 setter 注入。SSH 项目里 Action 类的实例由 Struts2 创建Service 的实例由 Spring 创建两者靠配置文件里的属性注入桥接。如果你在 Action 里看到直接 new UserService()那说明项目没有走完整的 Spring 注入这种代码在后续维护里会很难替换。再看业务层的 UserService 实现以及它调用的 DaoTransactional public class UserServiceImpl implements UserService { private UserDao userDao; public User login(String username, String password) { // 参数绑定避免字符串拼接 SQL return userDao.findByUsernameAndPassword(username, password); } // setter 省略 } // Dao 层 public class UserDaoImpl implements UserDao { private SessionFactory sessionFactory; public User findByUsernameAndPassword(String username, String password) { String hql from User u where u.username :name and u.password :pwd; Query query sessionFactory.getCurrentSession().createQuery(hql); query.setParameter(name, username); query.setParameter(pwd, password); ListUser list query.list(); return list.isEmpty() ? null : list.get(0); } }登录查询用的是 HQL 参数绑定没有做字符串拼接。这里要主动说一句课程设计里常见的翻车写法是from User where username username 这直接产生 SQL 注入漏洞。用命名参数 :name 和 :pwdHibernate 会走预编译语句这是源码里必须检查的一条红线答辩时也是高频考点。Transactional注解在这里决定了一个关键行为getCurrentSession() 必须在事务范围内否则 Hibernate 会抛出Could not obtain transaction-synchronized Session。Spring 的事务管理器会为一个请求开一个统一的事务边界Dao 里的每个操作共用同一个 Session。如果你换成了 openSession()每次查询都要手动关连接漏关就连接泄漏Oracle 连接数很快被耗尽。4.2 健康档案的新增与修改saveOrUpdate 背后的行为差异健康档案模块的核心动作是新增和修改。Hibernate 提供了一个合二为一的方法 saveOrUpdate但它的判定逻辑必须讲清楚Transactional public void saveOrUpdateProfile(HealthProfile profile) { Session session sessionFactory.getCurrentSession(); // profileId 为空则 insert非空则 update session.saveOrUpdate(profile); } Transactional public void deleteProfile(Long profileId) { Session session sessionFactory.getCurrentSession(); HealthProfile profile (HealthProfile) session.get(HealthProfile.class, profileId); if (profile ! null) { session.delete(profile); } }saveOrUpdate 的规则是实体的主键为空就执行 INSERT主键非空就执行 UPDATE。这个规则看起来简单实际用的时候经常出问题。比如页面表单里藏了一个带 id 的字段用户新增了一份档案结果主键没有清空Hibernate 把它当成修改数据库里根本没有这条记录更新影响行数为 0页面没有任何变化反过来从列表进入编辑页面id 没传到后端saveOrUpdate 又以为你是新增结果生成了一条重复档案。正确的习惯是新增操作对外只接收表单参数强制把 profileId 置空修改操作把 id 从隐藏域带入并调用同样的 saveOrUpdate。这个逻辑在 Action 层做判断不要在 Service 层依赖前端传参。事务边界在这里也很重要。Transactional放在 UserServiceImpl 和 HealthServiceImpl 的实现类方法上默认遇到 RuntimeException 才回滚。如果你在业务方法里 catch 了异常并打印日志而没有重新抛出Spring 认为事务正常提交数据就会出现半截状态。比如一个新增体检记录并更新档案的操作第二步失败被吞掉第一步已经提交系统就脏了。处理原则是业务方法里不要吞异常要么让异常向外抛要么在 catch 块里手动标记 rollback。4.3 Oracle 分页setFirstResult、rownum 与 12c 的 OFFSET FETCH健康档案列表是最典型的分页场景。Hibernate 自带分页方法底层会自动适配方言public PageHealthProfile findProfilePage(int pageNum, int pageSize, Long userId) { Session session sessionFactory.getCurrentSession(); // 先查总数 Query countQuery session.createQuery( select count(*) from HealthProfile h where h.user.id :userId); countQuery.setParameter(userId, userId); Long total (Long) countQuery.uniqueResult(); // 再查当前页数据 Query listQuery session.createQuery( from HealthProfile h where h.user.id :userId order by h.createTime desc); listQuery.setParameter(userId, userId); listQuery.setFirstResult((pageNum - 1) * pageSize); listQuery.setMaxResults(pageSize); ListHealthProfile list listQuery.list(); return new Page(list, total, pageNum, pageSize); }setFirstResult 和 setMaxResults 是 Hibernate 帮我们生成方言分页 SQL 的入口。当 dialect 配置为 Oracle10gDialect 时执行前会转成基于 rownum 的标准 Oracle 分页 SQL。如果你配错了方言或者方言版本过低分页结果可能乱掉。需要注意的是 setFirstResult 是从 0 开始计数第一页传 pageNum1 时计算结果是 0恰好取到第一条如果你算成pageNum * pageSize第一页直接丢掉前 N 条。如果你做的是复杂报表HQL 写起来很别扭可以直接上原生 SQL。Oracle 的 rownum 分页固定三段式嵌套SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT * FROM CHECK_RECORD WHERE USER_ID :userId ORDER BY CHECK_DATE DESC ) t WHERE ROWNUM :pageNum * :pageSize ) WHERE rn (:pageNum - 1) * :pageSize;最内层是业务排序结果中间层加上逻辑行号最外层截取目标区间。这种写法的坑在于 rownum 是在排序之前赋值的如果你把 ROWNUM 和 ORDER BY 写在同一层先取出来的旧行会占住行号最终排序后的分页结果错乱。另外注意这种查询会用不到索引当 CHECK_RECORD 数据量上来之后慢 SQL 优化要从 WHERE 条件字段的索引入手给 USER_ID 和 CHECK_DATE 建组合索引才是治本方案。如果用的还是 Oracle 12c 以上版本直接写OFFSET :offset ROWS FETCH NEXT :pageSize ROWS ONLY它把 rownum 的复杂度折叠掉了阅读性也更好但老 Hibernate 里没法直接用需要在 Dao 里写原生 SQL 或换成 MyBatis 才顺手。5. 部署运行避坑驱动、字符集、方言、404 的五个经典现场代码再顺利最后一步部署也容易翻车。我见过太多人卡在同一个地方数据库连不上、中文全乱码、页面打不开。这些问题的规律很强下面这五条把现象、原因、解决办法一次说清。每条都是我在跑通类似 SSH-oracle 项目时真实排查过的路径按顺序看能少走弯路。5.1 启动报 ClassNotFoundException: oracle.jdbc.OracleDriver现象Tomcat 启动时日志里出现java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver或者点击登录后页面报Cannot create JDBC driver of class oracle.jdbc.OracleDriver for connect URL。原因WEB-INF/lib 目录下没有 Oracle 的 JDBC 驱动 jar。这个文件通常授权限制不能随便分发所以很多课程设计包里会漏放或者放了一个占位空文件。另一个原因是你把 ojdbc 放在了自己的本地目录并手工加到 Build PathIDE 里能编译但部署到 Tomcat 时驱动没有复制到 WEB-INF/lib 下运行时找不到。解决从你本机 Oracle 安装目录的app\oracle\product\版本号\dbhome_1\jdbc\lib下找到 ojdbc6.jar 或 ojdbc8.jar复制到项目的 WEB-INF/lib 目录右键项目重新部署。用 IDEA 的话还要在 Project Structure 的 Artifacts 面板确认这个 jar 被包含进 WEB-INF/lib而不是只在编译期存在。5.2 页面和数据库里的中文全部变成问号现象登录系统后健康档案里的性别、既往病史全部显示为 ????新增一条中文记录存进去再从 sqlplus 查询也是乱码。原因这条链路里有三个地方要保持同一套字符集。第一Oracle 数据库本身的字符集如果装成了 ZHS16GBK而你的 JSP 页面是 UTF-8两边互相传值就会乱第二JSP 页面没声明编码Tomcat 按 ISO-8859-1 解析请求参数中文在到达后端之前已被截断第三Spring 的字符集过滤器没配置POST 请求的参数没有用 UTF-8 解码。解决先查 Oracle 字符集在 sqlplus 里执行SELECT userenv(language) FROM dual;看到 SIMPLIFIED CHINESE_CHINA.ZHS16GBK 或 AL32UTF8 记下来。代码这边在 web.xml 添加 Spring 的字符集过滤器filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping放在 Struts2 过滤器前面。forceEncoding 设为 true 的意思是不管原来页面是什么编码一律按 UTF-8 解码请求和编码响应。如果你的数据库是 ZHS16GBK建议把 JSP 页面里的 contentType 也改回 GBK或者干脆重装 Oracle 时选 AL32UTF8。编码问题的死结就在于两边各执一词统一成同一套才不会再出幺蛾子。5.3 Hibernate 启动报 MappingException 或 ORA-00933现象项目启动时 Spring 容器初始化 SessionFactory 失败日志里有MappingException: Could not parse mapping document或者执行查询时出现ORA-00933: SQL command not properly endedSQL 语句里能看到 limit 关键字。原因dialect 配置的方言与 Oracle 实际版本不一致。Hibernate 3/4 时代默认支持到 Oracle 10g 方言生成 rownum 分页逻辑而如果你的 jar 包是 Hibernate 5.x还沿用老配置里的 Oracle10gDialect某些语句会生成不符合新版的 SQL。另一个原因emmm这算是最常见的了Hibernate 实体字段类型与 Oracle 列类型不匹配比如实体里写了 Integer 但表里是 VARCHAR2Hibernate 在生成类型映射时直接抛错。解决方言统一按实际 jar 版本走。Hibernate 4 用org.hibernate.dialect.Oracle10gDialectHibernate 5 用org.hibernate.dialect.Oracle12cDialect。改完方言后清一下 Tomcat 的 work 目录Hibernate 对实体元数据有缓存不干净重启还可能继续报错。提示判断 Hibernate 版本看 jar 文件名就行hibernate-core-4.x.x.Final.jar 是 4hibernate-core-5.x.x.Final.jar 是 5。别只看 Spring 版本两者是独立的。5.4 访问 login.action 一直 404控制台没有任何报错现象浏览器输入http://localhost:8080/health/login.action返回 404Tomcat 日志没有任何异常struts.xml 看起来也没问题Action 类存在且方法签名正确。原因三条线逐条排查。第一Struts2 的过滤器没有接管请求web.xml 里过滤器的 url-pattern 写成了/或者漏配请求直接落到 JSP 容器找不到 login.action第二struts.xml 里的 package 没有继承 struts-default导致 Action 的栈没初始化请求匹配失败第三Action 类编译后不在 classes 目录里或者类的包名和 struts.xml 里的 class 属性不一致。解决先看 Tomcat 的启动日志有没有打印Struts2的初始化信息。如果连 Filter 都没初始化说明 lib 里缺少核心 jar 或过滤器类名写错。如果初始化正常打开 WEB-INF/classes 查看是否存在 com/health/action/LoginAction.class注意是全限定名路径。最后确认 struts.xml 里的 package 标签 extendsstruts-default少这个属性默认拦截器和结果类型全部失效。5.5 老项目在 IDEA 里打开全是乱码编译报错几百行现象从 zip 解压后用 IDEA 或 Eclipse 导入源码里的中文注释全是乱码JSP 页面里的汉字也变得不可读一编译报错上百行。原因这类课程设计项目大多在 Windows 上开发保存文件的默认编码是 GBK而 IDEA 的默认文件编码是 UTF-8。文件编码不一致编译器读到的字符串长度都变了自然大量报错。解决在 IDEA 的 File - Settings - Editor - File Encodings 里把 Project Encoding 和 Default encoding for properties files 都改成 GBK重启 IDE 再试。这里说一下很多人的习惯是立刻把所有文件转成 UTF-8我一般不推荐这样做因为项目里既有 Java 文件又有 XML 配置文件配置文件的 encoding 声明还在批量转换后容易搞混。如果你的包是运行在 Linux 服务器上部署那确实需要统一 UTF-8如果只是本地跑完答辩保持 GBK 反而省事。如果你平时用 VSCode 比较多配 JavaEE 环境看这类 Spring 老项目也不轻松它就是看代码方便跑 Tomcat 还是 IDEA 或 Eclipse 天然省心。6. 把课程设计改成自己的作品低成本改造与答辩演示顺序跑通只是第一步你要交的是能讲、能改、能演示的作品。这套 SSH-oracle 健康管理系统最大的价值不在于原始功能而在于换数据库、加功能、讲原理三条路你都走熟了。下面说三个最实用的方向。6.1 换数据库Oracle 换成 MySQL 的最小改动清单如果你的答辩环境装不了 Oracle换 MySQL 是成本最可控的路径。改动集中在三处驱动、连接串、方言。驱动从 ojdbc 换成com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver连接串换成jdbc:mysql://localhost:3306/health?useUnicodetruecharacterEncodingutf8方言换成org.hibernate.dialect.MySQL5Dialect。数据库那边把 NUMBER 改成 INT 或 DOUBLEVARCHAR2 改成 VARCHAR序列和触发器删掉主键改用 AUTO_INCREMENT。Hibernate 实体里的主键生成策略从 SEQUENCE 改成 IDENTITY其余代码几乎不动。换完跑一遍登录和新增档案半小时能验证完成。6.2 加一个体检趋势图给档案页做近 12 个月的统计接口课程设计最加分的改动是给系统加一张可视化图表。常见做法是一个统计接口返回近 12 个月的体检记录数量。直接在现有 HealthAction 里加一个统计方法写一段 HQL 按月分组public String monthlyCount() { String hql select substr(to_char(c.checkDate, yyyy-mm), 1, 7), count(*) from CheckRecord c group by substr(to_char(c.checkDate, yyyy-mm), 1, 7); ListObject[] result dao.findByHql(hql); // 转成 JSON 写入 response // 页面上用前端图表库画折线图 return NONE; }前端页面用开源的图表库画折线图展示近一年体检趋势。这个改进的答辩价值非常高它证明你同时掌握了 HQL 聚合查询、JSON 交互和前端可视化。比起改业务逻辑加图表是性价比最高的亮点。6.3 答辩演示顺序先讲调用链再讲分页最后讲事务边界演示时的讲解顺序比功能本身更重要。你打开登录页时不要直接输入账号密码点登录先对着代码说一遍链路浏览器请求被 Struts2 过滤器拦截根据 struts.xml 找到 LoginActionAction 通过 Spring 注入的 UserService 调用 DaoDao 用 Hibernate 的 Session 执行 Oracle 查询。这段话讲明白等于告诉老师你理解 SSH 的实质。进入健康档案列表页后讲分页。把 Dao 里的 setFirstResult 和 setMaxResults 指给老师看随口说出它底层生成的 Oracle rownum 分页 SQL老师立刻知道你踩过坑、查过源码。最后在新增档案时讲 Transactional 事务边界说明 getCurrentSession 为什么必须依赖 Spring 事务以及异常处理不当会造成数据半截提交。我自己的习惯是拿到这类包先不急着点运行先把 sql 脚本在 Oracle 里执行一遍再对着论文目录和 Action 命名把功能点列出来半小时内就能判断这套代码值不值得花时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表