资讯动态

SSH车辆管理系统架构拆解:从Struts配置到Oracle部署

发布时间:2026/9/17 15:57:36 来源:尧图企业网站定制
简介基于 Struts、Hibernate、Spring 与 Oracle 数据库构建的车辆管理系统完整项目面向 Java Web 入门及进阶学习者也适合毕业设计或课程设计场景用于解决车辆状态、维修记录、保养周期、燃油消耗等信息的统一管理问题。工程严格按 MVC 设计思路分层控制器、业务服务、DAO 持久层与 JSP 视图清晰分离完整呈现 SSH 框架的整合方式。压缩包共 594 个文件主要包含 122 个 Java 源码及编译后 class、112 个 jar 依赖库、68 个 JSP 页面、54 个 XML 配置以及 GIF、JS、CSS、HTML 等前端资源整体大小约 35.89MBJava 与 class 对应核心业务代码jar 支撑框架运行JSP 和静态资源组成展示层XML 与 properties 负责框架及数据库连接配置便于按类型检索和学习。已有 504 人浏览学习。从工程类结构可梳理出登录认证、车辆档案、油耗管理、驾驶员管理、年检管理等业务模块通过阅读 Service 实现类、DAO 映射及 Oracle 表结构可理解事务控制、依赖注入、ORM 映射等关键写法是系统学习 SSH 项目开发的完整参考资料。1. 一套 SSH 车辆管理系统拆包之后先看 class 文件再谈架构拿到手的是一个带 .rar 后缀的编译产物里面LoginAction.class、CarbstbDAO.class、UserServiceImpl.class一目了然这不是前后端分离项目也不是 Spring Boot 工程而是典型的 Struts Hibernate Spring Oracle 组合。Carbstb对应车辆台账Driverm是驾驶员主档Oilm管油料Annum管年审——把这些实体类名和 DAO 类名拼起来整个系统的业务边界就出来了。它适合两类人一类是想把课堂上的 SSH 理论落成可运行工程的在校生另一类是手头正在维护老项目、需要快速摸清请求链路的在职工程师——UcapliDAO管用车申请OutcmDAO管出车记录从这些命名能直接反推数据库表结构这是这包资料最值钱的地方。2. Struts 层拆解从 LoginAction 到 DAO一条 URL 请求在 SSH 项目里怎么走完Struts 在 SSH 里只承担 MVC 中的 Controller 和 View 调度。你要判断这套系统用的是 Struts1 还是 Struts2最直接的办法就是看 Action 是否继承框架基类LoginAction.class和UserServiceImpl.class放在一起且没有明显的 ActionSupport 痕迹基本可以按 Struts1 的execute方法来解读。Struts1 用ActionServlet作为前端控制器所有.do请求先进它再由struts-config.xml分发给具体 Action。2.1 请求路由先看 struts-config.xmlAction 的 path 就是 URL在 Struts1 项目中WEB-INF/classes下一定有一份struts-config.xml它定义了所有请求的映射关系。下面是一个典型的登录配置片段struts-config !-- 表单 Bean 与 JSP 表单字段绑定 -- form-beans form-bean nameloginForm typecom.vehicle.web.form.LoginForm/ /form-beans action-mappings !-- path 即浏览器访问的 /login.do -- action path/login typecom.vehicle.web.action.LoginAction nameloginForm scoperequest validatetrue input/login.jsp !-- Action 返回逻辑名后由框架转发到对应页面 -- forward namesuccess path/main.jsp/ forward namefailed path/login.jsp?error1/ /action /action-mappings /struts-config这段配置里path/login表示浏览器访问/login.do时进入该 Actiontype指定处理类全限定名nameloginForm让框架自动把表单字段封装进LoginForm对象scoperequest控制表单对象存放到 request 还是 sessioninput是校验失败后的回退页。forward的name是逻辑跳转名Action 里返回字符串后由它决定目标页面。注意validatetrue时框架会先调用表单 Bean 的validate()方法如果账号密码为空请求会直接打回input页面根本不进execute。2.2 execute 方法里的标准动作取参数 → 调 Service → 返回 ForwardStruts1 的 Action 生命周期中框架只替你做参数封装和转发调度真正的业务逻辑要交给 Service。看LoginAction的典型写法public class LoginAction extends Action { Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { // 从 ActionForm 获取页面提交参数 LoginForm loginForm (LoginForm) form; String username loginForm.getUsername(); String password loginForm.getPassword(); // 从工厂拿 Service内部实际取 Spring 容器中的 Bean UserService userService ServiceFactory.getUserService(); User user userService.login(username, password); if (user ! null) { // 登录成功写入 session跳转主页面 request.getSession().setAttribute(currentUser, user); return mapping.findForward(success); } return mapping.findForward(failed); } }这段代码的关键在ServiceFactory.getUserService()。在这个包里的.class文件中能看到UserServiceImpl说明 Service 接口和实现是分开的。ServiceFactory是一个简单的静态工厂内部通常从 Spring 容器里取userService这个 Bean——如果直接new UserServiceImpl()等于是绕开了 Spring 的事务管理。登录这个操作没有事务问题还好一旦后续执行出车登记、油料扣减这种多表写入没有 Spring 代理的方法就会各自提交数据一致性立刻出问题。2.3 从 DAO 类名反推业务模块先建立表资源地图CarbstbDAO、DrivermDAO、OilmDAO、AnnumDAO、OutcmDAO、UcapliDAO、XxuserDAO这七个 DAO 类已经把这个系统的模块边界标得很清楚类名对应业务域常见表名推测用途XxuserDAO系统用户XX_USER账号登录、权限控制CarbstbDAO车辆台账CAR_BASE车辆基本信息、状态DrivermDAO驾驶员档案DRIVER_MAIN驾驶员信息、准驾车型UcapliDAO车辆申请USE_CAR_APPLI用车申请单OutcmDAO出车管理CAR_OUT_CM出车记录、归还记录OilmDAO油料管理OIL_MAIN加油记录、油费统计AnnumDAO年审管理AVE_YEAR_NUM年检、保险日期提醒画这张表的意义在于接手一个只有.class和配置文件的项目第一件事不是看 JSP而是先把 DAO 类对应到表。OilmDAO里的方法大概率是findByDateRange、sumOilFee这类统计操作AnnumDAO里则是查最近到期记录。后续做报表或加功能时直接以这个映射为基础扩展比反复倒推 XML 要快得多。Struts 层本身不写 SQL它只负责把页面上来的请求参数转成 Service 方法入参再把返回结果塞到 request 里。3. Hibernate 持久层Carbstb 实体映射、one-to-many 配置与 Oracle 方言坑Carbstb和Driverm这些类名后半段的tb、m是表前缀简写这种命名在老旧项目里很常见意味着 Hibernate 映射关系多数写在.hbm.xml里而不是用注解。实体类负责把 Java 对象与 Oracle 表结构完成双向绑定CarbstbDAO里任何findById或findByProperty调用最终都会翻译成对CAR_BASE表的 SQL。3.1 实体类与表字段的映射关系Carbstb 的 hbm 配置假设车辆台账表的主要字段是ID、CAR_NO车牌号、CAR_TYPE车辆类型、BUY_DATE购置日期、STATE在库/出车/维修对应的映射文件开头的写法如下hibernate-mapping packagecom.vehicle.entity !-- 车辆台账表Java 类与 Oracle 表一一对应 -- class nameCarbstb tableCAR_BASE !-- 主键使用 Oracle 序列生成 -- id nameid columnID typelong generator classsequence param namesequenceSEQ_CAR_BASE/param /generator /id property namecarNo columnCAR_NO typestring length20 not-nulltrue/ property namecarType columnCAR_TYPE typestring length10/ property namebuyDate columnBUY_DATE typedate/ property namestate columnSTATE typeinteger/ !-- 一辆车对应多条出车记录一对多集合用 set 表达 -- set nameoutRecords tableCAR_OUT_CM inversetrue cascadesave-update key columnCAR_ID/ one-to-many classOutcm/ /set /class /hibernate-mapping主键生成方式选择了sequence这是 Oracle 项目的标准做法对应数据库里必须预先存在SEQ_CAR_BASE序列如果换成 MySQL一般用identity或native。not-nulltrue限制车牌不能为空length20和 Oracle 里VARCHAR2(20)对应避免插入超长字符串时在 JDBC 层才报错。set里的inversetrue表示CAR_BASE不是关系维护方真正维护外键的是CAR_OUT_CM表中的CAR_ID字段这样保存车辆时不会产生多余的 update 语句。3.2 集合映射 one-to-many 的 fetch 策略直接影响出车记录查询性能用车申请和出车记录是一对多的典型场景Ucapli用车申请对应多次审批或多次出车。如果只配置one-to-many而不指定加载策略Hibernate 默认对集合使用延迟加载在 session 关闭后再调用getOutRecords()就会抛LazyInitializationException。常见做法是在映射中显式声明set nameapplyItems tableUCA_APPLY_ITEM lazytrue batch-size20 key columnAPPLY_ID/ one-to-many classUcapliItem/ /setlazytrue保证查询车辆列表时不会立即把每辆车的用车申请全查出来batch-size20会在遍历 N 辆车时把每 20 辆车对应的申请表一次性用IN查询装载把 1N 次查询压缩成 1⌈N/20⌉ 次。注意批量抓取只在延迟加载触发的时刻生效如果项目里出现“页面加载很慢单条 SQL 很快”的现象十有八九是这里没调好。3.3 Oracle 方言与分页别再直接把 LIMIT 带入 HQLHibernate 的dialect配置决定了它生成的 SQL 语法。Oracle 用Oracle10gDialect或Oracle12cDialect分页时 Hibernate 生成三层嵌套 ROWNUM 查询效果等价于SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT * FROM CAR_OUT_CM ORDER BY OUT_DATE DESC ) t WHERE ROWNUM 20 ) WHERE rn 10;这句 SQL 里内层先做排序和行数截断外层再按行号过滤偏移量。使用 HQL 时不需要手写 ROWNUM直接调用Query query session.createQuery(from Outcm o order by o.outDate desc); query.setFirstResult(10); query.setMaxResults(10); ListOutcm list query.list();setFirstResult(10)对应从第 11 行开始取setMaxResults(10)取 10 行。这里要特别注意在 Oracle 中如果查询没有明确的order by分页结果是不可预期的因为 ROWNUM 在排序前就被分配了。统计跨天数据时也常用trunc比如查当日油费总额String hql select sum(o.fee) from Oilm o where o.feeDate trunc(sysdate); ListLong result session.createQuery(hql).list();trunc(sysdate)在 Oracle 里会把时间截断到当天零点配合 HQL 参数化查询不会破坏索引。如果换成 MySQL要用DATE(o.feeDate)或CAST方言差异在这里体现得最直接。4. Spring 容器接管依赖注入、事务边界与 SSH 三条配置线怎么衔接Spring 在 SSH 项目中处于中间层的核心位置Struts 的 Action 从 Spring 拿 ServiceService 从 Spring 拿到 Hibernate 的 SessionFactory事务管理器控制 Service 方法的提交回滚。.class清单里UserServiceImpl这种写法默认就假设了 bean 定义在applicationContext.xml中Action 不可能直接 new 一个UserServiceImpl再手动管理事务否则就没有使用 Spring 的必要。4.1 applicationContext.xml 里的 bean 定义顺序配置文件里最核心的三组定义是数据源、SessionFactory、事务管理器!-- 数据源DBCP 连接池对应 Oracle 的 JDBC 连接信息 -- bean iddataSource classorg.apache.commons.dbcp.BasicDataSource destroy-methodclose property namedriverClassName valueoracle.jdbc.driver.OracleDriver/ property nameurl valuejdbc:oracle:thin:127.0.0.1:1521:ORCL/ property nameusername valuevehicle_app/ property namepassword valueencrypted_placeholder/ property nameinitialSize value2/ property namemaxActive value20/ /bean !-- SessionFactory 由 Spring 创建Hibernate 去加载映射文件 -- bean idsessionFactory classorg.springframework.orm.hibernate3.LocalSessionFactoryBean property namedataSource refdataSource/ property namemappingLocations valueclasspath:com/vehicle/entity/*.hbm.xml/ property namehibernateProperties props prop keyhibernate.dialectorg.hibernate.dialect.Oracle10gDialect/prop prop keyhibernate.show_sqltrue/prop prop keyhibernate.jdbc.batch_size20/prop /props /property /bean !-- Service 和 DAO 都交给容器管理依赖通过 setter 注入 -- bean iduserService classcom.vehicle.service.impl.UserServiceImpl property nameuserDAO refuserDAO/ /bean bean iduserDAO classcom.vehicle.dao.impl.XxuserDAOImpl property namesessionFactory refsessionFactory/ /beandataSource使用 DBCP 连接池initialSize和maxActive决定启动时建几个连接、峰值最多用几个。LocalSessionFactoryBean会把mappingLocations中所有 hbm 文件加载进 SessionFactoryhibernate.show_sqltrue在开发阶段打开生产环境要关掉。最关键的是userDAO要把sessionFactory注入进去DAO 实现类再通过getHibernateTemplate()或直接持有 Session 操作数据库。4.2 声明式事务把 rollback 规则写在配置文件里UserServiceImpl里如果只有login()一个读方法事务边界的作用不明显但涉及到出车登记插入CAR_OUT_CM同时更新CAR_BASE.STATE时这两个操作必须在一个事务内。Spring 用 AOP 声明事务的经典配置是!-- 事务管理器绑定到 Hibernate SessionFactory -- bean idtxManager classorg.springframework.orm.hibernate3.HibernateTransactionManager property namesessionFactory refsessionFactory/ /bean tx:advice idtxAdvice transaction-managertxManager tx:attributes !-- 写操作必须加入事务 -- tx:method nameadd* propagationREQUIRED/ tx:method nameupdate* propagationREQUIRED/ tx:method namedelete* propagationREQUIRED/ !-- 查询方法只读不做脏检查提升性能 -- tx:method namefind* read-onlytrue propagationSUPPORTS/ tx:method namelogin read-onlytrue/ tx:method name* propagationREQUIRED/ /tx:attributes /tx:advice aop:config aop:pointcut idservicePointcut expressionexecution(* com.vehicle.service.impl.*.*(..))/ aop:advisor advice-reftxAdvice pointcut-refservicePointcut/ /aop:config事务配置的作用范围是service.impl包下的所有方法。add*、update*、delete*以REQUIRED传播进来表示如果已经处于事务中就加入没有则新建read-onlytrue的声明让 Hibernate 跳过脏检查查询性能会好一些。最后一条*兜底避免新加的方法因为没匹配到前缀而漏掉事务。注意一旦切到 Service 层就不要再在 DAO 里调用session.beginTransaction()否则两处事务叠加会互相干扰。4.3 Spring 与 Struts 的桥接方式决定 Action 里能不能用依赖注入Struts1 的 Action 是框架自己new出来的不在 Spring 管控范围内要让 Action 能拿到 Spring 容器里的userService需要做两件事。第一件是让 Struts 启动时先加载 Spring 上下文在struts-config.xml里加插件plug-in classNameorg.springframework.web.struts.ContextLoaderPlugIn set-property propertycontextConfigLocation value/WEB-INF/applicationContext.xml/ /plug-in第二件是把 Action 的type指到 Spring 的代理类上由代理去容器里找真正的 Action Beanaction path/login typeorg.springframework.web.struts.DelegatingActionProxy nameloginForm scoperequest /actionDelegatingActionProxy会根据path自动到 Spring 容器中查找名为/login的 Bean。这种机制下Action 里只需要声明UserService的 setter 方法剩下的交给 Spring 完成。理解了这条链就掌握了 Spring 容器在这里的核心作用Struts 负责转发Service 层由 Spring 管理生命周期Bean 的创建、装配、代理都由容器统一控制这也正是“手写 Spring”和真正 SSM/SSH 工程之间最大的差别。5. 把编译产物部署回 Oracle 环境时最先碰到的三个现实问题到这里代码逻辑已经梳理清楚接下来需要把 .class 文件和配置文件还原成一个能跑的 Web 应用。这个步骤在实际操作中常被低估所以用一章专门讲验证手段。5.1 监听服务和 JDBC URL 对不上Hibernate 报错只会显示连接超时Oracle 安装完后最常见的故障就是监听服务无法启动报错日志里出现ORA-12541: TNS:no listener。先检查监听状态# 查看监听是否在运行 lsnrctl status # 启动监听服务 lsnrctl start # 测试服务名连通性 tnsping ORCLtnsping ORCL返回OK后再用 SQL*Plus 验证工具账号sqlplus vehicle_app/password127.0.0.1:1521/ORCL如果sqlplus能连上但 JDBC 连不上问题几乎都在 URL 的 service name 上。项目里如果是jdbc:oracle:thin:127.0.0.1:1521:ORCL这种写法连接的是 SID如果数据库装的是 12c 以后的 CDB/PDB 模式需要改成/pdborcl格式的 service name。手动改完applicationContext.xml里的url后不要直接重启应用服务器先执行一下sqlplus确认改法是否正确因为 Tomcat 打印出的堆栈往往是Io exception: The Network Adapter could not establish the connection不会告诉你具体是哪个配置值错了。5.2 用 SQL 验证核心表数据和序列状态部署前还要确认表、序列、测试数据都齐全。Hibernate 的hbm2ddl一般不设为update所以表要由 DBA 脚本建好。快速检查数据可用性-- 检查主表和流水表是否有数据 SELECT COUNT(*) FROM CAR_BASE; SELECT MAX(ID) FROM CAR_OUT_CM; -- 验证主键序列可用避免插入时报 ORA-02289 SELECT SEQ_CAR_BASE.NEXTVAL FROM DUAL; -- 出车费用按天汇总模拟页面月度统计报表 SELECT TRUNC(OUT_DATE) 出车日期, SUM(FEE) 费用合计 FROM CAR_OUT_CM WHERE OUT_DATE TRUNC(SYSDATE-30) GROUP BY TRUNC(OUT_DATE);第一段验证表是否为空、主键序列能否自增第二段是出车费用按天汇总直接对应页面上的油耗统计和月度报表。如果SUM(FEE)的结果在页面里显示成科学计数法问题出在 JDBC 映射或前端格式化可以在查询时用TO_CHAR(SUM(FEE),FM999,999.00)转成字符串再回传。5.3 排错顺序先看 Hibernate 生成的 SQL再查 Action 跳转SSH 项目排错和 Spring Boot 不同没有统一 starter 日志。最有效的下探方式是把show_sql打开并配好 log4j让日志输出标准 SQL 绑定参数然后按三层链条定位页面点按钮后 URL 是否变成预期的.doAction 返回的forward是否有对应定义最后看 SQL 执行结果。如果 SQL 没打印出来而页面报 500问题一定在 Spring 容器初始化或 Struts 插件加载阶段优先翻 Tomcat 启动日志里BeanCreationException所在的 Bean 名。这套从请求链路反查的排查方式适用于任何 Struts Hibernate Spring 的老项目比在 JSP 里逐行打断点要快得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价