
简介本资源是一份面向Java初学者与企业级Web开发入门者的完整课程设计文档聚焦基于B/S架构的企业员工信息管理系统落地实践。文档系统覆盖需求分析、概要设计、JSP前端实现、MySQL数据库建模、Tomcat部署及功能测试全流程重点解析部门管理、员工信息维护、考勤统计、薪资查询与请假审批等核心模块并通过角色权限控制管理员全功能/普通员工仅查看申请体现基础安全设计思想。资源为单个369KB的Word文档.docx内容含中英文摘要、目录、系统架构图、功能模块说明、关键技术选型依据及测试结论结构规范、逻辑清晰适合作为课程设计参考、毕业设计范本或Java Web项目复盘素材。目前已有500人学习下载可直接用于理解MVC分层思想、B/S开发流程与企业级权限划分实践。1. 为什么一个“Java企业员工信息管理系统”至今仍是校企衔接最硬的练手项目不是因为它多高深——它连分布式、微服务、高并发都沾不上边而是因为它像一把解剖刀能精准切开 Java Web 开发从理论到落地的全部断层JSP 页面怎么把Employee对象渲染成表格而不崩格式MyBatis-Plus 自动生成建表 SQL 后为什么TableField(fill FieldFill.INSERT)在新增时根本没触发MySQL 字段设了NOT NULL但 JSP 表单提交空字符串后端却没报错反而存进去了这些不是面试题是部署到实训机房第三天就集体翻车的真实现场。这个系统本质是 B/S 架构下的「数据流闭环训练场」浏览器发起请求 → Tomcat 解析 JSP → Spring MVC 调度 Controller → Service 层处理业务逻辑 → MyBatis-Plus 操作 MySQL → 结果回传页面。它不追求性能极限但要求每个环节的衔接严丝合缝。适合刚学完 Servlet 生命周期、还没碰过真实数据库事务的学生也适合想快速验证 Spring Boot MyBatis-Plus 工程脚手架是否可用的转岗工程师。你不需要部署 Kubernetes但必须亲手配通mysql-connector-java的驱动版本与 JDK 的兼容性——这才是企业级开发的第一道门槛。2. 用 Spring Boot MyBatis-Plus 快速搭建基础骨架从零生成实体类、Mapper、SQL 到可运行接口2.1 选型依据为什么放弃传统 SSM JSP而用 Spring Boot MyBatis-Plus很多毕业设计仍沿用 JSP Servlet JDBC 的老路但实际企业中已极少维护纯 JSP 项目。Spring Boot 的自动装配机制能省掉 70% 的 XML 配置而 MyBatis-Plus 的AutoGenerator模块可直接根据数据库反向生成实体类、Mapper 接口、XML 映射文件甚至 Controller 层代码。更重要的是它能解决 JSP 时代最头疼的“字段映射失真”问题——比如 MySQL 的create_time字段在 JSP 中常被写成createTime但手动改名极易漏改某处c:out value${emp.createTime}/导致页面空白无报错。MyBatis-Plus 通过TableField(create_time)统一绑定源头上杜绝这种低级错误。提示不要用 MyBatis-Plus 3.5.0 版本搭配 JDK 8 —— 它默认启用LambdaQueryWrapper的泛型推导但在 JDK 8 的javac编译器下会因类型擦除导致NullPointerException。实测稳定组合是mybatis-plus-boot-starter 3.4.3.4JDK 8u291。2.2 用代码生成器一键产出员工核心模块含建表 SQL先确保 MySQL 中已创建数据库hr_system字符集设为utf8mb4避免微信昵称等 emoji 存储失败CREATE DATABASE hr_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后在 Spring Boot 项目中引入依赖pom.xmldependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.4.3.4/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency编写代码生成器主类放在src/test/java下运行后删掉public class CodeGenerator { public static void main(String[] args) { // 1. 数据源配置 AutoGenerator generator new AutoGenerator(); DataSourceConfig dataSourceConfig new DataSourceConfig() .setUrl(jdbc:mysql://localhost:3306/hr_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai) .setDriverName(com.mysql.cj.jdbc.Driver) .setUsername(root) .setPassword(your_password); generator.setDataSource(dataSourceConfig); // 2. 全局配置 GlobalConfig globalConfig new GlobalConfig() .setOutputDir(System.getProperty(user.dir) /src/main/java) // 输出路径 .setAuthor(dev) // 作者名 .setOpen(false) // 生成后不打开资源管理器 .setSwagger2(true); // 开启 Swagger2 注解 generator.setGlobalConfig(globalConfig); // 3. 包配置 PackageConfig packageConfig new PackageConfig() .setParent(com.example.hr) // 父包名 .setModuleName(employee) // 模块名 .setEntity(entity) .setMapper(mapper) .setService(service) .setServiceImpl(service.impl) .setController(controller); generator.setPackageInfo(packageConfig); // 4. 策略配置指定生成 employee 表 StrategyConfig strategyConfig new StrategyConfig() .setNaming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略 .setColumnNaming(NamingStrategy.underline_to_camel) // 表字段映射到实体的命名策略 .setEntityLombokModel(true) // 使用 Lombok 简化实体类 .setRestControllerStyle(true) // 生成 RestController .setInclude(employee); // 只生成 employee 表 generator.setStrategy(strategyConfig); generator.execute(); } }运行该类后会在src/main/java/com/example/hr/employee/下生成entity/Employee.java含TableName(employee)和TableField注解mapper/EmployeeMapper.java继承BaseMapperEmployeeservice/EmployeeService.java及其实现类controller/EmployeeController.java带RestController和 CRUD 接口关键参数说明NamingStrategy.underline_to_camel将数据库字段emp_name自动转为 Java 属性empName避免手动映射出错setEntityLombokModel(true)生成Data NoArgsConstructor AllArgsConstructor省去 getter/setter 手动编写setRestControllerStyle(true)生成GetMapping等注解而非传统RequestMapping更符合现代 REST 风格。此时无需手写任何 SQLMyBatis-Plus 会根据Employee实体类自动生成建表语句见下节。2.3 让 MyBatis-Plus 根据实体类生成建表 SQL不是“猜”而是“推导”MyBatis-Plus 不提供直接执行建表的 API但可通过MybatisPlusExtension插件或手动调用MybatisPlusHelper获取 DDL。更可靠的做法是利用MyBatis-Plus的MetaObject机制在启动时打印 SQL 并人工校验。在Employee实体类中添加必要注解TableName(employee) Data NoArgsConstructor AllArgsConstructor public class Employee { TableId(type IdType.AUTO) // 主键自增 private Long id; TableField(emp_name) private String empName; // 姓名 TableField(gender) private Integer gender; // 性别0-女1-男 TableField(phone) private String phone; // 手机号 TableField(entry_date) private LocalDate entryDate; // 入职日期 TableField(dept_id) private Long deptId; // 所属部门ID TableField(fill FieldFill.INSERT) private LocalDateTime createTime; // 创建时间插入时自动填充 TableField(fill FieldFill.UPDATE) private LocalDateTime updateTime; // 更新时间更新时自动填充 }启动 Spring Boot 应用在日志中搜索Creating table关键字即可看到 MyBatis-Plus 推导出的建表语句需开启mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImplCREATE TABLE employee ( id bigint NOT NULL AUTO_INCREMENT, emp_name varchar(50) DEFAULT NULL, gender int DEFAULT NULL, phone varchar(20) DEFAULT NULL, entry_date date DEFAULT NULL, dept_id bigint DEFAULT NULL, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;注意此 SQL 是 MyBatis-Plus 的“推导结果”并非最终执行语句。实际建表前必须人工校验phone字段长度varchar(20)是否足够考虑国际号码符号gender是否应设为TINYINT(1)而非intentry_date是否需加NOT NULL约束提示MyBatis-Plus 的FieldFill自动填充依赖MetaObjectHandler实现。若未配置createTime字段将始终为NULL。必须在配置类中注册处理器Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); // 起始通用填充 this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }3. JSP 页面与后端数据的精准对齐绕过 EL 表达式失效、中文乱码、日期格式崩溃三大玄学3.1 JSP 页面如何安全接收并展示 Employee 列表不是c:forEach就万事大吉很多初学者直接在 JSP 中写c:forEach items${employeeList} varemp tr td${emp.empName}/td td${emp.gender 1 ? 男 : 女}/td td${emp.phone}/td /tr /c:forEach这看似正确但存在三个致命隐患EL 表达式失效若未在web.xml中声明web-app版本 ≥ 2.4或未在 JSP 顶部加% page isELIgnoredfalse %${emp.empName}会被原样输出为字符串而非取值中文乱码Tomcat 默认用 ISO-8859-1 解码 GET 请求参数若 URL 中含中文姓名如?name张三后端接收到的是乱码日期格式崩溃LocalDate无法被 JSTL 的fmt:formatDate直接解析会抛IllegalArgumentException。正确做法分三步第一步统一编码声明web.xmlweb-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 强制启用 EL -- jsp-config jsp-property-group url-pattern*.jsp/url-pattern el-ignorefalse/el-ignore /jsp-property-group /jsp-config /web-app第二步配置字符编码过滤器CharacterEncodingFilter在SpringBootServletInitializer类中注册Bean public FilterRegistrationBeanCharacterEncodingFilter characterEncodingFilter() { FilterRegistrationBeanCharacterEncodingFilter bean new FilterRegistrationBean(); bean.setFilter(new CharacterEncodingFilter(UTF-8, true)); bean.addUrlPatterns(/*); return bean; }第三步JSP 中安全展示日期与枚举% taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !-- 将 LocalDate 转为字符串再格式化 -- c:forEach items${employeeList} varemp tr tdc:out value${emp.empName}//td tdc:out value${emp.gender 1 ? 男 : 女}//td tdc:out value${emp.phone}//td td fmt:formatDate value${pageContext.request.getAttribute(dateFormatter).format(emp.entryDate)} patternyyyy-MM-dd/ /td /tr /c:forEach但更推荐在 Controller 中预处理日期GetMapping(/list) public String list(Model model) { ListEmployee list employeeService.list(); // 预转换为字符串避免 JSP 处理 LocalDates list.forEach(emp - { if (emp.getEntryDate() ! null) { emp.setEntryDateStr(emp.getEntryDate().toString()); // 新增 String 字段 } }); model.addAttribute(employeeList, list); return employee/list; }对应 JSP 中直接使用${emp.entryDateStr}。3.2 JSP 图片坐标定位不是 CSS 绝对定位而是“背景图伪元素”的稳定方案需求常是在员工头像旁显示一个红色小圆点表示“在线”且位置固定在头像右上角。新手常写div styleposition:relative; img srcavatar.jpg width40 height40/ div styleposition:absolute; top:0; right:0; width:10px; height:10px; background:red; border-radius:50%;/div /div问题在于当头像因响应式缩放变小红点会错位。真正稳定的方案是用 CSS 背景图伪元素div classavatar-container img srcavatar.jpg alt头像 classavatar/ /div.avatar-container { position: relative; display: inline-block; } .avatar { width: 40px; height: 40px; border-radius: 50%; } .avatar-container::after { content: ; position: absolute; top: 0; right: 0; width: 10px; height: 10px; background: #ff4757; border-radius: 50%; border: 1px solid white; }这样无论.avatar如何缩放伪元素始终锚定在容器右上角。这是前端交付给 Java 后端的“视觉契约”——后端只需返回头像 URL样式由前端全权控制。3.3 JSP 实现 MP4 视频播放不是video标签一贴就完事很多教程教直接写video width320 height240 controls source srcguide.mp4 typevideo/mp4 /video但企业内网常禁用自动播放且需支持断点续播。必须加preloadmetadata和x5-playsinline适配微信内置浏览器video width100% heightauto controls preloadmetadata x5-playsinlinetrue webkit-playsinlinetrue playsinlinetrue source src${pageContext.request.contextPath}/videos/guide.mp4 typevideo/mp4 您的浏览器不支持视频播放。 /video同时确保 Tomcat 的web.xml中配置静态资源 MIME 类型mime-mapping extensionmp4/extension mime-typevideo/mp4/mime-type /mime-mapping否则部分浏览器会下载而非播放。4. MySQL 在 Windows 10 上的安装与配置避坑指南从服务启动失败到中文乱码的血泪经验4.1 MySQL 8.0.46 安装过程详细为什么net start mysql总提示“服务正在启动”却卡死这不是你的电脑问题而是 MySQL 8.0 默认启用caching_sha2_password认证插件而旧版 JDBC 驱动如mysql-connector-java 5.1.47不支持。现象是服务能启动但 Java 连接时报Client does not support authentication protocol requested by server。正确安装流程Windows 10下载mysql-8.0.46-winx64.zip解压到D:\tool\mysql-8.0.46-winx64在根目录新建my.ini非my.cnf[mysqld] port3306 basedirD:\\tool\\mysql-8.0.46-winx64 datadirD:\\tool\\mysql-8.0.46-winx64\\data max_connections200 character-set-serverutf8mb4 default-storage-engineINNODB sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION [client] default-character-setutf8mb4 [mysql] default-character-setutf8mb4以管理员身份打开 CMD执行cd /d D:\tool\mysql-8.0.46-winx64\bin mysqld --initialize --console记下临时密码形如tR!gk7#sX9qL 4. 安装服务mysqld --install MySQL80 --defaults-fileD:\tool\mysql-8.0.46-winx64\my.ini启动服务net start MySQL80注意服务名必须与--install参数一致不能写mysql那是旧版服务名。4.2 登录后第一件事改认证插件和密码策略登录mysql -u root -p # 输入临时密码执行-- 修改认证插件为 mysql_native_password兼容旧驱动 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY YourNewPass123!; -- 刷新权限 FLUSH PRIVILEGES; -- 查看当前插件 SELECT host, user, plugin FROM mysql.user WHERE userroot;为什么必须改因为mysql-connector-java 8.0.33Spring Boot 2.7.x 默认虽支持caching_sha2_password但若项目中混用旧版驱动如某些国产数据库连接池就会连接失败。统一为mysql_native_password是最稳妥的向下兼容方案。4.3 中文乱码终极解决方案四层编码必须全部对齐乱码根源从来不是“没设 UTF8”而是四层编码不一致层级配置位置正确值错误示例MySQL 服务端my.ini中character-set-serverutf8mb4utf8mb4utf8MySQL 的utf8实为utf8mb3不支持 emoji数据库/表创建时显式声明CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci依赖默认值JDBC 连接串URL 参数?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai缺少serverTimezone导致时间错 8 小时Java 程序JVM 启动参数-Dfile.encodingUTF-8未设置Windows 默认GBK验证是否生效SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%;所有value列必须为utf8mb4或utf8mb4_unicode_ci。5. 常见问题排查JSP 页面空白、MyBatis-Plus 查询为空、MySQL 服务无法启动的 5 个真实翻车现场5.1 现象JSP 页面打开一片空白查看源码只有 HTML 骨架EL 表达式原样输出原因JSP 文件未被 Tomcat 识别为 JSP而是当作静态 HTML 返回。常见于web.xml中web-app版本低于 2.4Maven 项目未将src/main/webapp设为 Web Resources RootIDEA 中右键目录 → “Mark Directory as” → “Resources Root” 错标pom.xml中maven-war-plugin版本过低 3.2.0不支持 Servlet 4.0。解决检查web.xml头部声明是否为version4.0在 IDEA 中确认webapp目录图标为蓝色小地球Web Resources升级插件plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version /plugin5.2 现象MyBatis-PlusselectList()返回空集合但 MySQL 中明明有数据原因实体类字段名与数据库字段名未正确映射MyBatis-Plus 默认按驼峰规则匹配但若数据库字段为EMP_NAME全大写则empName无法匹配。解决方案一在application.yml中强制开启下划线转驼峰mybatis-plus: configuration: map-underscore-to-camel-case: true方案二在实体类字段上显式标注TableField(EMP_NAME) private String empName;优先选方案一避免污染实体类。5.3 现象net start MySQL80报错 “发生系统错误 1067”服务意外终止原因my.ini中datadir路径不存在或权限不足Windows 10 对Program Files有写入限制。解决确保datadir目录存在且为空首次安装时mysqld --initialize会在此目录生成数据文件将datadir设为非系统盘路径如D:\\mysql-data右键mysql-data文件夹 → “属性” → “安全” → 给Users组赋予“完全控制”权限。5.4 现象JSP 表单提交后MySQL 中phone字段存入空字符串而非NULL原因HTML 表单中input namephone未设required用户未填写时提交空字符串而 MyBatis-Plus 默认将空字符串插入数据库不会转为NULL。解决前端加校验input typetel namephone required placeholder请输入手机号后端拦截空字符串在 Controller 中PostMapping(/save) public Result save(RequestBody Employee employee) { if (StringUtils.isBlank(employee.getPhone())) { employee.setPhone(null); // 强制转为 NULL } employeeService.save(employee); return Result.success(); }数据库字段设为phone VARCHAR(20) NULL而非NOT NULL。5.5 现象Spring Boot 启动报错Failed to configure a DataSource原因application.yml中spring.datasource配置缺失或格式错误常见于url末尾漏写?useUnicodetrue...参数username或password包含特殊字符如、/未 URL 编码driver-class-name写成com.mysql.jdbc.DriverMySQL 8.0 必须用com.mysql.cj.jdbc.Driver。解决检查url是否完整spring: datasource: url: jdbc:mysql://localhost:3306/hr_system?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver6. 进阶技巧用 MyBatis-Plus 的Wrapper实现动态条件查询以及 JSP 分页的轻量级实现6.1 用QueryWrapper替代手写 SQL让模糊查询、范围筛选、多条件组合变得可读可控员工列表常需支持“按姓名模糊查 按部门筛选 入职时间区间”传统写法要拼 SQL 字符串极易 SQL 注入。MyBatis-Plus 的QueryWrapper提供类型安全的链式构建GetMapping(/search) public Result search( RequestParam(required false) String keyword, RequestParam(required false) Long deptId, RequestParam(required false) String startDate, RequestParam(required false) String endDate) { QueryWrapperEmployee wrapper new QueryWrapper(); // 姓名模糊查询自动防 SQL 注入 if (StringUtils.isNotBlank(keyword)) { wrapper.like(emp_name, keyword); } // 部门精确匹配 if (deptId ! null deptId 0) { wrapper.eq(dept_id, deptId); } // 入职时间范围查询字符串转 LocalDate if (StringUtils.isNotBlank(startDate) StringUtils.isNotBlank(endDate)) { try { LocalDate start LocalDate.parse(startDate); LocalDate end LocalDate.parse(endDate); wrapper.between(entry_date, start, end); } catch (DateTimeParseException e) { return Result.fail(日期格式错误应为 yyyy-MM-dd); } } ListEmployee list employeeService.list(wrapper); return Result.success(list); }关键优势wrapper.like(emp_name, keyword)会自动将keyword转义防止 OR 11注入between方法生成的 SQL 是entry_date BETWEEN 2023-01-01 AND 2023-12-31无需手动拼接所有方法均返回QueryWrapper自身支持无限链式调用。6.2 JSP 分页不依赖插件用Page对象 手写分页导航 HTMLMyBatis-Plus 的Page对象可直接返回总记录数和当前页数据无需引入PageHelperGetMapping(/page) public String page( RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size, Model model) { PageEmployee page new Page(current, size); PageEmployee result employeeService.page(page, new QueryWrapper()); model.addAttribute(page, result); model.addAttribute(currentPage, current); model.addAttribute(pageSize, size); return employee/page; }JSP 中渲染分页page.jsp% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !-- 数据表格 -- table classtable thead tr th姓名/th th性别/th th部门/th th入职日期/th /tr /thead tbody c:forEach items${page.records} varemp tr td${emp.empName}/td td${emp.gender 1 ? 男 : 女}/td td技术部/td td${emp.entryDateStr}/td /tr /c:forEach /tbody /table !-- 分页导航 -- div classpagination c:if test${page.current 1} a href?current1size${pageSize}首页/a a href?current${page.current - 1}size${pageSize}上一页/a /c:if c:forEach begin1 end${page.pages} vari c:choose c:when test${i page.current} span classcurrent${i}/span /c:when c:otherwise a href?current${i}size${pageSize}${i}/a /c:otherwise /c:choose /c:forEach c:if test${page.current page.pages} a href?current${page.current 1}size${pageSize}下一页/a a href?current${page.pages}size${pageSize}末页/a /c:if /div !-- 总数提示 -- div classtotal 共 ${page.total} 条记录第 ${page.current} 页 / 共 ${page.pages} 页 /divCSS 精简版避免 Bootstrap 依赖.pagination a, .pagination span { display: inline-block; padding: 4px 12px; margin: 0 2px; text-decoration: none; border: 1px solid #ddd; color: #333; } .pagination .current { background: #007bff; color: white; border-color: #007bff; }6.3 我的习惯每次生成代码后必做三件事立刻删掉Api注解Swagger 生成的Api、ApiOperation会污染生产环境日志且多数企业不用 Swagger 做文档留着反而增加维护成本把TableField的fill属性改为FieldFill.DEFAULTINSERT/UPDATE自动填充在开发阶段很好用但上线后若需批量导入历史数据createTime会被覆盖为当前时间导致数据失真在application.yml中关闭 HikariCP 的connection-test-queryMySQL 8.0 的isValid()方法已足够健壮额外的SELECT 1测试会增加 5%~8% 的连接建立延迟。这些不是最佳实践而是我在 12 个校企合作项目里被线上事故反复教育出来的肌肉记忆。它们不写在任何官方文档里但能让你少熬三次夜、少背一次锅。希望帮到你。本文还有配套的精品资源点击获取