ARTICLE DETAIL

资讯详情

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

SSM+JSP老年服务系统实战:从零搭建到部署全流程解析

SSM+JSP老年服务系统实战:从零搭建到部署全流程解析 “夕阳红”老年服务系统光看这个标题就知道是典型的Java Web实践项目基于SSMSpring SpringMVC MyBatis做后端JSP做前端页面给社区、养老机构做一套老年人信息与养老服务的管理后台。国内很多高校的Java课程设计、毕业设计都会选这类题目因为业务场景足够真实技术栈又非常经典既有框架整合的深度又有增删改查的完整闭环。我经手过好几个类似项目这篇文章就把这个“夕阳红”系统的完整落地过程拆开讲一遍从需求分析、数据库设计、代码实现到打包部署哪些地方最容易踩坑我也会直接点出来。适合正在做同类SSMJSP毕设、或者想快速上手传统Java Web项目的人参考。1. 项目拆解先想清楚再做1.1 这个项目到底要做什么很多同学拿到“基于SSMJSP的老年服务系统”这个题目第一反应是上网找模板然后照着改个LOGO和页面文字就交差。这样不是不行但如果答辩老师问两句很容易露馅。我建议先自己把需求理清楚再动手写代码哪怕最终功能一样你知道每一步为什么这么做效果完全不同。“夕阳红”老年服务系统的核心场景其实很清晰有一个管理员可能还有护工/工作人员要维护老人基础信息比如姓名、性别、出生日期、床位号、紧急联系人要给老人建立健康档案记录血压、血糖、心率、体温等体检数据要支持服务预约例如上门照护、送餐、保洁预约后由工作人员接单处理还要组织老年活动老人可以报名参加。顺着这条业务线走系统的基本功能就出来了。很多网上的题目描述里还会加一个资讯公告模块用来发布养老政策、健康科普这个我们也可以做逻辑和新闻发布一样难度不大。我拿到这类题目的习惯是先用一张纸把角色和操作列出来。管理员能做什么护工能做什么老人或者家属能不能登录查看信息——这三个问题的答案直接决定系统复杂度。如果是毕设推荐做成两种角色管理员统管全部护工可以维护健康记录和处理服务工单。如果题目没有明确要求老人端登录就别为了炫技去加一个手机端否则工作量会翻倍。1.2 为什么用SSMJSP而不是Spring BootVue这几乎是每个做SSM项目的人都会被问的问题都2025年了为什么还要用SSM搭传统JSP项目原因很简单它不是给生产环境用的是给学习过程用的。高校的Java课程体系里Spring、SpringMVC、MyBatis还是教学内容的主干SSM能让你把“三层架构”“控制层反转”“面向切面”这些概念亲手串一遍Spring Boot把大量配置自动完成了很多东西反而看不到原理。而JSP作为Java服务端页面技术虽然现在新项目很少用但如果将来要接手老系统维护工作你就知道JSP的EL表达式、JSTL标签到底是怎么和Servlet配合的不至于看到旧代码发懵。另外从论文和答辩的角度看SSMJSP结构特别适合画架构图浏览器发请求到SpringMVC的ControllerController调Service接口Service实现类里注入MapperMapper通过MyBatis访问MySQL数据库查询结果逐层返回最后由Controller把数据放进Model转发到WEB-INF下的JSP页面渲染。这条链路清晰几乎就是教科书级别的“表现层-业务层-持久层”三层架构写进毕业设计里既标准又好解释。而且这套项目打出来的war包可以直接丢进Tomcat跑面试时聊传统Web项目部署也不虚。1.3 技术选型清单做一个可复现的SSMJSP项目我推荐的技术版本组合如下JDK 8 或 11不建议一上来用JDK 17有些旧版本依赖会有兼容问题Maven 3.6 以上负责依赖管理Spring 5.2.x SpringMVC 5.2.x MyBatis 3.5.xMyBatis-Spring 2.0.x负责MyBatis与Spring整合MySQL 5.7 或 8.0数据库引擎InnoDBDruid 1.2.x数据库连接池Tomcat 8.5 或 9.0Servlet容器JSTL 1.2配合JSP使用Lombok可选但我建议自己写getter/setter毕设场景求稳这个组合是经过大量项目验证的依赖之间冲突少网上的参考资料也最多遇到问题比较容易搜到解决方案。2. 框架整合与工程搭建2.1 三个框架各自的作用SSM不是一个大一统框架而是三个独立框架的组合它们的分工可以类比成一个餐厅的运作。Spring是“餐厅经理”负责调度所有员工也就是管理对象。Service层、Mapper这些对象不需要你手动newSpring通过IOC容器创建并注入到需要的地方同时用AOP统一处理事务、日志等横切逻辑。SpringMVC是“前台接待”负责接客用户请求进来它根据URL找到对应的Controller方法把请求参数绑定到方法入参上最后把返回的视图名解析成真实的JSP路径。MyBatis是“仓库管理员”负责对接数据库你把SQL写在Mapper XML文件里它负责执行并把结果集映射成Java对象。理解了分工配置就不会乱。Spring管Service和MapperSpringMVC只管Controller和视图解析MyBatis的SqlSessionFactory由Spring创建Mapper接口通过扫描方式注册到容器中。这三个框架不是各干各的而是由Spring做总集成SpringMVC和MyBatis都以Spring的Bean形式存在。2.2 从IDEA创建一个可运行的工程很多初学者卡在第一步IDEA里怎么建一个包含JSP的Maven Web工程。其实有两种方式一种是在IDEA新建项目时选择Maven Archetype选maven-archetype-webapp模板好处是自动生成webapp目录和web.xml另一种是先建普通Maven项目然后手动加web目录。我推荐第一种省事。工程里的标准目录结构大致是这样ssm-old/ ├── pom.xml ├── src/main/java │ └── com/example/ssm │ ├── controller │ ├── service │ │ └── impl │ ├── mapper │ ├── entity │ └── common ├── src/main/resources │ ├── jdbc.properties │ ├── mybatis-config.xml │ ├── springmvc.xml │ ├── applicationContext.xml │ └── mapper │ ├── ElderInfoMapper.xml │ └── ... └── src/main/webapp ├── WEB-INF │ ├── web.xml │ └── jsp │ ├── login.jsp │ ├── elder │ │ ├── list.jsp │ │ └── form.jsp │ └── ... └── static ├── css └── js以前有人会把Controller包放到SpringMVC的扫描范围内把Service包也放到SpringMVC的扫描范围内导致Service被两个容器重复创建事务注解有时候失效。稳妥的做法是Spring容器扫描Service和MapperSpringMVC容器只扫描Controller。applicationContext.xml里配扫描com.example.ssm.servicespringmvc.xml里配扫描com.example.ssm.controller。2.3 核心配置文件长什么样pom.xml里的关键依赖要配全少一个都可能在启动时报ClassNotFound。一个能跑起来的精简依赖集包含spring-context、spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java、druid、jstl、javax.servlet-apiprovided作用域等。javax.servlet-api一定要用provided因为Tomcat里已经有Servlet类打包进去会造成冲突。springmvc.xml的核心配置context:component-scan base-packagecom.example.ssm.controller/ mvc:annotation-driven/ mvc:resources mapping/static/** location/static// bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean这里InternalResourceViewResolver是关键Controller返回字符串elder/list时SpringMVC会拼成/WEB-INF/jsp/elder/list.jsp然后转发到那个页面。JSP放在WEB-INF下是安全做法浏览器直接访问不到必须经过Controller这也是这个项目里JSP页面和静态资源要分开存放的原因。applicationContext.xml里要配置数据源、SqlSessionFactory、Mapper扫描和事务管理器context:component-scan base-packagecom.example.ssm.service/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.ssm.mapper/ /bean tx:annotation-driven transaction-managertransactionManager/ bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /beanweb.xml里除了DispatcherServlet一定记得配Spring的ContextLoaderListener和字符编码过滤器。字符编码过滤器必须放在所有过滤器最前面否则POST请求中文乱码很难缠filter filter-nameencodingFilter/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-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping我在实际搭建时有一个体会配置文件不要一次性写完再启动那样报错很难定位。最好分三步先把SpringMVC和Controller跑通返回一个最简单的hello页面再把MyBatis接上查一条数据出来最后加事务和拦截器。每走一步都验证一次虽然时间上会多花半小时但排查成本会少一大半。3. 数据库设计老年服务的业务底盘3.1 从业务流程推导出表结构数据库设计是这类系统的地基表关系理不清后面写代码全是泪。我不建议一上来就对着表结构抄而是先走一遍业务流程。以“夕阳红”系统为例核心流程大概是这样管理员登录后台先录入老人档案护工定期给老人做健康检测把血压、血糖等数据填进系统老人或家属发起服务预约系统生成一个待处理的工单管理员看到工单后指派给相应护工护工处理完把状态改成已完成另外还有活动模块管理员发布活动老人线下或由工作人员代报名。按这个流程至少需要这些表后台用户表管理员/护工、老人信息表、健康档案表、服务预约表、活动表、活动报名表、公告表。有人会问护工是不是单独建一张表如果护工也要登录系统那我建议后台用户表加一个角色字段来区分这样登录逻辑统一如果护工不需要登录那就在服务预约表里直接存护工的姓名和电话简单很多。毕设场景推荐前者角色字段做权限拦截更方便论文里也好扩展。3.2 核心表的字段设计下面给出几张核心表的简化建表语句实际项目里可以再加一些审计字段比如create_time、update_time、deleted。后台用户表CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码(MD5加密), real_name VARCHAR(50) COMMENT 真实姓名, role TINYINT DEFAULT 1 COMMENT 角色: 1管理员 2护工, phone VARCHAR(20), status TINYINT DEFAULT 1 COMMENT 1启用 0禁用, create_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;老人信息表CREATE TABLE elder_info ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender TINYINT DEFAULT 1 COMMENT 1男 2女, birthday DATE, id_card VARCHAR(18) COMMENT 身份证号, phone VARCHAR(20), address VARCHAR(200), emergency_contact VARCHAR(50) COMMENT 紧急联系人, emergency_phone VARCHAR(20), bed_no VARCHAR(20) COMMENT 床位号, health_level TINYINT DEFAULT 1 COMMENT 健康等级 1良好 2一般 3需关注, create_time DATETIME, deleted TINYINT DEFAULT 0 COMMENT 0正常 1已删除 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;健康档案表CREATE TABLE health_record ( id INT PRIMARY KEY AUTO_INCREMENT, elder_id INT NOT NULL, check_date DATE NOT NULL, blood_pressure_high INT COMMENT 收缩压, blood_pressure_low INT COMMENT 舒张压, blood_sugar DECIMAL(5,2) COMMENT 血糖 mmol/L, heart_rate INT COMMENT 心率, temperature DECIMAL(4,1) COMMENT 体温, doctor_remark VARCHAR(500) COMMENT 医生备注, create_time DATETIME, KEY idx_elder (elder_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;服务预约表CREATE TABLE service_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 工单编号, elder_id INT NOT NULL, service_type TINYINT COMMENT 1送餐 2照护 3保洁 4就医陪同, appoint_date DATE, appoint_time VARCHAR(20) COMMENT 时间段, status TINYINT DEFAULT 0 COMMENT 0待派单 1处理中 2已完成 3已取消, staff_id INT COMMENT 处理人, 来自sys_user, remark VARCHAR(200), create_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;活动表CREATE TABLE activity ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, activity_time DATETIME, location VARCHAR(100), max_people INT DEFAULT 0 COMMENT 0表示不限制, register_count INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1报名中 2已结束, create_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;活动报名表可以把老人ID和活动ID做联合唯一索引防止重复报名。3.3 设计上的几个小心机这类系统的表设计不需要特别复杂但有几个细节值得注意。第一业务状态字段用tinyint加注释不要直接用字符串存“已完成”“已取消”。前端展示时用JSP的c:if或者后台转VO来映射成中文这样以后改状态名称不用动表结构。第二老人信息表里涉及身份证、手机号、紧急联系人这类敏感信息如果用真实数据演示记得脱敏论文里的截图也不要放真实姓名和证件号。第三删除业务数据时尽量不要物理删除用deleted字段做逻辑删除这样误删还能恢复答辩时也是一个加分点。第四外键约束在毕设项目里可加可不加加了数据一致性更强但插入数据时要先插主表不加的话靠应用层保证代码逻辑要干净。我更建议加索引尤其是elder_id这种高频查询字段。4. 后端核心功能实现4.1 登录功能与拦截器登录是系统的门面也是权限控制的起点。密码不能明文存正规做法是用MD5加盐或者在代码里对密码做摘要。虽然MD5不算安全但毕设场景基本够用答辩时如果能提一句“生产环境应该用BCrypt”反而显得你有安全意识。Controller层的代码很简单Controller public class LoginController { Autowired private SysUserService sysUserService; GetMapping(/login) public String loginPage() { return login; } PostMapping(/login) public String login(String username, String password, HttpSession session, Model model) { SysUser user sysUserService.login(username, DigestUtils.md5DigestAsHex(password.getBytes())); if (user null) { model.addAttribute(error, 用户名或密码错误); return login; } session.setAttribute(loginUser, user); return redirect:/index; } GetMapping(/logout) public String logout(HttpSession session) { session.invalidate(); return redirect:/login; } }更关键的是拦截器。不拦截的话用户不登录直接访问/elder/list也能看到数据这在答辩时会被直接扣分。实现一个HandlerInterceptor在preHandle里判断sessionpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(loginUser) ! null) { return true; } response.sendRedirect(request.getContextPath() /login); return false; } }然后在springmvc.xml里注册拦截规则mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ /mvc:interceptor /mvc:interceptors这里最容易被忽略的是/static/**的放行如果不放行登录页里的CSS和JS全被拦住页面样式会变得很难看。另外发送/login请求本身也放行否则登录接口都进不去。4.2 分页查询与条件检索老年服务系统的列表页特别多老人列表、健康记录列表、预约工单列表全都要分页。我自己习惯直接用PageHelper这个分页插件它和MyBatis的整合非常省事只要在MyBatis配置里加一个插件然后在Service调用前执行PageHelper.startPage(pageNum, pageSize)后面的查询自动变成分页SQL。Service层代码public PageInfoElderInfo findElderPage(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); ListElderInfo list elderInfoMapper.selectByKeyword(keyword); return new PageInfo(list); }Controller接收页面传过来的pageNum和keyword查询完把PageInfo放进Model。PageInfo里已经有了total、pages、list、hasNextPage、hasPreviousPage等属性JSP页面上直接用来渲染表格和分页条不需要自己再包一层。对应的MyBatis Mapper XML需要支持模糊查询select idselectByKeyword resultTypecom.example.ssm.entity.ElderInfo SELECT * FROM elder_info where deleted 0 if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR phone LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY id DESC /select这里有几个坑一是符号在XML里要转义比如create_time lt; now()或者放在CDATA区域里二是#{}和${}不要混用关键字这种东西必须用#{}预编译防止SQL注入三是CONCAT函数拼模糊查询条件不要自己在Java代码里写成%keyword%再传那样一旦关键字里有百分号就会出现意外结果。4.3 健康记录和服务预约的状态流转健康记录这个模块逻辑很直接基本是新增和列表。新增时要注意后端校验elder_id有没有被打码或者丢失空值会导致外键约束失败报错报得莫名其妙。列表页可以按日期倒序排让最近的记录显示在最前面。如果想做得更有温度可以在老人详情页里显示他最近一次的血压和血糖旁边用颜色标出是否在正常范围比如收缩压超过140标红。这种小功能不复杂但演示效果很好。服务预约相对复杂一点因为涉及状态流转。我把状态设计为0待派单、1处理中、2已完成、3已取消。老人信息录入时如果带了家属联系方式预约工单生成后可以由管理员电话确认在系统里把状态改成处理中并指派staff_id。更新操作要放在一个事务里比如Transactional(rollbackFor Exception.class) public void assignOrder(Integer orderId, Integer staffId) { ServiceOrder order serviceOrderMapper.selectById(orderId); if (order null || order.getStatus() ! 0) { throw new ServiceException(订单不存在或状态不允许派单); } serviceOrderMapper.updateStatusAndStaff(orderId, 1, staffId); // 同时给护工增加一条待办记录可省略 }事务加在Service实现类上注意必须通过Spring代理调用才生效同类内部方法调用this.method()不行这也是很多同学发现Transactional没生效的原因之一。5. JSP前端页面与交互细节5.1 JSP的三件套EL、JSTL、base路径JSP很多年没有被大力推广了但它作为服务端模板渲染技术在SSM老项目里仍然大量存在。JSP页面写得好不好直接决定这个项目看起来“专不专业”。最忌讳的做法是把整段Java代码塞进JSP的% %里这不叫开发叫应急处理。规范的写法是页面里只用EL表达式${}取数据用JSTL标签做循环和判断业务逻辑全部留在后端。页面的公共部分要处理base路径问题。因为部署后应用名可能变化路径写死会导致CSS加载不出来、表单提交404。我习惯在每个页面头部放一句% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % String ctx request.getContextPath(); request.setAttribute(ctx, ctx); %或者用JSTL设置c:set varctx value${pageContext.request.contextPath}/之后的表单、链接、静态资源都写成${ctx}/xxx这样不管应用名是叫ssm-old还是old-server代码都不用改。5.2 典型的列表页和表单页怎么做列表页是这个系统里出现频率最高的页面类型。一个标准的列表页由四部分组成搜索条件区、数据表格、操作按钮、分页条。搜索条件通常就一两个输入框加一个查询按钮数据表格用table标签表头固定数据行用c:forEach遍历操作按钮放在表格最后一列包括编辑、详情、删除。写分页条的时候很多人直接套网上的模板结果点击第二页之后keyword丢失查询条件就没了。正确做法是把keyword也拼到翻页链接里c:if test${page.hasPreviousPage} a href${ctx}/elder/list?pageNum${page.prePage}keyword${keyword}上一页/a /c:if表单页要注意编辑回显。在form.jsp里判断是新增还是编辑编辑时给input的value赋值EL表达式input typetext namename value${elder.name} required input typehidden nameid value${elder.id}日期类型的回显也简单input typedate的值格式要是yyyy-MM-dd提前在后台把Date格式化成这个格式否则浏览器不识别。用fmt:formatDate标签可以处理。5.3 前后端联调时那些说不出口的坑JSP项目联调遇到的问题大多是参数传递层面的。最常见的是400错误多半是日期格式对不上、或者表单提交了空字符串。请求参数类型是Integer的字段如果传了空字符串SpringMVC会绑定失败解决办法是接受String类型再转换或者在实体上配合DateTimeFormat转换日期同时在Controller里加BindingResult打印错误信息排查。另一个坑是页面点击删除按钮弹出确认框后直接删除不做二次确认。推荐用简单的JavaScript confirm不用引第三方弹层库演示时还显得操作严谨function del(id) { if (confirm(确定要删除这条老人档案吗)) { location.href ctx /elder/delete?id id; } }最后一个容易忽略的是“删除最后一页最后一条数据”后的空页问题。用户在第3页删掉唯一一条记录返回时pageNum还是3但此时数据库已经没有这么多数据页面会显示空白。我对PageHelper处理是从Controller里判断如果当前页大于总页数就重定向到最后一页这个小细节做好了答辩时会显得你考虑很周全。6. 运行部署与高频问题排查6.1 本地用IDEA把项目跑起来SSMJSP项目在本地运行最顺的路径是先创建数据库并执行建表脚本然后在IDEA里配置Tomcat ServerDeployment里添加war explodedArtifactApplication context设为/ssm-old最后启动。这里要注意一点JSP页面修改后IDEA有时不会自动同步到Tomcat的部署目录所以改了页面不重启就可能看到旧内容。如果确定改了却始终不生效检查一下右上角Tomcat配置里的On frame deactivation是否选了Update classes and resources或者干脆手动重启。依赖下载慢的问题在国内很普遍。在Maven的settings.xml里配置阿里云镜像下载速度会快很多。如果某个依赖始终下载失败检查本地仓库里有没有残留的.lastUpdated文件有就删掉重新mvn clean install。6.2 打包war并部署到服务器传统SSM项目最终交付形态是war包整个流程就是三条命令加一个拷贝动作。先在pom.xml里确保packaging是wargroupIdcom.example/groupId artifactIdssm-old/artifactId version1.0.0/version packagingwar/packaging然后执行mvn clean package打包完成后去target目录找到ssm-old.war把它丢到Tomcat的webapps目录下启动Tomcat它会自动解压部署。访问路径就是http://服务器IP:8080/ssm-old/login。如果服务器前面挂了Nginx有一个高频疑问是“Nginx支持JSP吗”。直接回答不支持。Nginx本身是静态Web服务器和反向代理服务器它并不知道怎么解析JSP脚本所以遇到.jsp请求必须把它反向代理给Tomcat处理。Nginx里可以做两层配置静态资源直接走本地文件系统动态请求转发给Tomcatserver { listen 80; server_name old.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* \.(css|js|png|jpg|gif)$ { root /opt/tomcat/webapps/ssm-old; } }生产环境部署还要改数据库连接密码、换一个不是root的数据库账号、确认Tomcat的端口防火墙放行这些虽然不属于代码层面但答辩时问你部署方案能说出来会很加分。6.3 常见问题速查表现象原因解决方案启动报“源发行版17需要目标发行版17”IDEA的Project SDK和Java语言级别不一致在Project Structure里统一JDK版本Maven的compiler plugin也指定版本Tomcat启动时端口占用8080被其他进程占用关闭占用程序或修改Tomcat的server.xml端口访问login直接404DispatcherServlet的url-pattern配成/但映射路径不对检查web.xml、注解RequestMapping、Artifact是否已部署ClassNotFound: org.springframework.*依赖没下载或打包时没带上Maven里跑mvn dependency:tree本地仓库清掉损坏文件重下数据库中文乱码连接URL没指定编码或表不是utf8JDBC URL加characterEncodingutf8建表用utf8mb4表单提交中文乱码缺少CharacterEncodingFilter在web.xml最前面配置编码过滤器并设置forceEncodingtrueJSP页面显示JSTL标签源码缺少jstl依赖或taglib声明错误pom.xml添加jstl 1.2依赖页面写对% taglib %修改了JSP页面不生效IDEA没有热部署到Tomcat配置Tomcat的update resources大概率需要重启删除数据后分页空白当前页数据量变化导致页号越界Controller判断pageNum大于总页数时重定向到最后一页6.4 演示时的一组实用数据准备做这类系统最怕演示时现场翻车我自己的处理办法是提前准备一套“演示脚本”把数据插好流程走熟。比如建一个名叫“张桂芳”的测试老人健康档案里填一组正常的血压血糖数据再预约一条“上门照护”的工单状态停在“待派单”。演示时先展示登录然后进老人列表搜关键字点进详情看健康记录接着受理工单最后发一条活动公告。整个流程不超过三分钟但把增删改查、分页、关联查询、状态流转全带出来了。数据准备还有个细节不要把测试数据的姓名做成“张三”“李四”这种看起来太假。用“张桂芳”“王德明”这种有真实感的姓名配合正常的出生日期和地址演示时观感完全不一样。相关字段值和业务逻辑也能对得上比如生日1952年健康记录里血压130/85就说得通。做SSMJSP这个技术栈的老项目我最深刻的体会是“扎实”二字。它不像Spring Boot那样开箱即用但正因为每一步都需要自己配置和思考你对请求怎么进来的、数据怎么查出来的、页面怎么渲染的会比直接写注解清楚得多。如果你正在被“夕阳红”这类题目折磨别急着复制粘贴从头把一个登录、一个查询、一个状态流转走通后面所有模块都会变得特别顺手。这个项目做完你对Spring容器、MyBatis映射、JSP渲染的理解可能比刷十套面试题都有效。
返回列表