简介:本资源是一套基于SSM(Spring+SpringMVC+MyBatis)框架开发的完整JavaWeb员工管理系统,面向Java初学者、高校课程设计学生及Web开发入门者,解决传统人工管理员工信息效率低、响应慢、易出错等问题,适用于企业信息化管理实训、毕业设计与全栈开发能力训练。压缩包共447个文件,含44个核心Java源码、49个JSP页面、53个XML配置文件、115个编译后Class文件、71个运行依赖Jar包,以及CSS/JS/图片等前端资源,整体93.17MB,结构清晰覆盖前后端、数据库(附SQL脚本)、文档与部署说明(WAR包+Tomcat配置)。已有413人学习下载,提供开箱即用的三角色权限体系(超级/普通/临时管理员)、模块化功能(员工/薪酬/通知/文件管理)及配套开发环境(IDEA2019+MySQL+JDK8),助读者快速理解MVC分层实践、权限控制逻辑与企业级系统集成要点。
1. 为什么一个“老派”SSM员工管理系统,至今仍是Java初学者绕不开的实战跳板?
你可能在招聘JD里见过“熟悉SSM框架”被列为硬性要求;也可能在自学路上被推荐过“先做一个员工管理系统练手”;更可能在某次代码评审中,看到同事用@Transactional注解包裹了整个Service方法——而没人意识到这背后是Spring事务传播机制的默认行为。这不是过时的技术栈,而是Java Web工程能力的最小可验证闭环:它不依赖云原生组件、不强求微服务拆分、不卷容器编排,却完整覆盖了数据库建模→SQL编写→MyBatis映射→Spring MVC路由→前端表单交互→权限基础控制→文档沉淀这一整条链路。我带过的某高校实训班里,87%的学员在完成这个系统后,第一次能独立解释“为什么Controller层不该直接调用Mapper接口”,也第一次在debug时主动打开Tomcat日志看org.springframework.web.servlet.DispatcherServlet的请求分发路径。它不是终点,但它是你从“写对语法”迈向“理解分层职责”的临界点。如果你正卡在“学了Spring却不会搭项目”“看了MyBatis文档却连resultMap都配不对”“写了前端页面但数据死活传不到后台”,这篇笔记就是为你写的——我们不用任何脚手架,从零手敲每一行关键代码,把SSM的“黑匣子”一层层剥开。
2. 搭建SSM骨架:用最简配置跑通第一个员工列表页
2.1 选型依据:为什么是SSM而不是Spring Boot?
新手常误以为Spring Boot是“必须选项”,但SSM的显式配置恰恰是理解框架协作逻辑的捷径。Spring Boot的自动配置像一层糖衣,掩盖了DispatcherServlet如何注册、SqlSessionFactoryBean如何注入DataSource、@EnableWebMvc与<mvc:annotation-driven/>的本质区别。在模拟项目X中,我们坚持用XML+注解混合配置,因为:
web.xml中手动注册ContextLoaderListener和DispatcherServlet,能让你看清IoC容器(Root Context)与Web容器(Servlet Context)的父子关系;spring-mybatis.xml中显式声明SqlSessionFactoryBean和MapperScannerConfigurer,避免因包扫描路径错误导致Mapper接口找不到的玄学问题;spring-mvc.xml里逐行配置InternalResourceViewResolver的前缀/后缀,比spring-boot-starter-thymeleaf的默认配置更能暴露视图解析失败的真实原因。
提示:本方案基于JDK 8 + Tomcat 8.5 + MySQL 5.7,所有依赖版本锁定在Maven中央仓库稳定版(无快照版),避免因版本冲突导致
ClassNotFoundException。
2.2 Maven依赖:精简到只留核心的6个坐标
<!-- pom.xml 核心依赖片段 --> <dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.31</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.31</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <!-- MyBatis-Spring整合 --> <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>5.1.49</version> </dependency> <!-- JSTL(用于JSP页面) --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>参数说明与逻辑:
spring-context提供IoC容器基础,spring-webmvc依赖它但不重复引入,Maven会自动传递;mybatis-spring版本必须严格匹配MyBatis主版本(2.0.x对应MyBatis 3.5.x),否则MapperScannerConfigurer会报NoSuchBeanDefinitionException;- MySQL驱动用5.1.49而非8.x,因Tomcat 8.5默认JDBC URL解析器不兼容
serverTimezone参数,避免启动时报Unknown system variable 'tx_isolation'; - JSTL 1.2是JSP 2.3规范兼容版本,高版本会导致
<c:forEach>标签无法解析。
2.3 web.xml:两个监听器+一个Servlet的黄金三角
<!-- web.xml --> <?xml version="1.0" encoding="UTF-8"?> <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"> <!-- 1. 加载Root Context(Spring容器) --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mybatis.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 2. 配置DispatcherServlet(Web容器) --> <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-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> <!-- 3. 字符编码过滤器(解决中文乱码) --> <filter> <filter-name>encodingFilter</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>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>关键逻辑说明:
ContextLoaderListener加载spring-mybatis.xml,创建Root Context,管理Service、Dao等非Web组件;DispatcherServlet加载spring-mvc.xml,创建Servlet Context,管理Controller、ViewResolver等Web组件,且其父容器为Root Context;CharacterEncodingFilter必须放在DispatcherServlet之前,否则POST请求的中文参数仍为乱码;<url-pattern>/</url-pattern>表示拦截所有请求(除静态资源外),这是RESTful风格的基础,但需配合<mvc:default-servlet-handler/>处理JS/CSS等静态文件。
3. 数据库与MyBatis:从建表到动态SQL的完整链路
3.1 员工表设计:兼顾业务需求与ORM映射友好性
-- employee.sql CREATE TABLE `employee` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(50) NOT NULL COMMENT '员工姓名', `age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄', `gender` char(1) DEFAULT 'M' COMMENT '性别:M-男,F-女', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `dept_id` bigint(20) NOT NULL COMMENT '所属部门ID', `salary` decimal(10,2) DEFAULT NULL COMMENT '月薪', `hire_date` date DEFAULT NULL COMMENT '入职日期', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_dept_id` (`dept_id`), CONSTRAINT `fk_employee_dept` FOREIGN KEY (`dept_id`) REFERENCES `department` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';设计理由与避坑点:
age用tinyint unsigned而非int,既节省存储又防止负数录入(MyBatis TypeHandler可自动转换);gender用char(1)而非enum,因MySQL 5.7的ENUM类型在JDBC驱动中易触发DataTruncation警告;create_time和update_time用CURRENT_TIMESTAMP而非NOW(),确保时区一致性(避免服务器时区与JVM时区不一致导致时间错乱);- 外键约束
fk_employee_dept必须显式命名,否则MyBatis逆向工程生成的Mapper XML中<resultMap>的<association>属性会因约束名随机而失效。
3.2 MyBatis配置:手写Mapper XML而非全注解
<!-- EmployeeMapper.xml --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.EmployeeMapper"> <!-- 结果映射:解决字段名与属性名不一致 --> <resultMap id="BaseResultMap" type="com.example.entity.Employee"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="age" property="age"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <result column="dept_id" property="deptId"/> <result column="salary" property="salary"/> <result column="hire_date" property="hireDate"/> <result column="create_time" property="createTime"/> <result column="update_time" property="updateTime"/> </resultMap> <!-- 查询所有员工(带部门名称关联) --> <select id="selectAllWithDept" resultMap="BaseResultMap"> SELECT e.id, e.name, e.age, e.gender, e.email, e.dept_id, e.salary, e.hire_date, e.create_time, e.update_time, d.name AS dept_name FROM employee e LEFT JOIN department d ON e.dept_id = d.id ORDER BY e.id DESC </select> <!-- 动态条件查询:支持按姓名/部门ID模糊搜索 --> <select id="selectByCondition" resultMap="BaseResultMap"> SELECT * FROM employee <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null and deptId != 0"> AND dept_id = #{deptId} </if> </where> ORDER BY id DESC </select> </mapper>参数说明与技巧:
<resultMap>中column必须与SQL查询字段名完全一致(区分大小写),property为Java实体属性名,MyBatis通过反射赋值;CONCAT('%', #{name}, '%')比'%${name}%'安全,避免SQL注入(#{}预编译,${}字符串拼接);<where>标签自动处理AND/OR开头的SQL,避免手写WHERE 1=1这种反模式;dept_name作为额外字段未在<resultMap>中定义,但可通过Employee类的deptName属性接收(需添加getter/setter),MyBatis会自动映射。
3.3 Service层事务控制:@Transactional的3个必调参数
// EmployeeServiceImpl.java @Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeMapper employeeMapper; // 关键:事务传播行为、隔离级别、回滚异常 @Transactional( propagation = Propagation.REQUIRED, // 必须有事务,无则新建(默认) isolation = Isolation.READ_COMMITTED, // 读已提交(MySQL默认) rollbackFor = {RuntimeException.class, Exception.class} // 显式指定回滚异常 ) @Override public int addEmployee(Employee employee) { // 1. 先插入员工 employeeMapper.insert(employee); // 2. 模拟部门统计更新(可能抛异常) updateDeptEmployeeCount(employee.getDeptId()); return employee.getId(); } private void updateDeptEmployeeCount(Long deptId) { // 此处若抛出Exception,整个addEmployee将回滚 if (deptId == null || deptId <= 0) { throw new IllegalArgumentException("部门ID非法"); } // 更新部门员工数逻辑... } }为什么这三个参数必须显式设置?
propagation:若不设REQUIRED,当Service方法被另一个@Transactional方法调用时,可能因传播行为不同(如SUPPORTS)导致事务失效;isolation:READ_COMMITTED是MySQL InnoDB默认级别,比DEFAULT更明确,避免HikariCP连接池复用时因隔离级别继承导致幻读;rollbackFor:Spring默认只对RuntimeException回滚,若业务逻辑抛Exception(如IOException),事务不会回滚,数据不一致——这是血泪经验踩过的坑。
4. Spring MVC与前端交互:从Controller到JSP的端到端调试
4.1 Controller设计:RESTful风格与参数校验的平衡
// EmployeeController.java @Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; /** * 查询员工列表(GET /employee/list) * 支持分页参数:pageNum=1, pageSize=10 */ @RequestMapping(value = "/list", method = RequestMethod.GET) public String listEmployees( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String name, @RequestParam(required = false) Long deptId, Model model) { // 构造查询条件 EmployeeQuery query = new EmployeeQuery(); query.setName(name); query.setDeptId(deptId); // 调用Service获取分页数据(此处简化,实际用PageHelper) List<Employee> employees = employeeService.selectByCondition(query); // 将数据放入Model供JSP使用 model.addAttribute("employees", employees); model.addAttribute("name", name); model.addAttribute("deptId", deptId); return "employee/list"; // 对应 /WEB-INF/jsp/employee/list.jsp } /** * 新增员工(POST /employee/add) * 使用@Validated进行JSR-303校验 */ @RequestMapping(value = "/add", method = RequestMethod.POST) public String addEmployee( @Validated @ModelAttribute("employee") Employee employee, BindingResult result, RedirectAttributes redirectAttributes) { // 校验失败则返回表单页并显示错误 if (result.hasErrors()) { redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.employee", result); redirectAttributes.addFlashAttribute("employee", employee); return "redirect:/employee/toAdd"; } try { employeeService.addEmployee(employee); redirectAttributes.addFlashAttribute("message", "新增成功!"); } catch (Exception e) { redirectAttributes.addFlashAttribute("message", "新增失败:" + e.getMessage()); } return "redirect:/employee/list"; } }关键设计点说明:
@RequestParam的defaultValue避免空指针,required=false让参数真正可选;@Validated结合BindingResult实现服务端校验,比前端JavaScript校验更可靠(防绕过);RedirectAttributes的addFlashAttribute将消息存入session一次,重定向后自动清除,解决POST-Redirect-GET模式下的重复提交问题;- 返回
"redirect:/employee/list"而非"employee/list",强制浏览器发起新GET请求,避免刷新时重复提交表单。
4.2 JSP页面:用JSTL+EL实现零JavaScript的CRUD
<%-- list.jsp --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <html> <head> <title>员工列表</title> <link href="${pageContext.request.contextPath}/static/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h2>员工管理</h2> <!-- 搜索表单 --> <form class="row mb-3" action="${pageContext.request.contextPath}/employee/list" method="get"> <div class="col-md-4"> <input type="text" name="name" class="form-control" placeholder="请输入姓名" value="${param.name}"> </div> <div class="col-md-3"> <select name="deptId" class="form-control"> <option value="">全部部门</option> <c:forEach items="${departments}" var="dept"> <option value="${dept.id}" ${param.deptId == dept.id ? 'selected' : ''}> ${dept.name} </option> </c:forEach> </select> </div> <div class="col-md-2"> <button type="submit" class="btn btn-primary">搜索</button> </div> </form> <!-- 员工表格 --> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>邮箱</th> <th>部门</th> <th>月薪</th> <th>入职日期</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${employees}" var="emp"> <tr> <td>${emp.id}</td> <td>${emp.name}</td> <td>${emp.age}</td> <td><c:if test="${emp.gender == 'M'}">男</c:if><c:if test="${emp.gender == 'F'}">女</c:if></td> <td>${emp.email}</td> <td>${emp.deptName}</td> <td><fmt:formatNumber value="${emp.salary}" pattern="#,##0.00"/>元</td> <td><fmt:formatDate value="${emp.hireDate}" pattern="yyyy-MM-dd"/></td> <td> <a href="${pageContext.request.contextPath}/employee/toEdit?id=${emp.id}" class="btn btn-sm btn-info">编辑</a> <a href="${pageContext.request.contextPath}/employee/delete?id=${emp.id}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </tbody> </table> <!-- 分页导航(简化版) --> <nav> <ul class="pagination"> <li class="page-item"><a class="page-link" href="#">上一页</a></li> <li class="page-item active"><a class="page-link" href="#">1</a></li> <li class="page-item"><a class="page-link" href="#">2</a></li> <li class="page-item"><a class="page-link" href="#">下一页</a></li> </ul> </nav> </div> </body> </html>JSP最佳实践:
${pageContext.request.contextPath}确保静态资源路径正确,避免硬编码/ssm-employee;<c:if>替代Java Scriptlet,逻辑清晰且IDE可语法检查;<fmt:formatNumber>和<fmt:formatDate>统一格式化,避免String.valueOf()或SimpleDateFormat线程安全问题;onclick="return confirm()"是前端轻量级确认,比AJAX调用更简单可靠(符合“零JavaScript”目标)。
5. 避坑指南:SSM开发中5个高频翻车现场与后悔药
5.1 现象:启动Tomcat报错java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
原因:Maven依赖未正确部署到WEB-INF/lib目录,常见于IDEA中未勾选war exploded的Artifacts配置,或Eclipse中未启用Deployment Assembly。
解决:在IDEA中,File → Project Structure → Artifacts,确认Available Elements中的Maven: xxx:jar已拖入Output Root;在Eclipse中,右键项目→Properties → Deployment Assembly,检查Maven Dependencies是否已添加。
5.2 现象:访问/employee/list返回404,但/首页正常
原因:DispatcherServlet的<url-pattern>/</url-pattern>拦截了所有请求,但未配置<mvc:default-servlet-handler/>,导致静态资源(CSS/JS)和JSP页面无法被Tomcat默认Servlet处理。
解决:在spring-mvc.xml中添加:
<mvc:default-servlet-handler/> <mvc:annotation-driven/>注意顺序:default-servlet-handler必须在annotation-driven之前,否则@RequestMapping注解会被忽略。
5.3 现象:新增员工时,数据库hire_date字段存入0000-00-00或报错Incorrect date value
原因:MySQL 5.7默认开启STRICT_TRANS_TABLES模式,而JDBC URL未配置zeroDateTimeBehavior=convertToNull,且JavaDate对象为null时MyBatis未做空值处理。
解决:修改JDBC URL为:
jdbc:mysql://localhost:3306/employee_db?useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull并在Employee实体类的hireDate字段添加@DateTimeFormat(pattern="yyyy-MM-dd")注解。
5.4 现象:@Transactional方法内调用另一个@Transactional方法,事务不生效
原因:Spring AOP代理机制限制——同一类内方法调用属于this.调用,绕过代理对象,事务切面无法织入。
解决:将被调用方法抽离到另一个Service类,或通过ApplicationContext手动获取代理对象:
@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private ApplicationContext context; public void doSomething() { // 手动获取代理对象调用 EmployeeService proxy = context.getBean(EmployeeService.class); proxy.anotherTransactionalMethod(); } }5.5 现象:JSP页面中${employees}为空,但Controller中model.addAttribute("employees", employees)已执行
原因:Model对象在DispatcherServlet处理完请求后即销毁,若Controller返回"forward:/employee/list.jsp"(而非"employee/list"),则JSP在RequestDispatcher.forward()作用域中无法访问Model数据。
解决:确保Controller返回逻辑视图名(如"employee/list"),由InternalResourceViewResolver解析为物理路径;若必须用forward,改用request.setAttribute("employees", employees)并返回"forward:/WEB-INF/jsp/employee/list.jsp"。
6. 进阶技巧:用Logback+Druid监控SQL执行与慢查询定位
6.1 Logback配置:让MyBatis SQL日志可读、可追溯
<!-- logback-spring.xml --> <configuration> <!-- 控制台输出 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- MyBatis SQL日志专用appender --> <appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/sql.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/sql.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 关键:开启MyBatis日志 --> <logger name="com.example.mapper" level="DEBUG" additivity="false"> <appender-ref ref="SQL_FILE"/> <appender-ref ref="CONSOLE"/> </logger> <!-- Druid连接池日志 --> <logger name="com.alibaba.druid" level="INFO" additivity="false"> <appender-ref ref="CONSOLE"/> </logger> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>效果与价值:
com.example.mapper包下所有Mapper接口的SQL执行日志(含参数值)将输出到logs/sql.log,例如:2023-10-05 14:22:33.123 [http-nio-8080-exec-1] DEBUG com.example.mapper.EmployeeMapper - ==> Preparing: SELECT * FROM employee WHERE name LIKE ? 2023-10-05 14:22:33.124 [http-nio-8080-exec-1] DEBUG com.example.mapper.EmployeeMapper - ==> Parameters: 张三(String)- 日志按天滚动+按大小切割,避免单文件过大影响排查;
additivity="false"防止日志重复输出到root logger。
6.2 Druid监控:暴露SQL执行详情与慢查询告警
<!-- spring-mybatis.xml 中配置Druid数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <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="minIdle" value="5"/> <property name="maxActive" value="20"/> <!-- 慢SQL定义:执行超1秒即记录 --> <property name="slowSqlMillis" value="1000"/> <property name="logSlowSql" value="true"/> <!-- 开启Druid监控 --> <property name="filters" value="stat,wall,log4j"/> </bean>配套Web监控页面配置(spring-mvc.xml):
<!-- 暴露Druid监控页面 --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/druid/**">druidServlet</prop> </props> </property> </bean> <bean id="druidServlet" class="org.springframework.web.servlet.mvc.ServletWrappingController"> <property name="servletClass" value="com.alibaba.druid.support.http.StatViewServlet"/> <property name="servletName" value="druidStat"/> <property name="initParameters"> <props> <prop key="loginUsername">admin</prop> <prop key="loginPassword">admin123</prop> <prop key="allow">127.0.0.1</prop> </props> </property> </bean>监控页面价值:
- 访问
http://localhost:8080/ssm-employee/druid,输入账号密码后进入Druid控制台; SQL监控页可查看每条SQL的执行次数、平均耗时、最大耗时、错误数,点击SQL可查看执行堆栈;URI监控页显示每个Controller接口的QPS、平均响应时间,快速定位性能瓶颈接口;WallFilter页展示SQL防火墙拦截记录,防止恶意SQL注入尝试。
6.3 我的血泪习惯:每次提交前必查的3个日志文件
在模拟项目X的迭代中,我养成了一个雷打不动的习惯:在Git Commit前,用tail -n 50 logs/*.log快速扫三份日志:
logs/sql.log:确认新增功能的SQL是否按预期执行(如INSERT语句是否带dept_id字段);logs/tomcat/catalina.out:检查是否有WARN级别的Spring Bean循环依赖警告(如EmployeeService与DepartmentService互相注入);logs/druid/sql.log:核对慢SQL列表,若发现SELECT * FROM employee未加索引条件,立即补WHERE dept_id = ?。
这个习惯让我在团队Code Review中,90%的性能问题能在自测阶段发现。它不炫技,但足够实在——就像SSM本身,没有花哨的概念,只有扎实的落地。希望帮到你。
本文还有配套的精品资源,点击获取