SSM框架实战:从零构建人事管理系统,详解Java Web三层架构
2026/8/31 19:17:51 网站建设 项目流程

简介:这是一套面向Java初学者与中级开发者的SSM框架实战项目源码,聚焦企业级人事管理核心业务场景,涵盖员工、部门、职位、公告、下载中心等完整模块,助力开发者掌握Web应用开发全流程。资源包共284个文件,含29个核心Java业务类(如EmployeeController、NoticeController)、18个JSP页面、47个Jar依赖包、20个JS脚本及7个CSS样式文件,配合SQL建库脚本与Layui前端组件,构成可独立运行的前后端一体化系统;压缩包大小30.77MB。已有1475人学习下载,配套详细导入运行文档,覆盖MySQL数据库初始化、IDEA/Eclipse工程配置、Tomcat部署及常见启动问题排查,代码结构清晰、分层规范,Interceptor权限拦截与Spring MVC文件上传下载功能均经实测可用,是理解SSM整合原理与企业级CRUD开发模式的优质练手素材。

1. 项目概述:一个经典技术栈的实战演练场

最近在整理硬盘,翻出来一个几年前用SSM框架做的人事管理系统源码。这个项目虽然技术栈现在看来不算“新潮”,但恰恰是这种经典的组合——Spring、SpringMVC、MyBatis加上JSP和MySQL——构成了Java Web开发领域一个非常稳固和普遍的基石。很多朋友在面试或者接手老项目维护时,都会遇到它。所以,我觉得把这个项目的核心设计、开发过程中的关键细节,以及那些容易踩坑的地方系统地梳理一遍,远比单纯丢出一份源码更有价值。这个系统麻雀虽小,五脏俱全,涵盖了员工信息管理、部门架构、考勤记录、薪资核算等基础人事功能,非常适合用来理解一个典型的三层架构Web应用是如何从零搭建,并处理各种业务逻辑和数据交互的。无论你是想学习SSM框架整合,还是为毕业设计寻找一个扎实的案例,或者单纯想回顾一下经典MVC模式的应用,这个项目都能提供一个清晰的蓝本。

2. 技术选型与架构设计思路拆解

2.1 为什么是SSM而不是Spring Boot?

现在一提到Java Web,大家首先想到的可能是Spring Boot。的确,Spring Boot通过自动配置和起步依赖极大地简化了初始搭建过程。但回过头来看这个SSM项目,它的技术选型背后有非常现实的考量。首先,SSM(Spring + SpringMVC + MyBatis)是经过多年企业级应用验证的、极其成熟的框架组合。Spring作为核心的IoC(控制反转)和AOP(面向切面编程)容器,负责管理所有Bean的生命周期和依赖注入;SpringMVC作为表现层框架,清晰地分离了控制器、模型和视图的职责;MyBatis则作为持久层框架,提供了灵活且强大的SQL映射能力。选择SSM,意味着你需要手动配置大量的XML文件(如applicationContext.xml,spring-mvc.xml,mybatis-config.xml)和web.xml。这个过程看似繁琐,但恰恰是理解框架工作原理、明确各个组件职责边界的最佳途径。你会清楚地知道一个请求是如何被DispatcherServlet拦截,如何匹配到对应的ControllerService层如何被注入,以及SqlSession是如何被创建和管理的。这种“透明感”对于打牢基础至关重要。很多在Spring Boot中被“约定大于配置”隐藏起来的细节,在SSM中都需要你亲手配置,这能帮你建立起对Java Web应用骨架的深刻认知。

2.2 各层职责与数据流转设计

这个人事管理系统的架构严格遵循了典型的三层架构模式,每一层都有明确的职责,保证了代码的高内聚和低耦合。

  1. 表现层(View & Controller):使用JSP作为视图模板,结合JSTL标签库和EL表达式来渲染页面。SpringMVC的Controller负责接收前端请求(通常是表单提交或Ajax调用),进行简单的参数校验和类型转换,然后调用对应的业务逻辑层(Service)方法。之后,根据业务方法返回的结果,决定跳转到哪个JSP页面进行渲染,或者直接返回JSON数据给前端。在这个人事系统中,像“新增员工”、“查询考勤”这样的操作,其入口点都在各个XXXController中。

  2. 业务逻辑层(Service):这是系统的核心,承载了所有的业务规则和流程。例如,计算员工当月薪资,不仅仅是从数据库读取基本工资,还需要结合考勤记录、绩效评分、社保公积金扣除项等一系列复杂计算。Service接口定义了业务契约,ServiceImpl类则提供了具体的实现。在这一层,我们会通过Spring的@Transactional注解来管理事务,确保例如“调动部门”同时更新员工表和部门人员统计这样的操作具有原子性。

  3. 持久层(Mapper / Dao):使用MyBatis与MySQL数据库进行交互。我们为每个实体(如EmployeeDepartment)创建对应的Mapper接口和XML映射文件。Mapper接口声明数据访问方法,而XML文件则编写具体的SQL语句,并定义如何将查询结果集映射到Java实体对象。MyBatis的优势在于SQL的可控性,对于复杂的人员综合查询(多表关联、动态条件),我们可以编写高度优化的SQL,这是很多全自动ORM框架难以比拟的。

  4. 实体层(Model / Entity):就是普通的JavaBean,例如Employee类,其属性对应数据库employee表的字段。它在各层之间传递数据。

一个典型的“添加员工”请求流转如下:JSP表单提交 ->EmployeeController.receiveForm()->EmployeeService.addNewEmployee()->EmployeeMapper.insert()-> 执行MySQL INSERT语句。Service层在处理过程中,可能会调用DepartmentService来验证部门ID的有效性,体现了业务逻辑的组合。

2.3 开发环境与工具链的选择

工欲善其事,必先利其器。当时这个项目主要使用以下工具链,它们至今依然适用:

  • IDE:IntelliJ IDEA Ultimate。它对Java EE、Spring、MyBatis的支持非常出色,特别是智能提示、XML配置导航和重构功能。社区版对于SSM开发也足够。
  • 项目构建:Maven。通过pom.xml统一管理所有依赖(Jar包),如Spring 5.x系列、MyBatis 3.5.x、MyBatis-Spring整合包、MySQL Connector/J、JSTL、Jackson(用于JSON处理)等。Maven的依赖传递和生命周期管理让项目构建标准化。
  • 数据库工具:Navicat for MySQL或MySQL Workbench。用于直观地进行数据库设计、执行SQL脚本和数据管理。
  • 应用服务器:Apache Tomcat 8.5或9.x。作为Servlet容器,部署和运行我们的Web应用。
  • 版本控制:Git。使用Git进行代码版本管理是必须的,无论是本地学习还是团队协作。

注意:在IDEA中开发JSP页面时,你可能会遇到语法高亮不全或函数无法跳转的问题。这通常是因为IDEA没有正确识别你的Web模块。你需要确保项目模块配置中,Web资源目录(如src/main/webapp)被正确标记为“Web资源根目录”。同时,对于较新的IDEA版本,可能需要安装像“JSP”或“JavaEE”相关的插件来获得更好的支持。

3. 核心模块详细设计与实现解析

3.1 数据库设计与实体建模

任何系统的基石都是数据库。一个设计良好的数据库结构能极大降低后续业务开发的复杂度。这个人事系统主要包含以下几张核心表:

  • 部门表 (department):存储部门信息,如部门ID、名称、上级部门ID(实现树形结构)、部门经理ID等。这里使用了自关联的外键来构建部门层级。
  • 员工表 (employee):核心表,字段包括员工ID、工号、姓名、性别、生日、部门ID(外键)、职位、入职日期、基本工资、联系方式等。department_id关联到部门表。
  • 考勤记录表 (attendance):记录员工每日考勤。包含记录ID、员工ID(外键)、考勤日期、上班打卡时间、下班打卡时间、是否迟到、是否早退、备注等。这里需要注意日期和时间的字段类型选择(DATETIMEDATETIME),以及索引的建立(如在employee_idattendance_date上建立复合索引以加速查询)。
  • 薪资记录表 (salary):每月生成的薪资明细。包含记录ID、员工ID、对应年月、基本工资、考勤扣款、绩效奖金、社保公积金扣除、实发工资等。这张表通常是计算后的结果表,数据由后台作业或特定业务操作生成。

实体类的设计基本与表字段一一对应,但会加入一些业务逻辑需要的属性。例如,Employee类中除了基本字段,可能会有一个Department类型的对象属性,用于在查询员工时直接关联获取其部门信息,而不是仅仅一个部门ID。这需要在MyBatis的映射文件中通过``标签实现“一对一”关联查询。

<!-- 在EmployeeMapper.xml中 --> <resultMap id="EmployeeWithDeptMap" type="com.example.hr.entity.Employee"> <id property="id" column="id"/> <result property="name" column="name"/> <!-- ... 其他字段映射 --> <association property="department" javaType="com.example.hr.entity.Department"> <id property="id" column="dept_id"/> <result property="name" column="dept_name"/> </association> </resultMap> <select id="selectEmployeeWithDepartment" resultMap="EmployeeWithDeptMap"> SELECT e.*, d.id as dept_id, d.name as dept_name FROM employee e LEFT JOIN department d ON e.department_id = d.id WHERE e.id = #{id} </select>

3.2 Spring与MyBatis的整合关键配置

整合的核心是让Spring来管理MyBatis的SqlSessionFactoryMapper接口的代理对象。主要配置集中在applicationContext.xml(或分拆的spring-dao.xml)中。

  1. 数据源配置:使用Spring提供的DriverManagerDataSource(学习用)或更高效的连接池如HikariDataSource(生产推荐)来配置数据库连接信息。

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hr_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="yourpassword"/> </bean>
  2. SqlSessionFactoryBean:这是整合的枢纽。它需要注入数据源,并指定MyBatis全局配置文件(mybatis-config.xml)的路径和Mapper XML文件的位置。

    <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>
  3. MapperScannerConfigurer:自动扫描指定的包路径,将Mapper接口注册为Spring Bean。这样我们就可以在Service层直接使用@Autowired注入EmployeeMapper了。

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.hr.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>

mybatis-config.xml中,我们可以进行一些全局设置,比如开启驼峰命名自动映射(mapUnderscoreToCamelCase),配置类型别名,或者添加插件(如分页插件PageHelper)。

3.3 SpringMVC控制器与JSP视图的协作

SpringMVC的配置主要在spring-mvc.xmlweb.xml中。web.xml中需要配置DispatcherServlet,并指定其加载的Spring配置文件。

spring-mvc.xml中,关键配置包括:

  • 组件扫描:让Spring自动发现带有@Controller注解的类。
    <context:component-scan base-package="com.example.hr.controller"/>
  • 注解驱动:启用MVC注解,如@RequestMapping,@ResponseBody等。
    <mvc:annotation-driven/>
  • 视图解析器:配置JSP文件的前缀和后缀。当控制器方法返回一个字符串如"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>
  • 静态资源处理:放行CSS、JS、图片等静态资源,不经过DispatcherServlet
    <mvc:resources mapping="/static/**" location="/static/"/>

一个典型的控制器方法如下:

@Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @GetMapping("/list") public String listEmployees(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize, Model model) { PageHelper.startPage(pageNum, pageSize); // 使用PageHelper分页 List<Employee> employees = employeeService.getAllEmployees(); PageInfo<Employee> pageInfo = new PageInfo<>(employees); model.addAttribute("pageInfo", pageInfo); return "employee/list"; // 跳转到 /WEB-INF/views/employee/list.jsp } @PostMapping("/add") @ResponseBody // 返回JSON,用于Ajax提交 public Map<String, Object> addEmployee(@RequestBody Employee employee) { Map<String, Object> result = new HashMap<>(); try { employeeService.addEmployee(employee); result.put("success", true); result.put("message", "添加成功"); } catch (Exception e) { result.put("success", false); result.put("message", "添加失败: " + e.getMessage()); } return result; } }

在JSP页面中,我们使用JSTL和EL表达式来展示数据和控制逻辑:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <c:forEach items="${pageInfo.list}" var="emp"> <tr> <td>${emp.employeeId}</td> <td>${emp.name}</td> <td>${emp.department.name}</td> <!-- 关联对象属性 --> <td> <a href="/employee/edit/${emp.id}">编辑</a> </td> </tr> </c:forEach> </table> <!-- 分页条 --> <div class="pagination"> <c:if test="${pageInfo.hasPreviousPage}"> <a href="?pageNum=${pageInfo.prePage}">上一页</a> </c:if> <c:forEach begin="1" end="${pageInfo.pages}" var="i"> <a href="?pageNum=${i}">${i}</a> </c:forEach> <c:if test="${pageInfo.hasNextPage}"> <a href="?pageNum=${pageInfo.nextPage}">下一页</a> </c:if> </div>

4. 业务功能实现与关键代码剖析

4.1 员工信息的增删改查(CRUD)与分页

这是最基础的功能,但实现质量直接影响用户体验。以查询为例,我们通常需要支持多条件组合查询,如按姓名模糊匹配、按部门筛选、按入职日期范围查询等。在Service层,我们会构建一个查询条件对象(例如EmployeeQuery),在Mapper的XML中使用MyBatis的动态SQL标签、``来灵活组装SQL。

<!-- EmployeeMapper.xml --> <select id="selectByCondition" parameterType="EmployeeQuery" resultMap="BaseResultMap"> SELECT * FROM employee <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="departmentId != null"> AND department_id = #{departmentId} </if> <if test="entryDateStart != null"> AND entry_date >= #{entryDateStart} </if> <if test="entryDateEnd != null"> AND entry_date <= #{entryDateEnd} </if> </where> ORDER BY id DESC </select>

分页我们使用了国内非常流行的PageHelper插件。只需在查询调用PageHelper.startPage(pageNum, pageSize),下一次的查询就会自动进行物理分页。查询结果用PageInfo包装后传给前端,它包含了总记录数、总页数、当前页数据列表等丰富信息,方便前端生成分页导航。

实操心得:使用PageHelper时,务必确保PageHelper.startPage()紧跟在需要分页的Mapper方法调用之前,且中间不能有其它数据库查询操作,否则分页会失效。另外,对于复杂的多表关联查询,PageHelper可能会生成count查询性能较差的SQL,此时可以考虑手动编写count语句或优化查询逻辑。

4.2 考勤统计与薪资计算逻辑

这是系统的业务核心,逻辑相对复杂。

考勤统计:通常会有定时任务(如使用Spring的@Scheduled注解)在每天凌晨统计前一天的考勤异常(迟到、早退、旷工)。统计逻辑依赖于attendance表中的打卡时间与预设的上下班时间规则进行比对。结果可以写入一张日统计表,也可以作为薪资计算的直接输入。

薪资计算:这是一个典型的批量、有状态的计算过程。通常每月执行一次。计算服务(SalaryCalculateService)的流程大致如下:

  1. 获取计算周期内所有需要核算的员工列表(如在职员工)。
  2. 遍历每个员工: a.读取基础信息:基本工资、岗位津贴等。 b.汇总考勤数据:根据当月考勤记录,计算迟到/早退/旷工扣款。 c.汇总绩效数据(如果有):根据绩效评分计算绩效奖金。 d.计算社保公积金:根据当地政策和员工缴费基数计算个人与公司缴纳部分(通常个人部分从应发工资中扣除)。 e.计算个税:根据累计应纳税所得额和税率表计算(这是一个更复杂的逻辑,可能涉及累计预扣法)。 f.汇总计算实发工资 = 应发工资(基本+津贴+奖金) - 考勤扣款 - 社保公积金个人部分 - 个人所得税
  3. 生成薪资记录:将每位员工的详细计算结果插入salary表。这个过程必须在一个事务中完成,确保要么全部成功,要么全部回滚,防止产生部分员工的薪资数据。
  4. 生成薪资条:可以同时生成PDF或HTML格式的电子薪资条,通过邮件或系统消息发送给员工。
@Service @Transactional(rollbackFor = Exception.class) public class SalaryCalculateServiceImpl implements SalaryCalculateService { @Autowired private EmployeeMapper employeeMapper; @Autowired private AttendanceMapper attendanceMapper; @Autowired private SalaryRecordMapper salaryRecordMapper; @Override public void calculateSalaryForMonth(String yearMonth) { // 1. 获取所有在职员工 List<Employee> employees = employeeMapper.selectOnJobEmployees(); for (Employee emp : employees) { SalaryRecord record = new SalaryRecord(); record.setEmployeeId(emp.getId()); record.setYearMonth(yearMonth); record.setBaseSalary(emp.getBaseSalary()); // 2. 计算考勤扣款 BigDecimal attendanceDeduction = calculateAttendanceDeduction(emp.getId(), yearMonth); record.setAttendanceDeduction(attendanceDeduction); // 3. 计算社保公积金 (简化示例) BigDecimal socialSecurity = emp.getBaseSalary().multiply(new BigDecimal("0.105")); //个人比例10.5% record.setSocialSecurity(socialSecurity); // 4. 计算应发和实发 BigDecimal totalPayable = record.getBaseSalary().subtract(attendanceDeduction); // 假设只有基本工资和考勤扣款 BigDecimal tax = calculateTax(totalPayable.subtract(socialSecurity).subtract(new BigDecimal("5000"))); // 个税起征点5000 record.setTax(tax); BigDecimal netSalary = totalPayable.subtract(socialSecurity).subtract(tax); record.setNetSalary(netSalary); // 5. 插入记录 salaryRecordMapper.insert(record); } // 所有员工计算完成,事务提交 } // ... 其他具体计算方法 }

4.3 部门树形结构的展示与维护

部门通常具有层级关系(如公司->事业部->部门->小组)。在数据库中,我们使用parent_id(上级部门ID)字段来实现这种树形结构。在展示时,我们需要将其递归地渲染成树状列表。

一种常见的做法是在Service层编写一个递归方法,将扁平的部门列表构建成树形结构。更高效的做法是在一次查询中获取所有部门,然后在内存中通过Map来构建树,避免多次查询数据库。

public List<DepartmentVO> buildDepartmentTree(List<Department> allDepts) { List<DepartmentVO> rootList = new ArrayList<>(); Map<Integer, DepartmentVO> nodeMap = new HashMap<>(); // 第一遍,将所有部门转为VO并放入Map for (Department dept : allDepts) { DepartmentVO vo = convertToVO(dept); nodeMap.put(dept.getId(), vo); } // 第二遍,构建父子关系 for (DepartmentVO vo : nodeMap.values()) { if (vo.getParentId() == null || vo.getParentId() == 0) { rootList.add(vo); // 根节点 } else { DepartmentVO parent = nodeMap.get(vo.getParentId()); if (parent != null) { parent.getChildren().add(vo); } } } return rootList; }

在前端,可以使用递归的JSP标签或者JavaScript库(如zTree、layui tree)来渲染这个树形结构。在维护(增删改)部门时,需要特别注意处理层级关系,例如删除一个部门时,需要决定是级联删除其所有子部门,还是将其子部门挂到其他部门下。

5. 开发部署中的常见问题与解决方案

5.1 依赖冲突与配置错误

这是SSM整合初期最常见的问题。例如,Spring、MyBatis、SpringMVC以及它们各自依赖的第三方库(如日志框架slf4j/logback、JSON处理器jackson)版本不兼容。

  • 症状:项目启动时抛出ClassNotFoundException,NoSuchMethodError, 或BeanCreationException,提示某个类找不到或方法签名不匹配。
  • 排查
    1. 首先检查pom.xml,使用mvn dependency:tree命令查看完整的依赖树,寻找是否存在同一个jar包的不同版本。常见的冲突点有:spring-core版本与spring-webmvc不一致;mybatismybatis-spring整合包版本不匹配;多个日志框架jar包共存。
    2. 使用``标签排除掉不需要的传递性依赖。
    3. 仔细核对Spring配置文件(applicationContext.xml,spring-mvc.xml)中的命名空间(namespace)版本是否与所使用的Spring版本匹配。错误的命名空间URI会导致配置标签无法被解析。
  • 解决方案:锁定一套经过验证的稳定版本组合。例如,Spring 5.2.x + MyBatis 3.5.x + MyBatis-Spring 2.0.x。在Maven中央仓库搜索这些组件时,注意查看其“编译依赖”项,确保它们相互兼容。

5.2 事务管理不生效

在Service层的方法上添加了@Transactional注解,但操作执行失败后数据并没有回滚。

  • 原因分析
    1. 异常类型不对:默认情况下,@Transactional只在遇到运行时异常(RuntimeException)和错误(Error)时回滚。如果方法抛出的是检查型异常(如IOException,SQLException),事务不会回滚。需要使用@Transactional(rollbackFor = Exception.class)来指定对所有异常回滚。
    2. 方法访问权限@Transactional是基于AOP代理实现的。如果事务方法被定义在同一个类的另一个非事务方法内部调用,或者方法是privateprotected的,则事务注解可能失效。因为代理对象无法拦截内部调用。
    3. 数据库引擎不支持:使用的MySQL表引擎是MyISAM,它不支持事务。必须使用InnoDB引擎。
    4. 未启用事务管理:在Spring配置中忘记配置事务管理器(DataSourceTransactionManager)或未开启注解驱动(``)。
  • 解决方案
    1. 明确指定rollbackFor
    2. 确保事务方法是public的,并且是通过Spring代理对象调用的(即从Spring容器中获取的Bean调用)。
    3. 检查并修改数据库表引擎为InnoDB。
    4. applicationContext.xml中正确配置:
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

5.3 MyBatis使用中的典型问题

  • #{}和${}的区别与误用:这是MyBatis面试必问题,也是实际开发中容易出错的地方。
    • #{}是预编译处理,MyBatis会将其替换为?,然后使用PreparedStatement的set方法安全地赋值,能有效防止SQL注入。绝大多数情况下都应该使用#{}
    • ${}是字符串替换,直接将参数值替换到SQL语句中。存在SQL注入风险。仅在需要动态传入列名、表名等SQL关键字时使用。例如,在动态排序时:ORDER BY ${orderByColumn}。使用${}时,必须对传入参数进行严格的校验和过滤。
  • 结果映射(ResultMap)复杂属性为null:当使用进行关联查询时,如果关联对象的属性为null,请检查:
    1. SQL查询语句的列别名是否与ResultMap中定义的column属性完全一致(大小写敏感)。
    2. 关联查询的SQL本身是否能正确查出数据。可以在数据库客户端单独执行该SQL验证。
    3. 延迟加载(懒加载)配置是否导致在Session关闭后才访问属性。如果不需要懒加载,可以在映射配置中设置fetchType="eager"
  • XML中特殊字符处理:在XML中编写SQL,小于号(<)、大于号(>)等需要转义。可以使用<![CDATA[ ... ]]>包裹SQL片段。
    <select id="selectRecent"> SELECT * FROM employee WHERE entry_date <![CDATA[ >= ]]> #{startDate} </select>

5.4 JSP页面开发与调试问题

  • JSP页面报错:/xxx.jsp not found:这通常是因为文件路径不对,或者视图解析器(InternalResourceViewResolver)的前缀后缀配置有误。确保JSP文件放在/WEB-INF/views/目录下(根据你的配置),且控制器返回的视图名与之匹配。也要检查Tomcat的部署目录下是否确实生成了对应的JSP文件。
  • EL表达式不生效,页面显示${xxx}原样:可能是因为JSP页面头部的page指令中设置了isELIgnored="true",或者使用的Servlet版本较低。确保在JSP开头使用:
    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
  • JSTL标签库无法识别:确保pom.xml中引入了JSTL依赖(如javax.servlet:jstl:1.2),并且在JSP页面顶部正确声明了标签库。
  • Ajax请求后端接收中文乱码:对于POST请求,确保前端发送时设置Content-Typeapplication/json;charset=UTF-8,或者在SpringMVC配置中添加字符编码过滤器。
    <!-- web.xml --> <filter> <filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

5.5 性能优化与安全考量

  • SQL性能:对于人事系统,随着数据量增长,员工和考勤的查询可能变慢。
    • 索引:在经常用于查询条件和关联的字段上建立索引,如employee.department_id,attendance.employee_id,attendance.attendance_date
    • 分页:务必使用数据库层面的分页(如MySQL的LIMIT),而不是在Java代码中截取List。PageHelper插件就是帮你自动生成LIMIT语句的。
    • 避免N+1查询:在MyBatis中,如果在一对多关联查询配置了懒加载,遍历一个员工列表时,访问每个员工的考勤记录会导致N条额外的SQL查询。此时应使用``进行“一对多”的集合查询,或者编写一个多表关联的SQL一次性查出所有数据。
  • 应用安全
    • XSS防护:在JSP中展示用户输入的数据时,使用JSTL的<c:out value="${input}"/>或EL表达式函数fn:escapeXml()对内容进行转义,防止脚本注入。
    • SQL注入防护:坚持使用MyBatis的#{}预编译方式。
    • CSRF防护:对于重要的操作(如删除、修改薪资),应加入CSRF Token验证。Spring Security提供了开箱即用的支持,在SSM项目中也可以手动实现,如在表单中添加一个随机Token,提交时与Session中的Token比对。
    • 权限控制:系统应区分管理员、HR、普通员工等角色,对不同角色的操作权限进行控制。可以在Controller方法上通过自定义注解和拦截器(Interceptor)来实现简单的权限校验。更复杂的可以使用Spring Security或Apache Shiro框架。

这个基于SSM的人事管理系统项目,就像一本经典的Java Web开发教科书。它可能没有用到最前沿的技术,但其中蕴含的分层思想、配置整合、事务管理、SQL优化等知识点,是构建更复杂系统的坚实基础。在开发过程中,耐心调试每一个配置,理解每一行代码背后的原理,遇到的每一个错误和其解决方案,都会成为你宝贵的经验。当你能够流畅地搭建并扩展这样一个系统时,再去学习Spring Boot,你会更加理解它“约定大于配置”带来的便利究竟简化了什么,从而能够更得心应手地使用它,甚至在需要的时候,也能从容地处理那些“约定”之外的特殊情况。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询