2026/10/28 4:04:27

SSH架构详解:Spring+Struts+Hibernate分层原理与实战

SSH架构详解:Spring+Struts+Hibernate分层原理与实战 简介本资源是一份面向Java Web初学者与中级开发者的三层架构系统性学习文档聚焦企业级Web应用的结构设计与SSH框架整合实践。文档深入剖析表示层Struts实现MVC、业务逻辑层Spring IoC/DI与AOP及数据访问层Hibernate ORM的职责划分与协同机制并通过SSH组合方案说明如何构建高内聚、低耦合、易维护的J2EE应用。资源为1个Word文档.doc全文约102KB内容涵盖框架原理、组件交互图示、配置要点、集成案例及关键词索引结构清晰、术语规范适合作为课堂讲义补充或项目开发前的技术预研材料。目前已有2376人学习下载读者可直接获取完整架构设计思路、SSH各层代码组织范式、struts-config.xml与hibernate.cfg.xml等核心配置说明以及基于Spring管理Struts Action的松耦合实践细节。1. 为什么今天还要讲 SSH 三层架构不是早被 Spring Boot 淘汰了吗很多人看到“SSHSpring 2.5 Struts 2.0 Hibernate 3.2”第一反应是这版本连 JDK 6 都没跑稳现在还讲它——但恰恰是这种“过时组合”成了理解 Java Web 架构演进的黑匣子式入口。我带过三届毕设、参与过五个遗留系统重构发现一个血泪经验凡是跳过 SSH 直接上 Spring Boot 的学生在答辩时被问到“Controller 怎么拿到 Service 实例”“事务为什么在 Service 层生效而不在 Action 里生效”“Hibernate Session 在哪一级被打开/关闭”十有八九答不全。因为 Spring Boot 把 IoC 容器、MVC 调度、ORM 会话管理全封装成自动配置你用得顺却看不见“控制权移交”的关键断点。而 SSH 正好相反它把每一层的边界、每一处依赖注入的时机、每一次 session 绑定的上下文都明明白白写在 XML 里、暴露在代码中。这份资源不是教你怎么快速上线而是帮你亲手搭一遍骨架——从 struts-config.xml 的action映射到 applicationContext.xml 的bean注入再到 hibernate.cfg.xml 的session-factory初始化所有环节可调试、可打断点、可逐行验证。它适合两类人一是正在做 Java Web 课程设计/毕业设计、需要交一份“能讲清楚每一层怎么协作”的完整方案的学生二是刚接手老系统、面对满屏ActionSupport和HibernateDaoSupport却不敢动的老项目维护者。这不是怀旧是补课。2. 总体架构设计四层职责划分与 SSH 各组件的精准卡位SSH 不是简单拼凑三个框架而是通过职责锚定让每个框架只干一件事并用 XML 配置强行划清边界。很多初学者翻车就翻在没搞懂“谁该管什么、谁不该碰什么”。我们先看这张实际项目中画在白板上的架构草图非 UML是工程师手绘逻辑┌─────────────────┐ HTTP 请求 ┌──────────────────┐ │ 表示层 │ ◀──────────────▶ │ 业务逻辑层 │ │ (Struts) │ │ (Spring) │ ├─────────────────┤ ├──────────────────┤ │ • JSP 页面 │ │ • UserServiceImpl │ │ • Action 类 │ ←─ 依赖注入 ─→ │ • TransactionProxy│ │ • struts-config.xml │ │ • applicationContext.xml │ └─────────────────┘ └──────────────────┘ ▲ ▲ │ │ └─────── 依赖注入 ─────────────┘ │ ┌──────────────────┐ │ 数据持久层 │ │ (Hibernate) │ ├──────────────────┤ │ • User.hbm.xml │ │ • UserDaoImpl │ │ • hibernate.cfg.xml │ └──────────────────┘注意这里明确标出“四层”而非“三层”是因为在真实 SSH 项目中Domain Model领域模型层是独立存在的物理层它不归 Struts 管、不归 Spring 管、也不归 Hibernate 管——它只是被各方引用的 POJO 集合。这个细节常被忽略却是解耦的关键。2.1 表示层Struts 的 MVC 切口必须落在 Action而非 JSPStruts 的核心价值不是标签库而是强制你在 Controller 层做决策。很多学生把业务逻辑写进 JSP 的% %脚本里或在 Action 中直接 new Service 对象这就彻底废掉了分层意义。正确做法是所有 JSP 只做两件事展示数据EL 表达式、提交表单s:form标签所有流程控制必须由 Action 类承接且 Action不能持有 DAO 或 Service 的 new 实例Action 必须通过 Spring 注入获得 Service 引用后面会给出 XML 配置实证提示Struts 的ActionSupport类本身不包含业务逻辑它只是提供addActionError()、getFieldErrors()等辅助方法。真正的业务入口是重写的execute()方法而这个方法里唯一该出现的调用就是userService.addUser(user)这类 Service 接口调用。2.2 业务逻辑层Spring 的 IoC 容器是整套架构的“交通指挥中心”Spring 在 SSH 中不是“配角”而是唯一有权决定对象生命周期和依赖关系的中枢。它的配置文件applicationContext.xml就是整个系统的“宪法”。这里必须强调一个反直觉设计Struts 的 Action 也要交给 Spring 管理而不是由 Struts 自己实例化。原因很简单——只有 Spring 管理的 Bean才能被注入 Service 和 DAO。下面是一段真实项目中applicationContext.xml的关键片段已脱敏保留原始结构!-- 声明 UserService 接口的实现类 -- bean iduserService classcom.simon.oa.service.impl.UserServiceImpl property nameuserDao refuserDao/ /bean !-- 声明 UserDao 接口的实现类 -- bean iduserDao classcom.simon.oa.dao.hibernate.UserDaoImpl property namesessionFactory refsessionFactory/ /bean !-- 关键声明 Struts Action让 Spring 创建它 -- bean iduserAction classcom.simon.oa.controller.UserAction scopeprototype property nameuserService refuserService/ /bean注意三点scopeprototype是必须的因为 Struts 每次请求都会创建新 Action 实例Spring 若用默认 singleton会导致多线程并发问题userAction的userService属性被refuserService注入而userService又注入了userDao形成完整依赖链userDaoImpl依赖sessionFactory这个 sessionFactory 由 Hibernate 配置提供下节详述。这套注入链条一旦写错启动时就会报BeanCreationException但正是这种“报错即定位”的特性让你一眼看清哪一层断了。2.3 数据持久层Hibernate 的 SessionFactory 是数据库连接的“总闸门”Hibernate 在 SSH 中不负责事务控制那是 Spring 的事也不负责对象创建那是 Spring 的事它只干一件事把 Java 对象和 SQL 语句之间的映射关系翻译出来并提供统一的 Session 操作入口。所以它的核心配置hibernate.cfg.xml必须精简、稳定、与 Spring 解耦?xml version1.0 encodingUTF-8? !DOCTYPE hibernate-configuration PUBLIC -//Hibernate/Hibernate Configuration DTD 3.0//EN http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd hibernate-configuration session-factory !-- 数据库连接信息 -- property nameconnection.driver_classcom.mysql.jdbc.Driver/property property nameconnection.urljdbc:mysql://localhost:3306/oa_db?useSSLfalse/property property nameconnection.usernameroot/property property nameconnection.password123456/property !-- JDBC 连接池 -- property namec3p0.min_size5/property property namec3p0.max_size20/property !-- Hibernate 行为 -- property namedialectorg.hibernate.dialect.MySQLDialect/property property namehbm2ddl.autoupdate/property property nameshow_sqltrue/property property nameformat_sqltrue/property !-- 映射文件位置 -- mapping resourcecom/simon/oa/model/User.hbm.xml/ /session-factory /hibernate-configuration关键参数说明hbm2ddl.autoupdate开发阶段神器每次启动自动比对实体与表结构缺字段就加不删表——但上线必须改为validate或删掉否则是生产事故c3p0.*必须显式配置连接池否则 Hibernate 默认用单连接高并发直接挂mapping标签指向的是.hbm.xml文件路径不是 Java 类路径这是新手最常写错的地方写成com.simon.oa.model.User就会找不到映射。2.4 领域模型层POJO .hbm.xml 是 ORM 的“契约文本”很多人以为 Hibernate 就是写个Entity注解但在 SSH 时代映射关系必须用 XML 显式声明这是强制你思考“对象如何对应表”的过程。以用户表为例User.hbm.xml内容如下?xml version1.0 encodingUTF-8? !DOCTYPE hibernate-mapping PUBLIC -//Hibernate/Hibernate Mapping DTD 3.0//EN http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd hibernate-mapping packagecom.simon.oa.model class nameUser tablet_user id nameid columnid generator classnative/ /id property nameusername columnusername length50 not-nulltrue/ property namepassword columnpassword length100 not-nulltrue/ property nameemail columnemail length100/ property namecreateTime columncreate_time typetimestamp/ /class /hibernate-mapping设计说明package属性定义了 Java 类的包名nameUser是类名tablet_user是数据库表名三者必须严格对应id块声明主键generator classnative/表示根据数据库类型自动选策略MySQL 用 auto_incrementOracle 用 sequenceproperty的column属性必须和数据库字段名完全一致区分大小写length和not-null是校验规则影响 Hibernate 生成的 DDLtypetimestamp显式指定 JavaDate类型映射为数据库DATETIME避免默认映射成DATE导致时间精度丢失。这个 XML 就是 Hibernate 的“契约”只要它存在且正确你改 Java 类字段名、改数据库字段名、甚至换数据库类型都只需改这里其他层代码零修改。3. 模块实现详解以用户管理模块为例手把手走通完整调用链我们不再讲抽象概念而是用一个真实可运行的模块——用户管理从数据库建表开始一路走到浏览器显示“添加成功”。所有代码均来自某高校课程设计真实项目已去除业务敏感信息保留全部技术细节。3.1 数据库准备建表语句与字段设计依据用户表t_user不是随便建的字段设计直指 SSH 架构约束CREATE TABLE t_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号唯一, password varchar(100) NOT NULL COMMENT BCRYPT 加密后的密码, email varchar(100) DEFAULT NULL COMMENT 邮箱用于找回密码, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户基本信息表;设计依据id用bigintHibernate 的native主键生成器在 MySQL 下默认用AUTO_INCREMENT需匹配bigint防止溢出password长度设为 100为 BCrypt 加密预留空间BCrypt 输出约 60 字符加盐后更长create_time设DEFAULT CURRENT_TIMESTAMPHibernate 的CreationTimestamp注解在 XML 映射中对应typetimestamp必须数据库支持唯一索引uk_username业务强约束Hibernate 的uniquetrue只校验内存数据库层必须兜底。3.2 领域模型层User.java 与 User.hbm.xml 的双向绑定User.java是标准 POJO无任何框架注解纯 JavaBeanpackage com.simon.oa.model; import java.util.Date; public class User { private Long id; private String username; private String password; private String email; private Date createTime; // getter/setter 省略必须有否则 Hibernate 反射失败 public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime createTime; } }注意id类型必须是Long包装类不能是long基本类型。因为 Hibernate 需要通过null判断对象是否为新记录id null表示 insert否则 update。User.hbm.xml已在上一节给出此处强调一个易错点Java 类属性名与 XML 中name属性必须完全一致包括大小写。比如setCreateTime()对应namecreateTime若写成namecreate_timeHibernate 启动时报PropertyNotFoundException但错误堆栈极深新手往往查半天才发现是大小写问题。3.3 数据访问层UserDao 接口与 Hibernate 实现类按规范DAO 必须先定义接口再写实现这是为将来替换 ORM 框架如换成 MyBatis留后路// com.simon.oa.dao.UserDao.java package com.simon.oa.dao; import com.simon.oa.model.User; import java.util.List; public interface UserDao { int add(User user); ListUser findAll(); User findById(Long id); void update(User user); void delete(Long id); }实现类继承HibernateDaoSupport这是 Spring 对 Hibernate 的封装基类它自动持有SessionFactory// com.simon.oa.dao.hibernate.UserDaoImpl.java package com.simon.oa.dao.hibernate; import com.simon.oa.dao.UserDao; import com.simon.oa.model.User; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import java.util.List; public class UserDaoImpl extends HibernateDaoSupport implements UserDao { Override public int add(User user) { // save() 返回 Serializable转成 Long 更安全 Long id (Long) this.getHibernateTemplate().save(user); return id.intValue(); } Override public ListUser findAll() { // loadAll() 是 HibernateTemplate 封装的便捷方法 return this.getHibernateTemplate().loadAll(User.class); } Override public User findById(Long id) { return (User) this.getHibernateTemplate().get(User.class, id); } Override public void update(User user) { this.getHibernateTemplate().update(user); } Override public void delete(Long id) { User user findById(id); if (user ! null) { this.getHibernateTemplate().delete(user); } } }关键说明getHibernateTemplate()是父类HibernateDaoSupport提供的模板对象它封装了Session获取、事务绑定、异常转换等底层操作save()方法返回Serializable实际是Long强制转型是安全的但intValue()可能丢失精度ID 超过Integer.MAX_VALUE时生产环境建议用longValue()loadAll(User.class)是全表查询性能差仅用于演示真实项目必须用 HQL 分页查询如getHibernateTemplate().find(from User u where u.status ?, status)。3.4 业务逻辑层UserService 接口与事务控制实现Service 层是事务边界所有数据库操作必须在此层完成。UserService接口定义业务契约// com.simon.oa.service.UserService.java package com.simon.oa.service; import com.simon.oa.model.User; import java.util.List; public interface UserService { int addUser(User user); ListUser listAllUsers(); User getUserById(Long id); void updateUser(User user); void deleteUser(Long id); }实现类UserServiceImpl注入UserDao并用 Spring 声明式事务// com.simon.oa.service.impl.UserServiceImpl.java package com.simon.oa.service.impl; import com.simon.oa.dao.UserDao; import com.simon.oa.model.User; import com.simon.oa.service.UserService; import org.springframework.transaction.annotation.Transactional; import java.util.List; public class UserServiceImpl implements UserService { private UserDao userDao; // 提供 setter供 Spring 注入 public void setUserDao(UserDao userDao) { this.userDao userDao; } Override Transactional // 关键此注解让 Spring 在方法前后开启/提交事务 public int addUser(User user) { // 业务校验用户名不能重复 if (userDao.findById(getUserIdByUsername(user.getUsername())) ! null) { throw new RuntimeException(用户名已存在); } return userDao.add(user); } Override Transactional(readOnly true) // 查询方法设为只读提升性能 public ListUser listAllUsers() { return userDao.findAll(); } Override Transactional(readOnly true) public User getUserById(Long id) { return userDao.findById(id); } Override Transactional public void updateUser(User user) { userDao.update(user); } Override Transactional public void deleteUser(Long id) { userDao.delete(id); } // 辅助方法根据用户名查 ID用于校验 private Long getUserIdByUsername(String username) { // 此处应写 HQL 查询为简化演示用循环遍历实际不可取 for (User u : userDao.findAll()) { if (u.getUsername().equals(username)) { return u.getId(); } } return null; } }注意Transactional注解必须配合 Spring 的事务管理器配置才生效。applicationContext.xml中需添加bean idtransactionManager classorg.springframework.orm.hibernate3.HibernateTransactionManager property namesessionFactory refsessionFactory/ /bean tx:annotation-driven transaction-managertransactionManager/若漏掉tx:annotation-drivenTransactional注解完全无效方法内抛异常也不会回滚。3.5 表示层Struts Action 与 JSP 的最小化协作Action 是瘦控制器只做三件事接收参数、调用 Service、转发结果。UserAction.java如下// com.simon.oa.controller.UserAction.java package com.simon.oa.controller; import com.simon.oa.model.User; import com.simon.oa.service.UserService; import com.opensymphony.xwork2.ActionSupport; import java.util.List; public class UserAction extends ActionSupport { private UserService userService; private User user; // 用于接收表单参数Struts 自动注入 private ListUser userList; // 用于传递查询结果到 JSP // 提供 setter供 Spring 注入 public void setUserService(UserService userService) { this.userService userService; } // 添加用户 public String add() { try { userService.addUser(user); this.addActionMessage(用户添加成功); return SUCCESS; } catch (Exception e) { this.addActionError(添加失败 e.getMessage()); return ERROR; } } // 查询所有用户 public String list() { try { userList userService.listAllUsers(); return SUCCESS; } catch (Exception e) { this.addActionError(查询失败 e.getMessage()); return ERROR; } } // getter 供 JSP EL 表达式使用 public User getUser() { return user; } public void setUser(User user) { this.user user; } public ListUser getUserList() { return userList; } }JSP 页面listUser.jsp极简只用 Struts 标签% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixs uri/struts-tags % html headtitle用户列表/title/head body h2用户管理/h2 !-- 显示操作消息 -- s:actionmessage/ s:actionerror/ !-- 添加用户表单 -- s:form actionaddUser methodpost s:textfield nameuser.username label用户名/ s:password nameuser.password label密码/ s:textfield nameuser.email label邮箱/ s:submit value添加用户/ /s:form !-- 用户列表 -- s:if testuserList.size() 0 table border1 trthID/thth用户名/thth邮箱/thth注册时间/thth操作/th/tr s:iterator valueuserList varu tr tds:property value#u.id//td tds:property value#u.username//td tds:property value#u.email//td tds:property value#u.createTime//td tda hrefdeleteUser?ids:property value#u.id/删除/a/td /tr /s:iterator /table /s:if s:else p暂无用户/p /s:else /body /html关键点nameuser.usernameStruts 会自动调用UserAction.setUser(new User())再调用User.setUsername()前提是User有无参构造器和setters:actionmessage/和s:actionerror/显示addActionMessage()和addActionError()设置的消息s:iterator遍历userList#u是当前迭代项value#u.id等价于((User)u).getId()。3.6 配置串联struts-config.xml 与 applicationContext.xml 的联动最后一步把所有层串起来。struts-config.xml告诉 Struts“当收到/listUser.do请求时去 Spring 容器里找userAction这个 Bean 来处理”?xml version1.0 encodingUTF-8? !DOCTYPE struts-config PUBLIC -//Apache Software Foundation//DTD Struts Configuration 2.0//EN http://struts.apache.org/dtds/struts-config_2_0.dtd struts-config !-- 配置 Spring 插件让 Struts 使用 Spring 管理的 Action -- plug-in classNameorg.apache.struts.plugins.SpringPlugin set-property propertypath value/WEB-INF/applicationContext.xml/ /plug-in action-mappings !-- 查询用户列表 -- action path/listUser typeuserAction nameuserForm scoperequest validatefalse input/listUser.jsp forward namesuccess path/listUser.jsp/ forward nameerror path/listUser.jsp/ /action !-- 添加用户 -- action path/addUser typeuserAction nameuserForm scoperequest validatetrue input/listUser.jsp forward namesuccess path/listUser.jsp/ forward nameerror path/listUser.jsp/ /action /action-mappings /struts-config重点说明plug-in标签加载 Spring 插件并指定applicationContext.xml路径这是 Struts 认识 Spring Bean 的前提action typeuserAction中的userAction是applicationContext.xml中定义的 bean id不是类全名validatetrue表示启用 Struts 的validate()方法校验需在UserAction中重写此处设为false因为校验已在 Service 层完成。至此一次完整的请求链就打通了浏览器访问/listUser.do→ Struts 的ActionServlet解析 → 查struts-config.xml找到typeuserAction→ 去 Spring 容器取userActionBean → 调用其list()方法 →list()调用userService.listAllUsers()→userService调用userDao.findAll()→userDao通过HibernateTemplate执行 HQL → 结果返回 JSP 渲染。4. 常见问题排查五个必踩的坑与现场急救方案SSH 架构的“可调试性”是双刃剑它让你看清每一步但也意味着每一步都可能出错。以下是我在指导学生和接手老项目时高频遇到的五个问题按现象、原因、解决三步法整理全是血泪经验。4.1 现象启动 Tomcat 报ClassNotFoundException: org.springframework.web.context.ContextLoaderListener原因web.xml中未配置 Spring 上下文监听器或spring-web-x.x.jar未放入WEB-INF/lib。解决检查web.xml是否有以下配置必须在servlet之前listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-value/WEB-INF/applicationContext.xml/param-value /context-param确认WEB-INF/lib下存在spring-web-2.5.6.jar版本需与spring-core一致若用 MyEclipse 自动生成检查 Build Path → Libraries → Deployment Assembly确保 Spring JAR 被勾选导出。4.2 现象Action 中userService为 null调用时报NullPointerException原因userActionBean 未被 Spring 管理或applicationContext.xml中ref名称写错。解决检查struts-config.xml是否配置了plug-in加载 Spring检查applicationContext.xml中userAction的class属性是否写错包名如com.simon.oa.controller.UserAction写成com.simon.oa.action.UserAction检查userAction的property名称是否与UserAction.java中的 setter 方法名一致setUserService()对应nameuserService不能写成nameUserService在UserAction构造器中加日志System.out.println(UserAction created)若没打印说明 Struts 没创建它而是 Spring 创建失败。4.3 现象userDao.findAll()返回空列表但数据库有数据或save()后数据库无记录原因Hibernate Session 未绑定到当前线程或事务未提交。解决检查applicationContext.xml是否配置了OpenSessionInViewFilterWeb 层或HibernateInterceptorService 层确认UserServiceImpl类上是否有Transactional且applicationContext.xml中有tx:annotation-driven在UserDaoImpl的findAll()方法开头加日志System.out.println(Session: this.getSessionFactory().getCurrentSession())若输出null说明 Session 未开启检查hibernate.cfg.xml中connection.url的数据库名是否正确端口是否被防火墙拦截。4.4 现象JSP 中s:property valueuserList/显示[]但s:iterator无法遍历原因userList是ListUser但 Struts 的 OGNL 表达式解析时类型丢失或UserAction的 getter 方法未正确暴露。解决在UserAction中确认getUserList()方法返回ListUser且userList字段是private ListUser userList;在 JSP 中用s:debug/标签查看值栈内容确认userList是否在 root 中改用s:property valueuserList.size()/测试是否能取到 size若为 0 则是 Service 层没查到数据若userList有数据但遍历失败尝试在struts.properties中添加struts.ognl.allowStaticMethodAccesstrue不推荐仅调试用。4.5 现象中文乱码表单提交的用户名在数据库存为??或 JSP 显示????原因Tomcat、数据库、JSP、Hibernate 四处编码不统一。解决四步闭环Tomcatconf/server.xml中Connector添加URIEncodingUTF-8数据库MySQL 连接 URL 加?useUnicodetruecharacterEncodingUTF-8JSP页面顶部% page contentTypetext/html;charsetUTF-8 %Hibernatehibernate.cfg.xml中加property nameconnection.useUnicodetrue/property和property nameconnection.characterEncodingUTF-8/property。提示改完必须重启 Tomcat且清空浏览器缓存否则旧 JS/CSS 缓存会干扰测试。5. 验收与答辩用三张表、两个演示、一套话术拿下毕设高分答辩不是背诵 PPT而是证明你真的“跑通了、看懂了、能改了”。我带过的毕设凡拿优秀的学生都有一套标准化验收动作。这套动作不花哨但直击老师最关心的三个问题“你做的东西真能跑”“你真理解每层干嘛”“如果需求变了你能改吗”下面是我的实战清单。5.1 验收三张表让老师一眼看懂你的工作量不要只说“我做了用户管理”要用表格量化。答辩前打印三张 A4 纸夹在报告书里老师翻到就信你。表 1模块功能覆盖表证明完整性功能点实现方式对应文件/配置是否通过测试用户添加Action 调用 ServiceService 调用 DAOUserAction.add()/UserServiceImpl.addUser()/UserDaoImpl.add()✔️用户列表查询Struts 转发 → Service → DAO → JSP 迭代struts-config.xml/UserAction.list()/listUser.jsp✔️用户删除URL 参数传 IDService 层事务控制deleteUser?id1/UserServiceImpl.deleteUser()/Transactional✔️输入校验Service 层用户名唯一性检查UserServiceImpl.addUser()中findById()查询✔️错误提示StrutsaddActionError() JSPs:actionerror/UserAction.add()/listUser.jsp✔️表 2架构分层对照表证明理解深度层级你用了什么技术你为什么选它一句话你没用它干什么划清边界表示层Struts 2.0强制 MVC 分离Action 是唯一入口杜绝 JSP 写逻辑不在 JSP 里 new Service不写 SQL业务逻辑层Spring 2.5IoC 管理所有 Bean含 ActionTransactional 控制事务不在 Action 里 new Service不手动开事务数据持久层Hibernate 3.2POJO 与表映射解耦HQL 屏蔽 SQL 差异不在 Service 里写 JDBC不手动管理 Connection领域模型层User.java User.hbm.xmlXML 显式声明映射改表结构只需改这里其他层不动User.java 无任何框架注解纯 JavaBean表 3配置文件关键参数表证明动手能力配置文件关键参数你设的值为什么这么设老师最爱问web.xmlContextLoaderListener✅ 已本文还有配套的精品资源点击获取