news 2026/10/1 3:29:59

SSM+Maven+MySQL企业人事管理系统实战:从建表到部署完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+Maven+MySQL企业人事管理系统实战:从建表到部署完整指南

做JavaWeb课设或者毕设的同学,SSM+Maven+MySQL这套组合应该是接触频率最高的搭配了。基于javaweb和mysql的ssm+maven企业人事管理系统,我用SSM框架完整实现了一个可运行、可部署、可扩展的企业人事管理系统,覆盖员工信息管理、部门维护、考勤记录、薪资处理等典型人事业务场景。这篇文章不是简单的“项目介绍”,而是把我从建表到部署全过程的思路、踩坑和实操细节都拆开讲,适合正在做类似课设/毕设的同学参考,也适合想系统搞懂SSM整合链路的开发者拿来当实战案例。

1. 项目到底需要管什么:人事管理系统的需求与选型逻辑

1.1 人事系统要解决的业务问题

很多同学一上来就急着建表写代码,结果做出来的系统成了“换个名字的CRUD演示”,看着功能齐全,实际经不起推敲。做企业人事管理系统,第一步不是写代码,而是把“人事”这事拆明白。

一个典型的中小型企业人事管理,核心业务无非四块:第一,员工档案管理,包括入职信息、联系方式、岗位、所属部门,这是最基础的数据底座;第二,部门管理,部门不是独立的字典,员工和部门之间必须有清晰归属;第三,考勤管理,记录日常出勤情况,这决定了后续工资计算的依据;第四,薪资管理,根据基础工资、考勤结果、奖惩记录生成工资条。除此之外,还需要一个管理员入口,区分普通员工能看什么、能改什么,这是系统的权限骨架。

我最终把功能模块划分成六块:管理员登录与权限控制、员工信息管理、部门管理、考勤管理、薪资管理、个人信息维护。这六个模块已经能完整覆盖“管人、管部门、管出勤、管钱”的人事业务闭环,同时每一块都有独立的增删改查逻辑,非常适合拿来学习SSM每一层的职责划分。

1.2 为什么是 SSM + Maven + MySQL 这个组合

选技术栈不是跟风,而是要回答“为什么是它”。在我这个项目里,SSM(Spring + SpringMVC + MyBatis)组合的价值在于三层职责极其清晰:Spring 管对象的创建和依赖注入,SpringMVC 管请求的接收和响应分发,MyBatis 管Java对象和数据库表之间的映射。三层各管一摊,遇到问题定位非常快。

对比传统的 JSP + Servlet 模式,SSM 最大的优势是解耦。Servlet 模式下,你要手动创建对象、手动获取参数、手动操作 JDBC,代码量翻倍不说,改一个字段要牵扯好多文件。SSM 把重复劳动交给框架,开发者只需要关注业务逻辑本身。

MySQL 作为数据存储端,理由就更直接了:免费、轻量、资料多、生态成熟,学生和企业都在用。Maven 在这个项目里的地位常被低估——它不只是“下载依赖的工具”,更是项目生命周期管理工具。我可以通过 pom.xml 统一管理 jar 包版本、配置打包方式,还能用一套命令完成编译、测试、打包,比手动往 lib 目录里塞 jar 包强太多。

1.3 这套系统适合谁看

如果你是正在做课程设计或者毕业设计的计算机专业学生,这个项目的业务复杂度和技术覆盖度正好卡在一个“能学到东西又不会失控”的区间。如果你是想系统入门 SSM 整合的开发者,从数据库设计到后端分层再到前端页面,整个链路是完整的,比零散看教程有用得多。我写这篇也会把我在实现过程中的真实选择和踩坑经历放进去,希望你看完能少走点弯路。

2. 从数据库开始设计:核心表结构如何支撑业务

2.1 核心表怎么拆

数据库设计是很多人容易敷衍过去的一步,但说句实在话,人事系统后面所有模块写起来顺不顺手,90%取决于表设计得好不好。我采用的设计原则是“一张表只干一件事”,宁可多拆一张表,也不要在一张表里堆满含义混乱的字段。

最终落地的表结构是五张核心表:管理员表 admin 管系统登录账号,部门表 dept 管组织架构,员工表 employee 管人员信息,考勤表 attendance 管每日出勤,薪资表 salary 管月度工资。员工表通过 dept_id 字段关联部门表,考勤表和薪资表通过 emp_id 字段关联员工表。这样的关联关系非常直观,后端的 SQL Join 写起来也是顺理成章。

2.2 建表 SQL 实战

我直接把核心建表语句放出来,这是我实际调试通过的版本,字符集和字段类型都做了处理,复制就能跑:

CREATE DATABASE IF NOT EXISTS company_hr DEFAULT CHARACTER SET utf8mb4; USE company_hr; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, role VARCHAR(20) DEFAULT 'ADMIN', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- 部门表 CREATE TABLE dept ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(100) NOT NULL UNIQUE, dept_leader VARCHAR(50), dept_phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- 员工表 CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(30) NOT NULL UNIQUE COMMENT '员工编号', name VARCHAR(50) NOT NULL, gender VARCHAR(10), dept_id INT, position VARCHAR(50), phone VARCHAR(20), email VARCHAR(100), entry_date DATE, status VARCHAR(20) DEFAULT '在职', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id) ) ENGINE=InnoDB; -- 考勤表 CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, work_date DATE NOT NULL, sign_in_time VARCHAR(20), sign_out_time VARCHAR(20), status VARCHAR(20) DEFAULT '正常', CONSTRAINT fk_att_emp FOREIGN KEY (emp_id) REFERENCES employee(id) ) ENGINE=InnoDB; -- 薪资表 CREATE TABLE salary ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, salary_month VARCHAR(20) NOT NULL COMMENT '工资月份,如2025-01', base_salary DECIMAL(10,2) DEFAULT 0, bonus DECIMAL(10,2) DEFAULT 0, deduction DECIMAL(10,2) DEFAULT 0, actual_salary DECIMAL(10,2) DEFAULT 0, is_issued TINYINT DEFAULT 0 COMMENT '是否已发放', issue_time DATETIME, CONSTRAINT fk_sal_emp FOREIGN KEY (emp_id) REFERENCES employee(id) ) ENGINE=InnoDB;

2.3 字段设计的几处关键细节

第一个经验是,员工编号 emp_no 一定要设唯一索引。企业场景里员工编号是天然的业务主键,身份证号不能直接拿来当主键,因为涉及隐私且长度不可控,但员工号必须保证唯一。

第二个经验是,金额字段用 DECIMAL 不用 DOUBLE。DOUBLE 是浮点数,存金额会出现精度丢失,工资算出来多几分钱少几分钱,这在人事系统里是不能接受的。

第三个经验是,状态字段 status 用字符串存“在职/离职/休假”这种可读值,比用 0/1 数字更直观。虽然数字存起来省空间,但实际项目里可读性非常重要,尤其你半年后回头看自己的代码时,字符串一眼能看懂,数字你得去翻注释。

第四个细节是关于时间的,“入职日期”用 DATE,“创建时间”用 DATETIME,日期和时间精度不同,混用会导致查询逻辑很别扭。我在建表时如果存在多个时间字段,都会统一检查一遍类型,这个习惯让我后面写 SQL 时省了不少麻烦。

3. SSM整合时的那些坑:配置文件与工程骨架

3.1 Maven 的 pom 依赖清单

工程骨架我用 Maven 搭建,标准打包方式是 war 包。这里直接给一份我调通的 pom.xml 核心依赖,版本号都是经过实际验证能稳定跑通的组合:

<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.3.20</spring.version> </properties> <dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- SpringMVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis和整合包 --> <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驱动:注意 8.x 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!-- Servlet/JSP/JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Jackson,用于JSON交互 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <!-- PageHelper分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.0</version> </dependency> </dependencies> <build> <finalName>HRSystem</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>

这里有一个非常容易踩的坑:JDK 8 环境下 spring 不要用 6.x,mysql-connector 不要混用 5.x 和 8.x。我一开始用的 Spring 6.0 配上 JDK 8,启动直接报错,后来统一降到 5.3.x 才稳定。MySQL 8.x 的驱动类名是com.mysql.cj.jdbc.Driver,如果你还用 5.x 的com.mysql.jdbc.Driver,启动会提示驱动类已废弃,甚至直接连接失败。

3.2 Spring + MyBatis + MVC 配置文件

SSM 项目通常有三份核心配置文件:applicationContext.xml(Spring 主配置)、springMVC.xml(SpringMVC 配置)、jdbc.properties(数据库连接信息)。我建议再拆一个 mybatis-config.xml 放 MyBatis 的全局设置,虽然非必需,但结构更清爽。

jdbc.properties 这里要注意 MySQL 8 的时区参数:

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

serverTimezone=Asia/Shanghai不加的话,MySQL 8 通常会报 CST 时区错误,这个错误很典型,会出现在控制台第一屏,很多同学看到英文报错就懵了,其实就是时区没指定。useSSL=false是避免本地连接时 SSL 握手警告刷屏,不影响功能。

applicationContext.xml 的核心配置段是开启注解扫描、加载 properties、配置 Druid 数据源、声明 SqlSessionFactory、配置 Mapper 扫描器:

<context:component-scan base-package="com.hr"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.hr.mapper"/> </bean>

关键点在于<context:component-scan>里要把 Controller 排除掉,否则 Spring 和 SpringMVC 两套容器都会扫描 Controller,导致事务代理混乱、请求映射冲突。这是 SSM 整合中很隐蔽的一个问题,报错往往不是立刻出现,而是表现为某些功能偶尔失效,排查起来特别费时间。

springMVC.xml 的配置核心是开启注解驱动、设置 JSP 视图解析器、放行静态资源:

<mvc:annotation-driven/> <context:component-scan base-package="com.hr.controller"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:default-servlet-handler/>

mvc:default-servlet-handler这个配置容易被忽略,但它很重要。如果不配,你的 CSS、JS、图片这些静态资源会被前端控制器拦截,页面上所有样式全部失效,控制台会报 404。我见过很多同学配好了后端接口却卡在静态资源上,页面白板一片,其实就是这一行配置的事。

3.3 用 IDEA 跑通 JavaWeb 项目的环境配置

工欲善其事,必先利其器。我用的是 IntelliJ IDEA,这里分享一下 IDEA 跑 JavaWeb 项目最关键的几个配置点。

第一,Maven 配置。打开 Settings -> Maven,把 Maven home path 指向你本地解压的 Maven 目录,User settings file 指向conf/settings.xml。强烈建议在 settings.xml 里配置阿里云镜像,否则你下载 Spring 全家桶依赖能等到怀疑人生。我用的镜像配置是这样的:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

第二,Project Structure 里确认 SDK 是 JDK 1.8,Language level 也是 8,否则代码里用 Lambda 表达式或者 try-with-resources 会报语法错误。

第三,配置 Tomcat。点击 Run -> Edit Configurations -> 左上角加号 -> Tomcat Server -> Local,然后配置 Deployment,把 artifact 选成HRSystem:war exploded。这里记住一个经验:开发调试用 war exploded 热部署模式,上线部署用 war 包。exploded 模式修改 JSP 或静态资源不用重启就能生效,能省大量时间。

第四,如果启动时端口冲突,Tomcat 默认 8080 端口,改一下 HTTP port 就行。MySQL 如果也是 3306 端口被占用,需要在 jdbc.properties 里同步更新。

4. 人事业务模块落地:从 Controller 到 Mapper 的完整链路

4.1 登录与权限拦截:SSM 里的拦截器实践

登录功能是人事系统的门面,所有操作都建立在“你是谁、你能干什么”的基础上。我用 SpringMVC 的拦截器做了一套轻量级权限控制。

登录的 Controller 接收用户名和密码,调用 Service 查 admin 表,校验通过后把用户信息放进 session。同时实现一个LoginInterceptor,继承HandlerInterceptorAdapter,在preHandle方法里判断 session 中是否有用户,没有就重定向到登录页。

public class LoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object admin = session.getAttribute("loginUser"); if (admin == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

在 springMVC.xml 注册拦截器并配置拦截规则:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/css/**"/> <mvc:exclude-mapping path="/js/**"/> <mvc:exclude-mapping path="/images/**"/> </mvc:interceptor> </mvc:interceptors>

这里有两个细节值得注意。第一,静态资源必须放行,否则登录页都加载不出 CSS。第二,/login这个路径必须排除掉,否则你永远进不了登录页面,会陷入“无法登录所以无法进入系统,系统不允许匿名访问所以无法登录”的死循环。

4.2 员工分页与多条件查询:PageHelper 的实践

员工管理是人事系统最核心的模块。我在实现时给它设计了三个维度:完整列表展示、按部门筛选、按关键字搜索。为了不让页面一次加载几千条数据,我还接入了 PageHelper 分页插件。

Controller 层的代码逻辑比较典型:

@Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @RequestMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize, String deptId, String keyword, Model model) { PageInfo<Employee> pageInfo = employeeService.findPage( pageNum, pageSize, deptId, keyword); model.addAttribute("pageInfo", pageInfo); model.addAttribute("deptId", deptId); model.addAttribute("keyword", keyword); return "employee/list"; } }

Service 层只做一件事,调用 Mapper 并启动分页:

@Override public PageInfo<Employee> findPage(Integer pageNum, Integer pageSize, String deptId, String keyword) { PageHelper.startPage(pageNum, pageSize); List<Employee> list = employeeMapper.selectByCondition(deptId, keyword); return new PageInfo<>(list); }

Mapper 接口对应的方法是:

List<Employee> selectByCondition(@Param("deptId") String deptId, @Param("keyword") String keyword);

Mapper XML 里的动态 SQL 是核心,<where>标签可以自动去掉多余的 AND,<if>标签实现条件拼接:

<select id="selectByCondition" resultType="com.hr.entity.Employee"> SELECT * FROM employee e <where> <if test="deptId != null and deptId != ''"> AND e.dept_id = #{deptId} </if> <if test="keyword != null and keyword != ''"> AND (e.name LIKE CONCAT('%', #{keyword}, '%') OR e.emp_no LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY e.id DESC </select>

这里我吃过一个亏,就是 MySQL 的 like 查询如果不加 CONCAT 而直接写LIKE '%#{keyword}%',MyBatis 会把它当成普通字符串处理,结果查询出来永远为空。正确写法是CONCAT('%', #{keyword}, '%')。这种细节不实际跑一遍根本发现不了,属于典型业务开发经验。

4.3 考勤与薪资模块:两种常见业务模式的实现

考勤模块我采用的是“每天打卡一次、按月汇总”的轻量模式。数据录入通过表单提交,工作日期和员工 ID 唯一,重复提交时更新记录而不是插入新记录。我利用INSERT ... ON DUPLICATE KEY UPDATE来做这种处理,需要配合唯一索引(emp_id, work_date):

<insert id="insertOrUpdate"> INSERT INTO attendance (emp_id, work_date, sign_in_time, sign_out_time, status) VALUES (#{empId}, #{workDate}, #{signInTime}, #{signOutTime}, #{status}) ON DUPLICATE KEY UPDATE sign_in_time = #{signInTime}, sign_out_time = #{signOutTime}, status = #{status} </insert>

薪资模块的逻辑更贴近业务。工资条的实际发放值actual_salary不是手填的,而是根据基础工资、奖金、扣款计算出来的:base_salary + bonus - deduction。我在 Service 层的generateSalary方法里做计算,然后批量写入 salary 表。

前端 JSP 层面,薪资列表页展示工资条,管理员可以按月生成、标记发放。这里我引入了 JSTL 的<fmt:formatNumber>标签来做金额格式化,避免页面上出现 10000.0 这种不好看的数据:

<fmt:formatNumber value="${salary.actualSalary}" pattern="#,##0.00"/>

5. JSP页面与前端交互:列表、表单、校验一网打尽

5.1 列表页与分页展示

后端数据准备好了,前端页面是用户体验的第一道关卡。人事系统的页面我用了 JSP + JSTL + EL 表达式来完成数据渲染,再配合 CSS + JavaScript 做交互效果。

员工列表页最核心的逻辑是分页导航和条件查询的表单保持。分页用 JSTL 的<c:forEach>遍历页号,点击页号时把当前查询条件带回去。我起初犯过一个错误:点击第二页后查询条件丢失了,因为请求参数里没携带 deptId 和 keyword。解决办法是在分页链接里把查询条件作为参数拼上去:

<c:forEach begin="1" end="${pageInfo.pages}" var="pageNum"> <a href="${pageContext.request.contextPath}/employee/list?pageNum=${pageNum} &deptId=${deptId}&keyword=${keyword}">${pageNum}</a> </c:forEach>

这种“参数透传”是 JSP 列表页最容易被忽略的细节,却是系统是否好用的关键分水岭。用户翻第二页就丢了筛选条件,很多系统就是这么被吐槽“难用”的。

5.2 表单回显与 JS 校验

新增和编辑员工我共用了一张表单页面,通过 URL 参数区分模式:新增时 form 里的 action 指向/employee/add,编辑时指向/employee/update,并且后端把当前员工对象放进 Model,前端通过${employee.empNo}这样的 EL 表达式回显数据。

JavaScript 校验这块,我做了三件事:必填项非空校验、手机号格式校验、邮箱格式校验。手机上号码校验用正则/^1[3-9]\d{9}$/,测试群里反馈说这个表达式在 Chrome 和 Edge 上都很稳定。校验通过后再提交表单,不通过则 alert 提示并阻止提交。

有一点我觉得值得强调:前端校验只是提升体验,后端 Service 层必须做二次校验。我在 Controller 层手动判断了姓名是否为空、手机号正则是否匹配,避免绕过页面直接构造请求进库。做企业系统不能只防老实人,该堵的口子都得堵上。

5.3 CSS 与公共资源的组织方式

页面样式我采用了统一的公共样式表style.css,所有页面都在<head>里通过<link>引入。为了让页面更统一,我定义了一套公共的按钮类.btn-primary、.btn-danger、表格类.table-bordered,这样任何页面要用按钮和表格,直接套类名就行,不用每页重写样式。

这里有一个上线的陷阱要提醒大家:JSP 页面里的 CSS、JS 引用路径一定用${pageContext.request.contextPath},不要写死/HRSystem/xxx这种绝对路径。我第一次部署到不同机器上,项目名变了,所有静态资源全部 404,页面直接裸奔。改用${pageContext.request.contextPath}后,不管部署成什么名字都能自动适配,这个坑踩得记忆深刻。

前端和后台的交互方面,如果有些动作不想刷新整页,可以用 jQuery 的$.ajax配合@ResponseBody返回 JSON。我在删除员工、标记工资发放这两个操作上用了这种方式,前端弹窗确认后异步请求后端,成功后局部刷新表格,体验比整页刷新顺滑很多。

6. Maven 构建与部署:从 IDEA 到 Tomcat 的生产之路

6.1 本地运行全流程

整个项目的运行流程,我梳理成一条清晰的链路。首先确保本机装好了 JDK 8、Maven 3.6+、MySQL 5.7/8.0、Tomcat 8.5/9.0。然后按顺序操作:

  1. 启动 MySQL 服务,用 Navicat 或命令行执行项目附带的hr_db.sql,初始化数据库和五张表。
  2. 在 IDEA 中打开项目,等待 Maven 自动下载依赖,第一次可能比较久,建议先配好阿里云镜像。
  3. 修改jdbc.properties,确认数据库密码和本机一致。
  4. 配置 Tomcat 并添加项目 artifact,启动。
  5. 浏览器访问http://localhost:8080/HRSystem/login,输入预置的管理员账号 admin/admin123 登录。

很多同学卡在第一步“怎么建库建表”上。如果你用的是 MySQL 8,建议直接在命令行执行source命令,或者用图形化客户端打开 SQL 文件执行。如果你在导入时遇到编码问题,优先把 SQL 文件的编码转换成 UTF-8 再执行,中文乱码基本都是这个原因。

6.2 打包 war 的部署实践

开发环境跑通只是第一步,真正的项目还要能部署到独立的 Tomcat。Maven 打包命令是:

mvn clean package

执行完毕后,在项目target目录下会生成HRSystem.war。把这个 war 文件复制到 Tomcat 的webapps目录,启动 Tomcat,它会自动解压并部署项目。访问路径就是http://服务器IP:8080/HRSystem/。

打包前有几个坑值得专门说。第一,pom.xml中的<finalName>决定了 war 包的名字,也决定了访问路径,如果想改访问路径,改这里比改 Tomcat 配置更直接。第二,Maven 打包默认不会把src/main/java下的 XML 文件打包进去,如果你的 Mapper XML 不是放在src/main/resources而是和接口放在一起,需要在 build 中加入资源处理配置,否则部署后所有查询都会报Invalid bound statement。我在实际开发中统一把 Mapper XML 放在resources/mapper目录下,就是为了规避这个典型问题。

第三,部署时数据库连接信息如果是在 jdbc.properties 中写死的,每次换环境都要重新打包。更好的做法是用 JVM 环境变量替换密码部分,比如${DB_PASSWORD},在 Tomcat 的setenv.sh或系统环境变量中定义这个值。这个技巧对大型项目的多环境部署特别重要,我这个系统虽然规模不大,但我也提前用了这种写法,后续换环境只需要改环境变量而不需要重新编译。

6.3 部署之后常见的启动失败问题

我把实际运行中遇到过的报错整理成对照表,希望能帮你快速定位问题。

报错现象可能原因解决思路
启动报 ClassNotFoundException依赖没打全,或打包时 lib 目录缺失确认 Maven 依赖完整,检查 war 中 WEB-INF/lib 是否有 jar
访问页面报 404注解路径不对,或视图路径拼错检查 @RequestMapping 前缀和 InternalResourceViewResolver 的 prefix 是否对应
控制台报 NoSuchBeanDefinitionException扫描包路径写错确认 component-scan base-package 是否覆盖到实际包名
查询 SQL 报 Invalid bound statementMapper XML 没被扫描到检查 mybatis mapper-locations 路径和 XML 文件是否在 target 中
MySQL 连接报 SSL 或时区错误URL 参数缺失确认 serverTimezone 和 useSSL 参数已配置
中文乱码字符集不统一数据库、JSP、Tomcat 连接的 characterEncoding 全部统一为 UTF-8

这些报错我在调试时基本都碰过一轮,现在回头看大多都是配置层面的问题,只要按部就班检查,基本都能在十分钟内解决。排查报错的第一原则是“看完整堆栈”,不要只看第一行就慌,SSM 框架的报错往往在中间几行才露出真实原因。

7. 把 JSP、CSS 和 JavaScript 的组织再往前推进一步

7.1 公共头部与尾部的组件化思路

写了几十个 JSP 页面之后,你一定会发现大量重复代码:每个页面头部都有一样的前缀声明、CSS 引用、导航栏,尾部都有一样的脚本引用和版权信息。我可以对 JSP 进行简单的组件化处理,把公共部分抽成独立文件,通过<%@ include file="common/header.jsp" %>静态引入。

这种方式虽然比现代前端框架的组件落后,但在传统 JSP 项目里已经能极大提升维护效率。比如导航栏多了一个菜单入口,只需要改 header.jsp 一个文件,所有页面同步生效,不用逐个页面去改,那个效率提升是肉眼可见的。

7.2 JS 函数的复用封装

JavaScript 部分我也做了模块化封装。我把常用的提示、弹窗确认、Ajax 请求封装成公共函数,放在js/common.js里,比如:

function confirmDelete(url, tips) { if (!tips) { tips = '确定要删除这条记录吗?'; } if (confirm(tips)) { $.ajax({ url: url, type: 'POST', success: function (resp) { if (resp === 'success') { alert('操作成功'); window.location.reload(); } else { alert('操作失败'); } } }); } }

这样页面里删除按钮只需要写confirmDelete('${pageContext.request.contextPath}/employee/delete/' + id, '确认删除该员工?')就能实现统一的交互逻辑。一致性上也有保证,后期想改提示样式或弹窗样式,只改 common.js 一处即可。

7.3 页面性能与细节体验

JSP 页面虽然简单,但性能体验上仍有一些可以做好的地方。我在列表页的数据量边界做了一层保护,如果单页数据超过 100 条,就提示用户缩小查询范围。这个设计的出发点很简单:数据库查询再快,浏览器渲染上千行 DOM 都会卡顿,用户翻页体验也会变差。

还有一个小细节:表单提交之后,我会做一个“防止重复提交”的处理。方法是在提交按钮上做 JS 控制,第一次点击后禁用按钮,避免用户因为网络延迟连续点击,导致数据库里出现重复数据。这个在实际使用中经常被忽略,但对人事系统这种“录入准确第一”的场景,特别值得重视。

写在最后的一次完整验证

我把整个项目从头到尾又跑了一遍完整流程:清库重建、IDEA 启动、Tomcat 部署、打包 war 放进独立 Tomcat 运行、浏览器模拟管理员登录、新增部门、新增员工、录入考勤、生成工资、查验数据。每个环节跑完后,基本可以确定这个系统的链路是通的,数据一致性和权限拦截都符合预期。实际开发这类 SSM 人事管理系统,我的个人体会是:业务不复杂,复杂的是框架整合的细节,环境和配置的坑要远比业务代码的坑多。建表时多花半小时思考字段设计,工程启动时多花十分钟核对配置文件,后面落地模块会顺畅很多。如果你也正在做类似的 JavaWeb 人事管理系统,照着这个思路从数据库设计开始一步步搭建,跑通后再扩展公告管理、请假审批这些延展功能,会比你直接从网上找一套代码抄下来改名字有收获得多。

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

Redis缓存店铺查询:读多写少场景下的设计与实践

店铺项目做到第二天&#xff0c;终于轮到性能优化里最经典也最实用的一环&#xff1a;把店铺查询信息加进Redis缓存。“黑马店铺”这个项目本身就是读多写少的典型——用户进首页看店铺列表、点店铺详情、搜索店铺&#xff0c;几乎全是查询操作。如果每次查询都直接打到MySQL&a…

作者头像 李华
网站建设 2026/10/1 3:29:26

RuoYi-Vue二次开发第一步:Git克隆与分支切换

1. 前言&#xff1a;RuoYi-Vue 二次开发的第一脚&#xff0c;从拉代码开始后台管理系统做久了&#xff0c;你会发现市面上能直接拿来改的开源项目就那么几个&#xff0c;RuoYi-Vue 绝对算得上绕不开的一个。它是基于 Spring Boot Vue 的经典前后端分离脚手架&#xff0c;权限、…

作者头像 李华
网站建设 2026/10/1 3:29:08

物理智能云边端协同架构:从分层设计到工程落地的实操指南

1. 物理智能云边端协同架构到底在解决什么问题第一次听到“物理智能云边端协同”这个组合词&#xff0c;很多人会下意识把它归到“又一个概念包装”的筐里。我一开始也这么想&#xff0c;直到真正接触了几个把感知、决策、执行串起来的项目&#xff0c;才发现这个词组其实描述的…

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

Linux虚拟机安装PCL:依赖梳理、源码编译与点云避坑

1. 先把PCL的依赖链摸清楚&#xff0c;再谈装不装得上很多人第一次在 Linux 虚拟机里搞 PCL&#xff0c;是先搜一条安装命令&#xff0c;敲下去&#xff0c;等到报错再回头查。这套流程在 PCL 上大概率会撞墙三次以上。原因很简单&#xff1a;PCL 不是那种"一个 tar 包解压…

作者头像 李华
网站建设 2026/10/1 3:27:58

Ubuntu 22.04安装Claude Code并在VSCode中集成完整指南

Ubuntu 22.04 安装 Claude Code 并在 VSCode 中跑通的完整记录最近项目里频繁要写自动化脚本和代码生成工具&#xff0c;朋友推荐我试试 Claude Code。在 Ubuntu 22.04 下装机、配 VSCode 的过程还是踩了不少坑的——尤其是版本匹配、Node.js 环境、扩展加载路径这几个地方&…

作者头像 李华
网站建设 2026/10/1 3:27:52

RFM2g反射内存驱动详解:buffer读写、事件通知与调试避坑

简介&#xff1a;面向VME总线2GHz反射内存&#xff08;RFM2g&#xff09;的驱动与功能开发包&#xff0c;适用于嵌入式控制系统、工业测控以及需要和RFM2g板卡完成快速数据交互的底层开发与集成场景。资源内置142个文件&#xff0c;压缩包体积约11.11MB&#xff0c;以DLL动态库…

作者头像 李华