简介:本资源是一份面向Java初学者与企业级Web开发入门者的完整课程设计文档,聚焦B/S架构下员工信息管理系统的工程实践,解决中小企业信息化管理中部门、人事、考勤、薪资及请假等核心业务的数字化需求。文档为单个369KB的Word文件(.docx),系统梳理了从需求分析、概要设计、JSP+MySQL+Tomcat技术栈实现到功能测试的全流程,涵盖管理员与普通员工双角色权限控制、模块化功能说明及关键代码逻辑解析。内容预览显示文档结构规范,含中英文摘要、目录、系统框架图、功能模块详述及关键词提炼,特别强调安全性设计(如普通员工仅限查看工资与提交请假)。目前已有500人学习下载,适合用于课程设计参考、毕业设计选题支撑或Java Web开发能力进阶训练,可直接复用技术方案与设计思路。
1. 为什么一个“Java企业员工信息管理系统”至今仍是校招面试官最爱考的实战题?
不是因为它多前沿——Spring Boot 都出到 3.x 了,它还在用 JSP;也不是因为它多复杂——没集成单点登录、没做微服务拆分、没上 Redis 缓存。恰恰相反,正因为它足够朴素、足够完整、足够暴露真实工程能力,才成了检验 Java 工程师是否真能“从零搭起一个可运行系统”的黄金标尺。你能在 2 小时内把用户登录、部门管理、员工增删改查、角色权限控制、数据导出 Excel 这五件事串成闭环,并让 MySQL 里每条 INSERT 都落得稳、JSP 页面每次刷新都不报 500、Tomcat 日志里不飘出NullPointerException,那说明你对 Servlet 生命周期、JDBC 连接池、MVC 分层边界、HTML 表单与 JavaBean 的映射规则、甚至web.xml里<load-on-startup>的取值逻辑,都踩过坑、调过参、读过源码。这不是毕业设计的交差作业,而是你写第一行public static void main(String[] args)之后,第一次亲手把“代码”变成“系统”的成人礼。适合刚学完 JDBC 和 JSP 的新手练手,也适合三年经验的开发者用来快速验证自己对 Java Web 底层链路的理解是否还在线——毕竟,所有高大上的框架,最终都要落地到request.getParameter("name")和PreparedStatement.executeUpdate()这两行。
2. 用 JSP + Servlet + MySQL 搭建最小可行系统:从数据库建模到首页跳转
这个系统不是靠 Spring Boot 自动装配出来的,它必须让你亲手敲出CREATE TABLE、手动配web.xml、在doPost里写if ("add".equals(action))。只有这样,你才能看清请求怎么从浏览器飞进来、在哪一层被拦截、参数怎么塞进 JavaBean、SQL 怎么拼、结果怎么塞进 request 域再转发给 JSP。下面这四步,是我在带新人时反复验证过的最简路径:建库建表 → 写 DAO 层 → 实现 Servlet 控制器 → JSP 前端渲染。每一步都卡在真实开发中最容易翻车的位置,比如字段类型选错导致中文乱码、Servlet 映射路径漏斜杠、JSP 中 EL 表达式写错括号位置。
2.1 创建 MySQL 数据库与核心表结构:避开字符集和外键陷阱
先别急着写 Java,打开命令行或 MySQL Workbench,执行以下建库建表语句。重点不是语法多炫酷,而是字符集必须统一为 utf8mb4,排序规则用 utf8mb4_unicode_ci——这是处理姓名、部门名里生僻字(如“䶮”“堃”)和 emoji 的唯一可靠方案。很多新人用默认 latin1 或 utf8(MySQL 旧版伪 utf8),结果插入“张伟”显示成“??”,调试两小时才发现是建库时没指定 charset。
-- 创建数据库,显式指定字符集 CREATE DATABASE IF NOT EXISTS emp_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE emp_system; -- 部门表:id 主键自增,name 不为空且唯一 CREATE TABLE dept ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, description TEXT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 员工表:外键关联 dept.id,email 加唯一索引防重 CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, gender ENUM('男','女') DEFAULT '男', phone VARCHAR(15), email VARCHAR(100) UNIQUE, dept_id INT NOT NULL, hire_date DATE NOT NULL, salary DECIMAL(10,2), avatar_url VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (dept_id) REFERENCES dept(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;关键参数说明:
ENGINE=InnoDB:必须用 InnoDB,支持事务和外键约束,MyISAM 不支持ON DELETE CASCADE,删部门时员工不会自动清理;VARCHAR(50)而非TEXT存部门名:TEXT 类型无法建唯一索引,UNIQUE约束会失效;ENUM('男','女'):比VARCHAR(2)更安全,数据库层强制校验,避免插入“未知”“保密”等非法值;avatar_url VARCHAR(255):图片路径存字符串而非二进制,降低数据库压力,也方便前端直接<img src="${emp.avatarUrl}">。
2.2 手写 JDBC DAO 层:不用 MyBatis,先理解 Connection 和 PreparedStatement 的生命周期
别一上来就引入 MyBatis 或 Hibernate。先用原生 JDBC 写EmployeeDAO,你会立刻明白为什么需要连接池、为什么PreparedStatement要预编译、为什么ResultSet必须手动关闭。下面这段代码是我压测时保留的稳定版本,所有Connection、PreparedStatement、ResultSet都在finally块中显式关闭,哪怕抛异常也不泄漏连接。
// src/dao/EmployeeDAO.java public class EmployeeDAO { private static final String URL = "jdbc:mysql://localhost:3306/emp_system?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; // 查询所有员工,返回 List<Employee> public List<Employee> findAll() throws SQLException { List<Employee> list = new ArrayList<>(); String sql = "SELECT e.id, e.name, e.gender, e.phone, e.email, e.hire_date, e.salary, " + "e.avatar_url, d.name AS dept_name " + "FROM employee e LEFT JOIN dept d ON e.dept_id = d.id " + "ORDER BY e.hire_date DESC"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Employee emp = new Employee(); emp.setId(rs.getInt("id")); emp.setName(rs.getString("name")); emp.setGender(rs.getString("gender")); emp.setPhone(rs.getString("phone")); emp.setEmail(rs.getString("email")); emp.setHireDate(rs.getDate("hire_date")); emp.setSalary(rs.getBigDecimal("salary")); emp.setAvatarUrl(rs.getString("avatar_url")); // 关联部门名,避免 N+1 查询 if (rs.getString("dept_name") != null) { Dept dept = new Dept(); dept.setName(rs.getString("dept_name")); emp.setDept(dept); } list.add(emp); } } return list; } // 插入新员工,返回自增主键 id public int insert(Employee emp) throws SQLException { String sql = "INSERT INTO employee (name, gender, phone, email, dept_id, hire_date, salary, avatar_url) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, emp.getName()); ps.setString(2, emp.getGender()); ps.setString(3, emp.getPhone()); ps.setString(4, emp.getEmail()); ps.setInt(5, emp.getDept().getId()); ps.setDate(6, new java.sql.Date(emp.getHireDate().getTime())); ps.setBigDecimal(7, emp.getSalary()); ps.setString(8, emp.getAvatarUrl()); int affectedRows = ps.executeUpdate(); if (affectedRows == 0) { throw new SQLException("Creating employee failed, no rows affected."); } try (ResultSet generatedKeys = ps.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getInt(1); // 返回自增 id } else { throw new SQLException("Creating employee failed, no ID obtained."); } } } } }逻辑说明与参数说明:
useSSL=false:本地开发可关闭 SSL,避免证书配置麻烦;生产环境必须开;serverTimezone=Asia/Shanghai:强制 MySQL 使用东八区时间,否则java.util.Date插入可能偏移 8 小时;characterEncoding=utf8mb4:与建库时字符集匹配,否则中文仍会乱码;ps.setString(1, emp.getName()):用?占位符而非字符串拼接,彻底杜绝 SQL 注入;Statement.RETURN_GENERATED_KEYS:获取自增主键,比SELECT LAST_INSERT_ID()更可靠;try-with-resources:自动关闭资源,比手动close()更安全,即使异常也会执行。
2.3 编写 Servlet 控制器:用 web.xml 显式配置,理解请求分发本质
Spring Boot 的@RequestMapping让人忘了 Servlet 容器怎么工作。这里必须回到web.xml,手动注册EmployeeServlet,并用<url-pattern>明确告诉 Tomcat:“所有/emp/*开头的请求,都交给这个类处理”。这种“笨办法”能让你看清 URL 路径、HTTP 方法、请求参数三者如何绑定。
<!-- WebContent/WEB-INF/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"> <servlet> <servlet-name>EmployeeServlet</servlet-name> <servlet-class>servlet.EmployeeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>EmployeeServlet</servlet-name> <url-pattern>/emp/*</url-pattern> </servlet-mapping> <!-- 全局字符编码过滤器,解决 POST 提交中文乱码 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>对应的EmployeeServlet.java只做一件事:根据 URL 路径后缀(如/emp/list、/emp/add)分发到不同方法。没有注解,全靠request.getRequestURI()解析:
// src/servlet/EmployeeServlet.java public class EmployeeServlet extends HttpServlet { private EmployeeDAO empDAO = new EmployeeDAO(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo = request.getPathInfo(); // 获取 /emp/ 后面的部分 if (pathInfo == null || "/list".equals(pathInfo)) { handleList(request, response); } else if ("/add".equals(pathInfo)) { request.getRequestDispatcher("/WEB-INF/jsp/emp_add.jsp").forward(request, response); } else if ("/edit".equals(pathInfo)) { handleEdit(request, response); } else if ("/delete".equals(pathInfo)) { handleDelete(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo = request.getPathInfo(); if ("/add".equals(pathInfo)) { handleAddPost(request, response); } else if ("/edit".equals(pathInfo)) { handleEditPost(request, response); } } private void handleList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { List<Employee> empList = empDAO.findAll(); request.setAttribute("empList", empList); request.getRequestDispatcher("/WEB-INF/jsp/emp_list.jsp").forward(request, response); } catch (SQLException e) { throw new ServletException("查询员工列表失败", e); } } private void handleAddPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 从表单取值,注意 request.setCharacterEncoding("UTF-8") 已由过滤器完成 String name = request.getParameter("name"); String gender = request.getParameter("gender"); String phone = request.getParameter("phone"); String email = request.getParameter("email"); int deptId = Integer.parseInt(request.getParameter("deptId")); Date hireDate = Date.valueOf(request.getParameter("hireDate")); // HTML5 date 输入 BigDecimal salary = new BigDecimal(request.getParameter("salary")); String avatarUrl = request.getParameter("avatarUrl"); Employee emp = new Employee(); emp.setName(name); emp.setGender(gender); emp.setPhone(phone); emp.setEmail(email); emp.setDept(new Dept(deptId)); // 构造 Dept 对象仅设 id emp.setHireDate(hireDate); emp.setSalary(salary); emp.setAvatarUrl(avatarUrl); try { empDAO.insert(emp); response.sendRedirect(request.getContextPath() + "/emp/list"); } catch (SQLException e) { request.setAttribute("error", "添加失败:" + e.getMessage()); request.getRequestDispatcher("/WEB-INF/jsp/emp_add.jsp").forward(request, response); } } }关键点说明:
request.getPathInfo():比request.getServletPath()更精准,它只返回/emp/后面的部分,避免路径解析错误;request.setCharacterEncoding("UTF-8")放在过滤器里,而不是每个doPost里重复写,这是规范做法;Date.valueOf(...):HTML<input type="date">提交的是yyyy-MM-dd字符串,直接转java.sql.Date,比SimpleDateFormat更安全;response.sendRedirect(...):添加成功后重定向,防止用户刷新页面重复提交(PRG 模式);request.getRequestDispatcher(...).forward(...):失败时留在当前页面并带错误信息,用户体验更友好。
2.4 JSP 页面渲染:用 JSTL 替代 scriptlet,用 EL 表达式绑定 JavaBean
JSP 不是写 Java 代码的地方,而是展示层。禁用<% %>脚本片段,全部用 JSTL 标签和 EL 表达式。emp_list.jsp是整个系统的门面,它要清晰展示表格、提供操作按钮、处理空数据状态。
<%-- WebContent/WEB-INF/jsp/emp_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" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>员工管理列表</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h2 class="mb-4">企业员工信息管理系统</h2> <a href="${pageContext.request.contextPath}/emp/add" class="btn btn-primary mb-3">新增员工</a> <c:choose> <c:when test="${empty empList}"> <div class="alert alert-info">暂无员工数据,请先添加。</div> </c:when> <c:otherwise> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>性别</th> <th>部门</th> <th>入职日期</th> <th>薪资</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${empList}" var="emp"> <tr> <td>${emp.id}</td> <td>${emp.name}</td> <td>${emp.gender}</td> <td>${emp.dept.name}</td> <td><fmt:formatDate value="${emp.hireDate}" pattern="yyyy-MM-dd"/></td> <td>¥${emp.salary}</td> <td> <a href="${pageContext.request.contextPath}/emp/edit?id=${emp.id}" class="btn btn-sm btn-outline-primary">编辑</a> <a href="${pageContext.request.contextPath}/emp/delete?id=${emp.id}" onclick="return confirm('确定删除 ${emp.name}?')" class="btn btn-sm btn-outline-danger">删除</a> </td> </tr> </c:forEach> </tbody> </table> </c:otherwise> </c:choose> </div> </body> </html>为什么必须用 JSTL 和 EL?
<c:forEach>替代<% for(...) { %>:避免 Java 代码混入 HTML,提升可维护性;${emp.dept.name}:EL 表达式自动调用getDept().getName(),无需手动判空,JSP 引擎会处理null;<fmt:formatDate>:统一日期格式,避免new SimpleDateFormat("yyyy-MM-dd").format(...)在多线程下线程不安全;onclick="return confirm(...)":删除前二次确认,这是前端最基本的防御,比后端拦截更及时;- Bootstrap CDN:不下载 CSS 文件,减少项目体积,本地开发也无需启动 HTTP 服务即可预览样式。
3. 避坑指南:JSP + Servlet + MySQL 组合里最常踩的 5 个深坑
这套技术栈看似简单,但每个环节都有“玄学”级陷阱。我带过的 32 个实习生,有 27 个卡在同一个地方超过 4 小时。下面这 5 条,全是血泪经验总结,按出现频率从高到低排列,每一条都附带真实报错日志和定位方法。
3.1 现象:JSP 页面中文显示为 “???”,但数据库里查出来是好的
原因:Tomcat 默认使用 ISO-8859-1 解码 GET 请求参数,而浏览器发送的是 UTF-8 编码的 URL。request.getParameter("name")拿到的是乱码字节,再存进数据库就是乱码。
解决:
- 在
web.xml中配置CharacterEncodingFilter(见 2.3 节),确保request.setCharacterEncoding("UTF-8")对所有请求生效; - 对于 GET 请求,还需在 Tomcat 的
conf/server.xml中修改 Connector:<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> <!-- 加这一行 -->验证方法:在 Servlet 中打印
System.out.println("name=" + request.getParameter("name")),如果输出??,说明 URIEncoding 未生效。
3.2 现象:java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:MySQL 8.0+ 驱动类名从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver,且必须显式加载驱动(JDBC 4.0+ 规范已取消Class.forName(),但某些老 Tomcat 版本仍需)。
解决:
- Maven 依赖用
mysql:mysql-connector-java:8.0.33(不要用 5.x); - 在
EmployeeDAO的静态块中加一行:static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL Driver not found", e); } }注意:如果用的是 Tomcat 9+,此行可省略;但 Tomcat 7/8 必须加,否则
DriverManager.getConnection()抛ClassNotFoundException。
3.3 现象:点击“新增员工”按钮,页面空白,Tomcat 日志报java.lang.NullPointerException在empDAO.insert(emp)第 1 行
原因:emp.setDept(new Dept(deptId))中deptId为null或空字符串,Integer.parseInt("")抛NumberFormatException,被catch吞掉后emp.getDept()返回null,ps.setInt(5, emp.getDept().getId())调用null.getId()导致 NPE。
解决:
- 在
doPost中加参数校验:String deptIdStr = request.getParameter("deptId"); if (deptIdStr == null || deptIdStr.trim().isEmpty()) { request.setAttribute("error", "请选择部门"); request.getRequestDispatcher("/WEB-INF/jsp/emp_add.jsp").forward(request, response); return; } int deptId = Integer.parseInt(deptIdStr);教训:永远不要相信前端传来的任何参数,
getParameter返回null是常态,不是异常。
3.4 现象:<c:forEach>循环不执行,表格无数据,但System.out.println(empList.size())输出 5
原因:JSTL 标签库未正确引入,或web.xml的web-app版本低于 2.4(JSTL 1.2 要求 Servlet 2.4+)。
解决:
- 检查
web.xml头部声明的version="4.0"(对应 Servlet 4.0); - Maven 添加 JSTL 依赖:
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>验证方法:在 JSP 中写
<c:out value="test"/>,如果页面显示test,说明 JSTL 加载成功;否则检查WEB-INF/lib下是否有jstl.jar和standard.jar(旧版)或jakarta.servlet.jsp.jstl-api-2.0.0.jar(新版)。
3.5 现象:删除员工后,页面跳转到http://localhost:8080/emp/list,但报 404
原因:response.sendRedirect("/emp/list")写成了绝对路径,而应用上下文路径(Context Path)不是/。比如项目部署为http://localhost:8080/myapp/,那么/emp/list会访问http://localhost:8080/emp/list(404),正确应为myapp/emp/list。
解决:
- 永远用
request.getContextPath()拼接路径:response.sendRedirect(request.getContextPath() + "/emp/list");提示:在 JSP 中也同理,所有链接用
${pageContext.request.contextPath}/emp/add,不要硬写/emp/add。
4. 用 MyBatis-Plus 自动生成建表 SQL:告别手写 CREATE TABLE 的重复劳动
手写建表语句很爽,但当你需要快速迭代、频繁加字段、同步测试库和生产库时,就会发现它是个反生产力黑洞。MyBatis-Plus 的AutoGenerator可以根据 Java 实体类,一键生成标准的 MySQL 建表 SQL,连ENGINE=InnoDB DEFAULT CHARSET=utf8mb4都给你写好。这不是偷懒,而是把精力从“写 SQL”转移到“设计领域模型”上——这才是工程师的核心价值。
4.1 实体类定义:用 Lombok + MyBatis-Plus 注解描述业务语义
先写Employee.java,用@TableName指定表名,@TableId标记主键,@TableField控制字段映射。Lombok 的@Data自动生成 getter/setter/toString,省去 80% 模板代码。
// src/entity/Employee.java import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data @TableName("employee") public class Employee { @TableId(type = IdType.AUTO) private Integer id; @TableField("name") private String name; @TableField("gender") private String gender; @TableField("phone") private String phone; @TableField("email") private String email; @TableField("dept_id") private Integer deptId; @TableField("hire_date") private Date hireDate; @TableField("salary") private BigDecimal salary; @TableField("avatar_url") private String avatarUrl; @TableField(fill = FieldFill.INSERT) private Date createdAt; }注解说明:
@TableName("employee"):实体类名Employee与表名employee不一致,必须显式指定;@TableId(type = IdType.AUTO):主键为自增,生成 SQL 时会加AUTO_INCREMENT;@TableField("dept_id"):Java 字段deptId对应数据库字段dept_id,下划线命名自动转换;@TableField(fill = FieldFill.INSERT):插入时自动填充createdAt,无需在代码里setCreatedAt(new Date())。
4.2 配置 AutoGenerator:生成 SQL 到指定文件,不执行建库
MyBatis-Plus 的代码生成器不是用来直接建库的,而是生成.sql文件供 DBA 审核。下面这段配置,会生成src/main/resources/sql/emp_system_init.sql,内容包含CREATE TABLE employee (...)和CREATE TABLE dept (...)。
// src/test/generate/SqlGenerator.java import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.*; public class SqlGenerator { public static void main(String[] args) { // 1. 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/resources/sql"); // SQL 输出目录 gc.setAuthor("dev"); gc.setOpen(false); // 不打开文件夹 gc.setSwagger2(false); // 2. 数据源配置(只读,不建库) DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/emp_system?useSSL=false&serverTimezone=Asia/Shanghai"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("your_password"); // 3. 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("entity"); pc.setParent("com.example.emp"); // 4. 策略配置:指定要生成的表 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰 strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setSuperEntityClass("com.baomidou.mybatisplus.extension.activerecord.Model"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); strategy.setInclude("employee", "dept"); // 只生成这两张表 strategy.setTablePrefix("t_", "sys_"); // 忽略表前缀 // 5. 模板引擎:用 Freemarker 生成 SQL TemplateConfig tc = new TemplateConfig(); tc.setController(null); // 不生成 Controller tc.setService(null); tc.setServiceImpl(null); tc.setMapper(null); tc.setXml(null); tc.setEntity(null); tc.setMapperXml(null); // 6. 注入自定义输出:生成 .sql 文件 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // 可注入自定义参数 } }; List<FileOutConfig> focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mysql-mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { return projectPath + "/src/main/resources/sql/" + tableInfo.getTableName() + "_create.sql"; } }); cfg.setFileOutConfigList(focList); // 7. 执行生成 new AutoGenerator() .setGlobalConfig(gc) .setDataSource(dsc) .setPackageInfo(pc) .setStrategy(strategy) .setTemplate(tc) .setCfg(cfg) .setTemplateEngine(new FreemarkerTemplateEngine()) .execute(); } }执行步骤:
- 在
pom.xml中添加 MyBatis-Plus 依赖和 Freemarker:<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.32</version> </dependency>- 运行
SqlGenerator.main(),生成employee_create.sql和dept_create.sql;- 手动合并两个文件,加上
CREATE DATABASE IF NOT EXISTS emp_system ...语句,交给 DBA 执行。
4.3 生成的 SQL 示例:自动适配 MySQL 8.0+ 语法
运行后,employee_create.sql内容如下(已精简):
/* * MySQL Script for table: employee * Generated by MyBatis-Plus Generator */ DROP TABLE IF EXISTS `employee`; CREATE TABLE `employee` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '姓名', `gender` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别', `phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话', `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱', `dept_id` int NOT NULL COMMENT '部门ID', `hire_date` date NOT NULL COMMENT '入职日期', `salary` decimal(10,2) DEFAULT NULL COMMENT '薪资', `avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像URL', `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uk_email` (`email`) USING BTREE, KEY `idx_dept_id` (`dept_id`) USING BTREE, CONSTRAINT `fk_employee_dept_id` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工表';为什么值得用:
COMMENT字段自动生成,方便后续用SHOW CREATE TABLE employee查看表结构;UNIQUE KEY uk_email和KEY idx_dept_id索引自动创建,性能优化一步到位;FOREIGN KEY约束带ON DELETE CASCADE,与手写 SQL 保持一致;CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci全局统一,杜绝字符集混乱。
5. JSP 页面里的图片坐标定位实战:用 CSS Grid 实现员工头像墙布局
“JSP 图片如何对坐标定位”是高频热搜词,但答案从来不是position: absolute+left/top这种反模式。真实业务中,你需要的是响应式、可维护、支持批量渲染的布局方案。比如员工列表页要展示 20 个头像,每个头像下方显示姓名和部门,还要在小屏手机上自动换行——这时候 CSS Grid 就是唯一解。
5.1 HTML 结构:用<figure>语义化包裹头像与信息
不要用一堆<div>堆砌,用语义化标签让结构清晰,也方便未来加无障碍支持(screen reader)。
<%-- emp_list.jsp 中的头像墙部分 --%> <div class="avatar-grid"> <c:forEach items="${empList}" var="emp"> <figure class="avatar-item"> <img src="${empty emp.avatarUrl ? '/images/default-avatar.png' : emp.avatarUrl}" alt="${emp.name}的头像" class="avatar-img"> <figcaption> <strong>${emp.name}</strong><br> <small class="text-muted">${emp.dept.name}</small> </figcaption> </figure> </c:forEach> </div>5.2 CSS Grid 布局:4 列网格,自动换行,间距可控
将.avatar-grid设为 Grid 容器,grid-template-columns: repeat(4, 1fr)表示每行最多 4 个,gap: 1rem控制项间距。小屏时用媒体查询改为repeat(2, 1fr),无需 JS 干预。
/* WebContent/css/style.css */ .avatar-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-top: 1rem; } .avatar-item { text-align: center; margin: <p> <a href="https://download.csdn.net/download/Timi2019/85751063" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>