news 2026/9/28 1:59:11

SSM员工信息管理系统:从三层架构到部署实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM员工信息管理系统:从三层架构到部署实战全解析

简介:龙腾公司员工信息管理系统是一套基于SSM框架、JSP与MySQL的Java毕业设计源码包,面向计算机专业学生、Java初学者以及需要快速完成课程设计或毕业设计的开发者。系统覆盖员工、部门、职位、薪资、培训、考勤六大核心模块,员工管理支持信息核查与增删改查,部门与职位可动态维护,薪资条可添加、修改、删除与查看,培训支持全流程管理,考勤包含上下班打卡与记录查询,能较好体现典型管理系统的完整业务链。压缩包共891个文件,以Java源码、JSP页面、JS交互脚本、CSS样式、PNG/JPG图片素材及XML配置为主要构成,另含SQL数据库脚本和说明文档,便于理解数据库表结构与项目部署;总大小约10.47MB。开发环境基于JDK1.8、Tomcat7、Maven3.3,可在Eclipse或IDEA中运行。目前已有63人学习下载,资料结构较完整,适合用来掌握SSM整合流程、熟悉增删改查实现细节,并作为毕业设计或课程设计的直接参考。

1. 龙腾公司员工信息管理系统:这个SSM项目到底在解决什么问题

一家小公司把员工信息放在Excel里,每年调薪、转岗都要重新整理一遍;HR要给老板看个部门人数,得手工合并好几个表。龙腾公司员工信息管理系统就是这类Java课程设计里最常见的选题:用SSM(Spring + SpringMVC + MyBatis)加 JSP 和 MySQL,把员工基本信息、部门分配、考勤记录、账号登录做成一个能增删改查的网页系统。做这个题目的人大多是计算机专业在校生,要么是毕业设计、要么是课程设计,想找一个完整可跑的SSM项目源码做参考,顺便把SSM三层架构在真实代码里过一遍。这篇笔记就按这类源码的标准结构,拆清楚它由哪些文件组成、每层在干什么、怎么在自己电脑上跑起来,以及拿到源码后怎么动手把它改出自己的功能。适合正在选毕设题目、或者刚拿到一份SSM员工管理系统源码但不知道从哪里下手的同学。

2. 先把SSM三条线理顺:配置文件里看清Spring、SpringMVC、MyBatis怎么分工

龙腾这个员工系统的源码解压后,第一眼是一堆java包、resources配置和jsp页面。有人会先去翻Controller或者DAO,我觉得顺序反了:先读配置文件,把三个框架的分工搞清楚,代码读起来才有轮廓。

2.1 三层架构在员工系统里怎么分配:谁管请求、谁管业务、谁管SQL

SSM不是三个独立框架拼在一起,而是各管一段。SpringMVC管HTTP层:用户在浏览器点“员工列表”,请求被DispatcherServlet拦下来,转发给对应的Controller方法。Spring管业务层:Service和DAO对象的创建、依赖注入、事务边界都由Spring容器负责,所以Controller里通常只需要@Autowired一个Service接口,自己不用new。MyBatis管数据层:写SQL、执行查询、把结果集的每一列映射成Employee对象的属性。

放到员工管理这个场景里,一次完整请求是这么走的:浏览器访问/employee/list→ SpringMVC的DispatcherServlet找到EmployeeController的list方法 → Controller调用EmployeeService的list方法 → Service调EmployeeMapper接口 → MyBatis执行mapper.xml里的select语句 → 返回List → Controller把数据塞进ModelAndView → JSP用JSTL标签循环渲染成表格 → 浏览器显示。我一般建议拿到源码先顺着这个链路读,读完后SSM面试题问“请求流程”也不会卡壳。

2.2 用Maven建工程:pom.xml里必须有的核心依赖

源码多半是Maven工程,IDEA打开后第一件事看pom.xml。一个能跑起来的SSM员工系统,依赖项大致是下面这些,版本按自己环境选5.3.x的Spring、3.5.x的MyBatis、8.0.x的MySQL驱动都行。

<dependencies> <!-- Spring MVC 与 Spring 核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <!-- MyBatis 以及 Spring 整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <!-- Servlet 与 JSP 相关:Tomcat 已自带,scope 用 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL 标签库,JSP 页面里<c:forEach>全靠它 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>

这里容易踩的第一个坑就是版本匹配。mybatis-spring 2.0.x对应MyBatis 3.5.x,配Spring 5没问题;如果把mybatis-spring换成1.x老版本,会遇到NoSuchBeanDefinitionException这种奇怪的启动报错。另外JSTL不会随着Tomcat自动装进项目,所以代码里用了<c:forEach>就必须显式加这个依赖,否则页面报 “The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved”。

2.3 SpringMVC配置:DispatcherServlet与视图解析器怎么把JSP串起来

传统SSM项目用web.xml把SpringMVC挂进Tomcat,这类源码里web.xml起着“总入口”的作用。它要干两件事:注册DispatcherServlet,让它接管所有以/开头的请求;再注册一个Spring的ContextLoaderListener,用来加载Spring根容器。

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 加载 Spring 根容器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mybatis.xml</param-value> </context-param> <!-- 前端控制器:拦截所有请求 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 中文乱码过滤器,必须放在所有过滤器最前面 --> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>

注意这里tenant的servlet-mapping用的是/而不是*.do,意味着所有静态资源和JSP都要经过DispatcherServlet。JSP页面本身没问题,SpringMVC会把ViewName交给视图解析器去解析渲染;但CSS、JS、图片这些静态资源会被Controller拦截,所以spring-mvc.xml里必须配置<mvc:resources>放行,不然页面样式全是乱的。

spring-mvc.xml是另一个重头,它做三件事:扫描Controller、开启SpringMVC注解驱动、配置视图解析器。典型配置长这样。

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="..."> <!-- 只扫描 controller 包,别把 Service 也扫进来 --> <context:component-scan base-package="com.longteng.controller"/> <!-- 开启 @RequestMapping 等注解支持 --> <mvc:annotation-driven/> <!-- 放行静态资源 --> <mvc:resources mapping="/static/**" location="/static/"/> <!-- 视图解析器:Controller 返回 "employee/list" 时解析为 /WEB-INF/views/employee/list.jsp --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans>

视图解析器的prefix和suffix是理解整个页面路径的关键。Controller方法里返回字符串"employee/list",解析器自动拼成/WEB-INF/views/employee/list.jsp。把JSP放在WEB-INF目录下还有个好处:用户直接在浏览器地址栏输入路径访问不到,必须经过Controller转发,这在毕设答辩里可以当“安全性设计”讲。

2.4 MyBatis配置:把数据源、Mapper接口和SQL映射连起来

员工系统的数据访问层由三部分配合:数据源、SqlSessionFactory、Mapper扫描。这些配置习惯放在spring-mybatis.xml里。很多源码用c3p0或Druid做连接池,我建议用Druid,除了连接池功能还能在网页上看到SQL监控。

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="..."> <!-- 读取 jdbc.properties --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- Druid 数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> </bean> <!-- SqlSessionFactory:MyBatis 核心 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <!-- 实体类别名包,mapper.xml 里可以直接写 resultType="Employee" --> <property name="typeAliasesPackage" value="com.longteng.entity"/> </bean> <!-- 扫描 Mapper 接口,自动生成代理对象 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.longteng.mapper"/> </bean> </beans>

对应jdbc.properties里就是数据库连接参数,MySQL 8.0的url里必须带serverTimezone和useSSL=false,这是mysql连接报错的高发区:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/longteng_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456

读这个配置文件时我一般会确认两件事:mapperLocations指向的路径跟实际目录是否一致,typeAliasesPackage的包名跟实体类包名是否一致。这两个配置错了,启动时不报错,一调用Mapper就报Invalid bound statement (not found),属于SSM里最经典的黑匣子问题,排查起来特别难受。

3. 用MySQL建好员工数据四张表:字段设计决定后面JSP页面好写不好写

3.1 四张表的关系:员工、部门、用户、考勤怎么关联

这类员工管理系统的数据模型不会太复杂,常见就四张表。员工表是核心,存姓名、性别、手机号这些基本信息;部门表存部门名称和负责人;用户表管登录账号密码;考勤表记录员工每天的上下班打卡时间。它们之间的关系用一句话说清:一个部门下挂多个员工,一个员工产生多条考勤,一个用户账号对应一个操作员。员工表通过dept_id与部门表关联,考勤表通过emp_id与员工表关联。

我见过不少课设把部门和员工揉在一张表里,页面确实好写,但问题来了:一个部门改名,要把所有员工记录都update一遍;而且“查出员工所属部门名称”这种最基础的需求,不得不在Java代码里再查一次部门表。分表之后SQL里一个JOIN就解决,答辩时还能讲“我遵循了数据库规范化设计”。

3.2 用DDL把四张表建出来:字段类型与约束

在MySQL里执行下面这段DDL,整个系统的数据骨架就搭好了。我建议用Navicat或者MySQL Workbench建库执行,执行完检查一下表结构,别急着写代码。

CREATE DATABASE IF NOT EXISTS longteng_db DEFAULT CHARACTER SET utf8mb4; USE longteng_db; -- 部门表 CREATE TABLE t_dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID', dept_name VARCHAR(50) NOT NULL COMMENT '部门名称', manager VARCHAR(20) COMMENT '部门负责人', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表'; -- 用户表 CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(30) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(64) NOT NULL COMMENT '密码(MD5)', real_name VARCHAR(20) COMMENT '真实姓名', role VARCHAR(20) DEFAULT 'ADMIN' COMMENT '角色' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 员工表 CREATE TABLE t_employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID', emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '工号', name VARCHAR(20) NOT NULL COMMENT '姓名', gender CHAR(2) COMMENT '性别', birthday DATE COMMENT '出生日期', dept_id INT COMMENT '所属部门ID', position VARCHAR(30) COMMENT '职位', phone VARCHAR(11) COMMENT '手机号', email VARCHAR(50) COMMENT '邮箱', hire_date DATE COMMENT '入职日期', status TINYINT DEFAULT 1 COMMENT '1在职 0离职', FOREIGN KEY (dept_id) REFERENCES t_dept(dept_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表'; -- 考勤表 CREATE TABLE t_attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤ID', emp_id INT NOT NULL COMMENT '员工ID', att_date DATE NOT NULL COMMENT '考勤日期', check_in DATETIME COMMENT '上班打卡时间', check_out DATETIME COMMENT '下班打卡时间', status VARCHAR(10) DEFAULT '正常' COMMENT '正常/迟到/早退/缺勤', FOREIGN KEY (emp_id) REFERENCES t_employee(emp_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤表';

字段类型这里有几个经验点。salary这类金额字段用DECIMAL(10,2),不要用float,float有精度问题,算工资时会出现0.1+0.2不等于0.3的尴尬。手机号用VARCHAR(11)而不是BIGINT,因为手机号可能带前缀、可能以后要存区号,数值类型会吃掉前导零。birthday和hire_date用DATE类型,页面传值给Controller时要用@DateTimeFormat(pattern="yyyy-MM-dd")做格式转换。所有表都加上AUTO_INCREMENT主键,后面分页排序都基于它。

3.3 三范式的实际取舍:冗余字段该不该留

设计表时有个常见纠结:员工表要不要直接存部门名字而不是存dept_id,考勤表要不要冗余员工姓名。教科书会告诉你第三范式不能冗余,但实际毕业设计里,页面要显示“张三所在的部门是技术部”,如果按第三范式就得子查询或JOIN。我的建议是:员工表和部门表之间保留外键关联,别冗余部门名,因为JOIN语句是在mapper.xml里写,SQL熟练程度直接写在答辩PPT里,是个加分项;而像统计考勤列表这种高频查询,可以在查询DTO里冗余返回员工姓名,但不改表结构。

数据库层面的用户密码设计值得一提。源码里t_user表通常直接存MD5值,这是毕业设计最常见的做法,安全度一般;如果想把答辩档次拉高一点,可以在代码里做MD5(密码 + 固定盐),登录时用同一个规则再算一遍比对。这样即使数据库泄露,密码也不是明文。

3.4 初始数据怎么准备:测试账号和样例员工

系统跑通之前必须往表里塞初始数据,否则登录页面都进不去。user表至少插一条管理员账号。员工表插几条测试数据,部门也先建三四个,这样列表页和下拉框才好看。常见做法是写一个SQL脚本,在源码的doc或sql目录下,双击导入数据库即可。如果源码包里没带SQL脚本,就按上面DDL执行后自己补INSERT语句,后续调试会省很多事。

4. 在JSP页面上把员工信息管起来:列表、分页、增删改的落地写法

JSP入门最常卡的点不是JSP语法本身,而是“Controller里返回的数据,页面怎么拿得到”。这一章按员工列表、新增、编辑、删除四个功能过一遍。

4.1 Controller怎么把数据喂给JSP:ModelAndView这一步别漏

员工模块的Controller,标准写法是每个方法返回ModelAndView或String,数据放Model里。看一个典型的list方法:

@Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; /** * 员工列表:支持分页和按姓名模糊查询 */ @RequestMapping("/list") public ModelAndView list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize, @RequestParam(required = false) String name) { List<Employee> list = employeeService.findPage(pageNum, pageSize, name); int total = employeeService.count(name); int totalPages = (int) Math.ceil((double) total / pageSize); ModelAndView mv = new ModelAndView(); // 数据放进 Model,JSP 里通过 ${list} 取 mv.addObject("list", list); mv.addObject("pageNum", pageNum); mv.addObject("totalPages", totalPages); mv.addObject("name", name); // 视图名交给视图解析器拼成 /WEB-INF/views/employee/list.jsp mv.setViewName("employee/list"); return mv; } }

参数说明:pageNum默认1,pageSize默认5,name是可选查询条件。注意前端页码传过来是字符串,这里用@RequestParam加defaultValue做自动类型转换,就不用在方法里手写Integer.parseInt了。用ModelAndView比返回String+Model参数更直观,新手不易漏掉addObject那一步。

4.2 列表页JSP:JSTL遍历 + 分页栏怎么处理

对应的list.jsp核心结构长这样,重点看表格循环和分页链接:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>员工列表 - 龙腾公司</title> </head> <body> <h3>员工列表</h3> <form action="${pageContext.request.contextPath}/employee/list" method="get"> <input type="text" name="name" value="${name}" placeholder="按姓名查询"/> <button type="submit">查询</button> </form> <table border="1" cellpadding="6" cellspacing="0"> <tr> <th>工号</th> <th>姓名</th> <th>性别</th> <th>部门</th> <th>职位</th> <th>手机号</th> <th>入职日期</th> <th>操作</th> </tr> <c:forEach items="${list}" var="emp"> <tr> <td>${emp.empNo}</td> <td>${emp.name}</td> <td>${emp.gender}</td> <td>${emp.dept.deptName}</td> <td>${emp.position}</td> <td>${emp.phone}</td> <td>${emp.hireDate}</td> <td> <a href="${pageContext.request.contextPath}/employee/edit?id=${emp.empId}">编辑</a> <a href="${pageContext.request.contextPath}/employee/delete?id=${emp.empId}" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </table> <%-- 分页栏 --%> <div> <c:if test="${pageNum > 1}"> <a href="${pageContext.request.contextPath}/employee/list?pageNum=${pageNum-1}&name=${name}">上一页</a> </c:if> <span>第 ${pageNum} / ${totalPages} 页</span> <c:if test="${pageNum < totalPages}"> <a href="${pageContext.request.contextPath}/employee/list?pageNum=${pageNum+1}&name=${name}">下一页</a> </c:if> </div> </body> </html>

几个容易翻车的细节。JSTL的c:forEach属性名是items不是list,写错了页面直接报错。${emp.empNo}对应的是Employee类的empNo属性,如果实体类里的属性名是emp_no,MyBatis开启驼峰映射的配置没改,这里就取不到值。删除用GET在毕设里图省事可以,但实际建议改成POST,我后面专门说这条。分页栏的上一页/下一页要做条件判断,不然第一页时点“上一页”会跑到pageNum=0,SQL里的LIMIT会报负数。

4.3 新增和编辑共用一个表单:Controller里的save方法判断id

新增和编辑没必要写两个页面,共用同一个employee/form.jsp。进入页面时如果带了id,先查出员工记录回显到表单;没带id就是空白表单。提交时统一调save方法,Controller根据id是否为空决定insert还是update。

@RequestMapping("/save") public String save(Employee employee, @RequestParam("deptId") Integer deptId, @RequestParam(value = "birthday", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) { employee.setBirthday(birthday); // 前端下拉框传的是 deptId,需要手动封装Dept对象 Dept dept = new Dept(); dept.setDeptId(deptId); employee.setDept(dept); if (employee.getEmpId() == null) { employeeService.add(employee); } else { employeeService.update(employee); } return "redirect:/employee/list"; }

这段代码有点实际项目的味道了。前端表单的name属性要跟Employee实体字段对得上,比如<input name="phone">会自动绑到employee.phone。部门下拉框的值是deptId,但Employee里关联的是Dept对象,所以Controller手动组装一下。日期字段必须加@DateTimeFormat,否则浏览器传来的"2023-05-01"字符串转Date会报400错误。这里有个习惯:保存成功后用redirect重定向而不是forward,否则用户刷新页面会重复提交表单,产生两条员工记录。

4.4 删除接口为什么不用GET

源码里最常见也最该改的坏味道,就是删除用GET。<a href="/employee/delete?id=1">这种方式浏览器地址栏直接输入就能删数据,搜索引擎爬虫扫到链接也会自动请求一次,等于给系统留了个后门。改成POST方式需要两步:JSP里把删除链接换成一个小表单,Controller方法上写@RequestMapping(value="/delete", method=RequestMethod.POST)。个别源码会直接用@RequestMapping("/delete")同时接受GET和POST,这至少是能跑的。如果想在毕设里体现水平,就把删除改成POST并给Controller加一个事务注解,保证删员工的同时关联的考勤记录也被清理,这条属于能在答辩时主动讲的亮点。

5. 把ssm+jsp+mysql项目跑起来:从环境选型到常见部署避坑

5.1 环境版本怎么选:JDK、Tomcat、MySQL、IDEA不打架的组合

拿到源码后第一件事不是打开代码,是把环境版本对齐。SSM框架的版本敏感性比Spring Boot高很多,很多翻车现场就是版本组合不一致导致的。我自己常用的组合是:JDK 1.8 + Tomcat 8.5 + MySQL 5.7 + Maven 3.6.3 + IDEA 2021以上。这套组合兼容性最好,网上排错资料也最多。如果你机器上装的是MySQL 8.0,驱动用com.mysql.cj.jdbc.Driver,url里必须加serverTimezone=Asia/Shanghai,这是MySQL 8和5.x最本质的差别。JDK 17以上不建议用在传统JSP项目上,Tomcat 8.5跑不了更高的Java版本,搞毕设没必要在这种地方给自己加难度。

环境变量方面,JAVA_HOME要指向JDK安装目录,MAVEN_HOME同理。IDEA里把Maven设置成自己装的版本,别用IDEA自带的,不然依赖仓库位置和镜像配置都得重新弄。mysql安装教程网上很多,装完记得在系统服务里确认mysqld在运行,命令行用mysql -uroot -p能连上就算成功,常见报错ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'十有八九是服务没启动。

5.2 数据库导入:不要只建库不导数据

源码包里的SQL脚本通常在根目录的sql/或doc/文件夹下,文件名类似longteng_db.sql。用Navicat或MySQL Workbench执行这个脚本,它会自动建库建表并插入初始数据。执行完检查三件事:库里有没有t_employee这张表,表里有没有测试员工数据,t_user表里的管理员账号密码是什么。这三项确认了,后面登录和列表页才不会扑空。

如果源码里SQL脚本是分散的DDL和INSERT,就按第3章的顺序先建表再插入账号。初始密码字段里存的一般是MD5值,常见是e10adc3949ba59abbe56e057f20f883e,也就是明文123456。拿这个值登录不进去时,别怀疑代码,先把数据库里的密码字段和查询条件比对一下。

5.3 传统JSP项目打包war:IDEA里配置Tomcat的两种方式

传统JSP项目跟Spring Boot不同,不能直接java -jar,必须放进Tomcat容器跑。在IDEA里有两种常见做法。第一种是在IDEA里配置Tomcat Server,把当前项目以war exploded方式部署,适合开发调试;第二种是maven打包成war,丢进Tomcat的webapps目录,适合最终交付。

# 在项目根目录执行,跳过测试打包 mvn clean package -DskipTests # 产物在 target/ 目录下,把 war 包复制到 Tomcat 的 webapps 下 cp target/longteng-1.0.war /usr/local/tomcat/webapps/ # 启动 Tomcat(Linux/Mac) /usr/local/tomcat/bin/startup.sh # Windows 下启动 %CATALINA_HOME%\bin\startup.bat

启动成功后,访问地址是http://localhost:8080/longteng-1.0/login而不是http://localhost:8080/login。这里的longteng-1.0是war包的名字,也就是Tomcat自动生成的context path。IDEA里用Tomcat部署时,Application context则是在Deployment面板里手写的,比如填/longteng。前后端所有的pageContext.request.contextPath会自动带上这个前缀,所以写代码时别把路径写死成/employee/list,不然部署后全404。

5.4 常见部署避坑:五个容易翻车的点

现象一:Tomcat启动成功,但访问项目路径报404。原因大部分是war包没解压成功,或者context path写错。解决:先看webapps目录下有没有对应文件夹,再确认地址栏路径大小写是否跟war包一致。

现象二:启动日志报Cannot resolve class com.mysql.jdbc.Driver。原因是MySQL 8把驱动类改名了,com.mysql.jdbc.Driver老路径已经移除。解决:jdbc.properties里改成com.mysql.cj.jdbc.Driver,并检查pom中驱动版本是不是8.x。

现象三:页面中文全是乱码。原因一般是三个点之一:数据库连接url没有加characterEncoding=utf8,IDEA的文件编码不是UTF-8,或者CharacterEncodingFilter没配置。解决:按上面顺序逐个检查,最隐蔽的是IDEA里jsp文件编码显示GBK,重新用UTF-8编码转换后再保存。

现象四:登录成功后列表页一片空白,浏览器控制台提示500。原因通常是SQL执行异常,比如表名、字段名跟mapper.xml里写的对不上。解决:看IDEA控制台打印的完整异常栈,重点找SQLSyntaxErrorException和BadSqlGrammarException,复制SQL到Navicat里手动跑一遍。

现象五:点击删除员工后没有反应,刷新数据还在。原因十有八九是页面确认框的confirm返回false,或者点击事件被样式层挡住了。解决:先看浏览器Network面板,确认点击后是否发出了请求;如果请求发出但数据库没变,再看Controller方法的transaction是否回滚了;如果是GET删除,检查是否有依赖约束导致删除失败。这些逻辑问题比环境问题好查,但容易让人在页面和SQL之间反复横跳,浪费半小时。

6. 在毕设源码上加一个字段:验证你真正看懂了这个SSM项目

6.1 加“学历”字段要改哪些文件

拿到一份能跑的SSM员工系统源码后,我推荐一个验证方法:给员工表加一个字段,比如“学历”。别小看这个改动,它会逼你把数据层到展示层的每条链路都过一遍。改动的文件按顺序是这些:

文件修改内容
longteng_db.sql或数据库ALTER TABLE t_employee ADD COLUMN education VARCHAR(20) COMMENT '学历';
Employee.java实体类添加private String education;以及getter/setter
EmployeeMapper.xml在resultMap、insert、update三处各加一个education字段映射
employee/form.jsp表单区添加学历输入框(或下拉框)
employee/list.jsp表格头加一个<th>学历</th>,内容加<td>${emp.education}</td>

只要动这五个文件就能跑通,但如果源码里用了查询DTO或者VO,那么查询结果返回的类也要加字段,否则页面上emp.education为null。这个追加练习做完,员工系统的数据流你就门儿清了,比反复看架构图有用得多。

6.2 验证是改全了还是漏了

改完后按这个顺序验证一遍:先执行ALTER语句确认数据库有列;再看列表页能不能显示;然后走新增流程,填一个“本科”保存;最后编辑这条记录,看学历值有没有回显到表单里。如果新增没问题但编辑回显为空,八成是update语句没把education列写进去;如果列表页显示null,先查resultMap,再查实体类getter。我当年带课设时自己就漏改过resultMap,页面上新增的数据能查出来但编辑页面就是空,排查到半夜才发现是mapper.xml的resultMap少配了一列,这就是SSM项目里典型的“看起来全是接口和XML,实际藏雷都在XML里”的体验。

顺便说一句:把“给表加一个字段需要动几个文件”这件事练熟了,面试被问到SSM请求流程、MyBatis的resultMap作用、JSP表单数据绑定这些基础题,基本都能答到点子上。因为你不是背答案,是真跑过一遍。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 1:59:02

西门子博途PTO脉冲控制调试避坑指南:5个常见错误与解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:58:38

H5U配CANopen控制步进伺服:从原理到调试全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:58:31

基于YOLOv5与CNN的车牌检测识别项目实战:从CCPD数据集到模型复现

简介&#xff1a;这份资源面向计算机、人工智能及相关专业的本科生与初学者&#xff0c;提供一套基于CNN与YOLOv5的车牌检测与识别完整工程&#xff0c;数据集采用CCPD官方数据集&#xff0c;可用于毕业设计、课程设计、大作业、工程实训及学科竞赛等场景&#xff0c;帮助读者快…

作者头像 李华
网站建设 2026/9/28 1:56:53

HART硬件设计实战:基于SD2057的低功耗调制解调器外围电路详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:56:53

新版Keil烧录成功但程序不运行?AC6编译器优化是罪魁祸首

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:56:46

基于Python与YOLOv5的路面桥梁裂缝检测识别实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华