员工管理系统
一个基于Springboot
和Mybatis
实现的员工管理系统,使用PageHelper
分页和事务处理。
需求
- 接口一:支持按照姓名(模糊查询)、工号、机构id、 机构名称(模糊查询)、 状态查询员工信息 (以上四个条件可以输入一个也可以输入多个)。结果支持分页展示。
- 接口二: 支持输入机构列表、 查询多个机构下的员工信息、结果支持分页展示。
- 接口三:更新指定员工的状态值、机构号、机构名称(以上三个信息可以输入一个也可以输入多个)
- 接口四:指定工号删除员工
- 接口五:新增员工信息
- 接口六:使用@Transactional注解,实现事务回滚
项目目录结构
bash
│ .gitignore
│ pom.xml
└─src
├─main
│ ├─java
│ │ └─com
│ │ └─example
│ │ └─work3
│ │ │ Work3Application.java
│ │ │
│ │ ├─config // 各种配置
│ │ │ WebExceptionAdvice.java
│ │ │
│ │ ├─controller // 前端接控制器(controller层),调用service
│ │ │ ManagerController.java
│ │ │
│ │ ├─dto //Data Transfer Object,重构类,仅保留需要的属性
│ │ │ ManagerRequestDTO.java
│ │ │ ManagerWithDepartmentDTO.java
│ │ │ PageResult.java
│ │ │ Result.java
│ │ │
│ │ ├─entity // 实体类
│ │ │ Department.java
│ │ │ Manager.java
│ │ │
│ │ ├─mapper // mapper接口(dao层),操作数据库
│ │ │ DepartmentMapper.java
│ │ │ ManagerMapper.java
│ │ │
│ │ └─service // 服务端接口(service层)
│ │ │ ManagerService.java
│ │ │
│ │ └─impl // 服务端接口的实现类,调用mapper
│ │ ManagerServiceImpl.java
│ │
│ └─resources
│ │ application.yaml // Springboot的配置项
│ │
│ └─mapper // mapper接口的映射文件
│ DepartmentMapper.xml
│ ManagerMapper.xml
│
└─test
└─java
└─com
└─example
└─work3
Work3ApplicationTests.java
项目创建
1. 创建Springboot项目
2. 添加Maven依赖
xml
<!--pom.xml文件内容 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>work3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>work3</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
</project>
3. 删除多余的文件,完善目录结构
4. 编写Springboot的配置文件application.yaml
yaml
server:
port: 8081 # 应用服务端口号
spring:
application:
name: work3 # 应用名称
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 驱动
url: jdbc:mysql://localhost:3306/ebank?useSSL=false&serverTimezone=UTC # 数据库连接 URL
username: root # 数据库用户名
password: 123456 # 数据库密码
jackson:
default-property-inclusion: non_null # JSON 序列化时忽略 null 字段
mybatis:
mapper-locations: classpath:mapper/*.xml # Mapper XML 文件路径
type-aliases-package: com.example.work3.entity # 实体类所在包
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL 到控制台
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl # 替换StdOutImpl为log4j实现
pagehelper:
helper-dialect: mysql # 数据库类型为 MySQL
reasonable: true # 页码合理化(pageNum <= 0 时自动置为 1)
support-methods-arguments: true # 支持从方法参数获取分页参数
params: count=countSql # 指定 count 查询的参数名称
logging:
level:
com.example.work3.mapper: DEBUG # 配置Mapper包路径,显示SQL语句
org.apache.ibatis: DEBUG # MyBatis相关日志
org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG # 打印事务提交/回滚日志
# 自定义控制台输出格式(可选)
pattern:
console: "%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
5. entity中创建实体类
java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Manager {
String managerNo; // 工号
String managerName; // 姓名
String departmentId; // 机构号
String departmentName; // 机构名称
String status; // 状态 0-正常 1-异常
}
java
import lombok.Data;
@Data
public class Department {
private Long id;
private String departmentId;
private String departmentName;
}
6. dto中创建服务器响应结果类
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Boolean success;
private String errorMsg;
private Object data;
public static Result ok() {
return new Result(true, null, null);
}
public static Result ok(Object data) {
return new Result(true, null, data);
}
public static Result fail(String errorMsg) {
return new Result(false, errorMsg, null);
}
}
7. config中创建WebExceptionAdvice类
java
import com.example.work3.dto.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class WebExceptionAdvice {
@ExceptionHandler(RuntimeException.class)
public Result handleRuntimeException(RuntimeException e) {
log.error(e.toString(), e);
return Result.fail("服务器异常");
}
}
8. dto中创建请求类和分页结果类
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ManagerRequestDTO {
// 公共字段
private String managerNo; // 工号
private String managerName; // 姓名
private List<String> departmentIds; // 机构ID列表
private String departmentId; // 单机构ID
private String departmentName; // 机构名称
private Integer status; // 状态 0-正常 1-异常
// 分页字段
private Integer pageNum;
private Integer pageSize;
}
java
@Data
public class ManagerWithDepartmentDTO {
private Manager manager;
private Department department;
}
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
private List<T> list; // 数据列表
private long total; // 总记录数
private int pageNum; // 当前页码
private int pageSize; // 每页大小
// 静态工厂方法,方便从 PageInfo 创建 PageResult
public static <T> PageResult<T> of(PageInfo<T> pageInfo) {
return new PageResult<>(
pageInfo.getList(),
pageInfo.getTotal(),
pageInfo.getPageNum(),
pageInfo.getPageSize()
);
}
}
9. service中定义功能接口
java
public interface ManagerService {
// a.接口一:支持按照姓名(模糊査询)、工号、机构id、机构名称(模糊查询)、状态查
// 询员工信息(以上四个条件可以输入一个也可以输入多个),结果支持分页展示
PageResult<Manager> queryManagers(ManagerRequestDTO dto);
// b.接口二:支持输入机构列表,查询多个机构下的员工信息,结果支持分页展示
PageResult<Manager> queryByDepartments(ManagerRequestDTO dto);
// c.接口三:更新指定员工的状态值、机构号、机构名称(以上三个信息可以输入一个也可
// 以输入多个)
Result updateManager(ManagerRequestDTO dto);
// d.接口四:指定工号删除员工
Result deleteManager(String managerNo);
// e.接口五:新增员工信息
Result addManager(Manager manager);
// f.接口六:同时插入部门和员工
void addManagerWithDepartment(Manager manager, Department department);
}
10. mapper中创建操作实体类的ManagerMapper接口, 然后启动类上加注解@MapperScan("com.example.work3.mapper")扫描 Mapper 类
java
// 用了@MapperScan("com.example.work3.mapper")就可以不用@Mapper
@Mapper
public interface ManagerMapper {
// 接口一: 条件分页查询
List<Manager> queryManagers(ManagerRequestDTO dto);
// 接口二: 按机构列表查询
List<Manager> queryManagersByDepartments(ManagerRequestDTO dto);
// 接口三: 更新指定员工信息
int updateManager(ManagerRequestDTO dto);
// 接口四: 删除员工
int deleteManager(String managerNo);
// 接口五: 新增员工
int insertManager(Manager manager);
}
java
@Mapper
public interface DepartmentMapper {
// 接口六
int insertDepartment(Department department);
}
11. ManagerMapper.xml中写mybatis语句
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.work3.mapper.ManagerMapper">
<!-- 接口一:按条件分页查询员工信息 -->
<select id="queryManagers" resultType="com.example.work3.entity.Manager">
SELECT manager_no, manager_name, department_id, department_name, status
FROM manager_info
<where>
<if test="managerNo != null and managerNo != ''">
AND manager_no = #{managerNo}
</if>
<if test="managerName != null and managerName != ''">
AND manager_name LIKE CONCAT('%', #{managerName}, '%')
</if>
<if test="departmentId != null and departmentId != ''">
AND department_id = #{departmentId}
</if>
<if test="departmentName != null and departmentName != ''">
AND department_name LIKE CONCAT('%', #{departmentName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
<!-- 接口二:按机构列表分页查询员工 -->
<select id="queryManagersByDepartments" resultType="com.example.work3.entity.Manager">
SELECT manager_no, manager_name, department_id, department_name, status
FROM manager_info
<where>
<if test="departmentIds != null and departmentIds.size > 0">
AND department_id IN
<foreach collection="departmentIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
<!-- 接口三:更新员工信息 -->
<update id="updateManager" parameterType="com.example.work3.dto.ManagerRequestDTO">
UPDATE manager_info
<set>
<if test="managerName != null and managerName != ''">
manager_name = #{managerName},
</if>
<if test="departmentId != null and departmentId != ''">
department_id = #{departmentId},
</if>
<if test="departmentName != null and departmentName != ''">
department_name = #{departmentName},
</if>
<if test="status != null">
status = #{status},
</if>
</set>
WHERE manager_no = #{managerNo}
</update>
<!-- 接口四:删除员工 -->
<delete id="deleteManager" parameterType="string">
DELETE FROM manager_info
WHERE manager_no = #{managerNo}
</delete>
<!-- 接口五:新增员工 -->
<insert id="insertManager" parameterType="com.example.work3.entity.Manager">
INSERT INTO manager_info (manager_no, manager_name, department_id, department_name, status)
VALUES (#{managerNo}, #{managerName}, #{departmentId}, #{departmentName}, #{status})
</insert>
</mapper>
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.work3.mapper.DepartmentMapper">
<!-- 接口六:同时新增员工和部门 -->
<insert id="insertDepartment" parameterType="com.example.work3.entity.Department">
INSERT INTO department_info (department_id, department_name)
VALUES (#{departmentId}, #{departmentName})
</insert>
</mapper>
12. impl中创建实现功能接口的实现类
java
@Service
public class ManagerServiceImpl implements ManagerService {
@Autowired
private ManagerMapper managerMapper;
@Autowired
private DepartmentMapper departmentMapper;
// 接口一
@Override
public PageResult<Manager> queryManagers(ManagerRequestDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<Manager> list = managerMapper.queryManagers(dto);
PageInfo<Manager> pageInfo = new PageInfo<>(list);
return PageResult.of(pageInfo);
}
// 接口二
@Override
public PageResult<Manager> queryByDepartments(ManagerRequestDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<Manager> list = managerMapper.queryManagersByDepartments(dto);
PageInfo<Manager> pageInfo = new PageInfo<>(list);
return PageResult.of(pageInfo);
}
// 接口三
@Override
public Result updateManager(ManagerRequestDTO dto) {
// 参数校验
if (dto.getManagerNo() == null || dto.getManagerNo().isEmpty()) {
return Result.fail("工号不能为空");
}
int rows = managerMapper.updateManager(dto);
if (rows > 0) {
return Result.ok("更新成功");
} else {
return Result.fail("未找到该员工或没有需要更新的字段");
}
}
// 接口四
@Override
public Result deleteManager(String managerNo) {
if (managerNo == null || managerNo.isEmpty()) {
return Result.fail("工号不能为空");
}
int rows = managerMapper.deleteManager(managerNo);
if (rows > 0) {
return Result.ok("删除成功");
} else {
return Result.fail("未找到该员工");
}
}
// 接口五
@Override
public Result addManager(Manager manager) {
if (manager.getManagerNo() == null || manager.getManagerNo().isEmpty()) {
return Result.fail("工号不能为空");
}
if (manager.getManagerName() == null || manager.getManagerName().isEmpty()) {
return Result.fail("姓名不能为空");
}
int rows = managerMapper.insertManager(manager);
if (rows > 0) {
return Result.ok("新增成功");
} else {
return Result.fail("新增失败");
}
}
// 接口六
@Override
@Transactional(rollbackFor = Exception.class) // 开启事务回滚
public void addManagerWithDepartment(Manager manager, Department department) {
// 1. 插入部门
departmentMapper.insertDepartment(department);
// 2. 模拟异常
if (manager.getManagerNo() == null) {
throw new RuntimeException("Manager 编号不能为空!");
}
// 3. 插入员工
managerMapper.insertManager(manager);
}
}
13. controller中创建ManagerController类
java
@Slf4j
@RestController
@RequestMapping("/managers")
public class ManagerController {
@Autowired
private ManagerService managerService;
// 接口1
@PostMapping("/query")
public Result queryManagers(@RequestBody ManagerRequestDTO dto) {
PageResult<Manager> pageResult = managerService.queryManagers(dto);
return Result.ok(pageResult);
}
// 接口2
@PostMapping("/queryByDepartments")
public Result queryManagersByDepartments(@RequestBody ManagerRequestDTO dto) {
PageResult<Manager> pageResult = managerService.queryByDepartments(dto);
return Result.ok(pageResult);
}
// 接口3
@PutMapping("/update")
public Result updateManager(@RequestBody ManagerRequestDTO dto) {
return managerService.updateManager(dto);
}
// 接口4
@DeleteMapping("/{managerNo}")
public Result deleteManager(@PathVariable String managerNo) {
return managerService.deleteManager(managerNo);
}
// 接口5
@PostMapping("/add")
public Result addManager(@RequestBody Manager manager) {
return managerService.addManager(manager);
}
// 插入员工同时插入部门
@PostMapping("/addWithDepartment")
public Result addWithDepartment(@RequestBody ManagerWithDepartmentDTO dto) {
try {
managerService.addManagerWithDepartment(dto.getManager(), dto.getDepartment());
return Result.ok("添加成功");
} catch (Exception e) {
log.error("添加员工及部门失败,事务已回滚", e);
return Result.fail("添加失败:" + e.getMessage());
}
}
}
14. 启动服务并在PostMan做接口测试
15. 新增员工并获取插入后的主键值
xml
<insert id="insertManager" parameterType="com.example.work3.entity.Manager" useGeneratedKeys="true" keyProperty="managerId">
INSERT INTO manager_info (manager_no, manager_name, department_id, department_name, status)
VALUES (#{managerNo}, #{managerName}, #{departmentId}, #{departmentName}, #{status})
</insert>
java
@PostMapping("/add")
public Result addManager(@RequestBody Manager manager) {
System.out.println("插入前ID: " + manager.getManagerId());
Result result = managerService.addManager(manager);
System.out.println("插入后ID: " + manager.getManagerId());
return result;
}
遇到的异常及解决办法
WARNING
Failed to parse mapping resource: 'file [D:\JavaProject_IDEA\work3\target\classes\mapper\ManagerMapper.xml]'
💡排查方法
- 检查
XML
语法是否有误 检查所有<if>、<set>、<foreach>
标签是否成对闭合。
检查<set>
内是否有多余的,
- Mapper XML 文件路径不对
在application.yaml
中:
mybatis:
mapper-locations: classpath:mapper/*.xml
确保ManagerMapper.xml
实际路径是:src/main/resources/mapper/ManagerMapper.xml
WARNING
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'managerMapper' ... Invalid value type for attribute 'factoryBeanObjectType': java.lang.String
💡排查方法
- Mapper 没有被正确扫描
如果你使用了@MapperScan("com.example.work3.mapper")
,Spring 会自动扫描接口并生成Mapper Bean
。
如果在Mapper 接口
上用@Mapper
注解,也可以单独被扫描。 如果配置冲突或路径错误,就可能出现Invalid value type for attribute 'factoryBeanObjectType'
。- 依赖版本不兼容
例如MyBatis
的版本与Spring Boot
版本不匹配,也可能报这个错。
IMPORTANT
特别是Spring Boot 2.x/3.x
与 MyBatis 3.x
的版本一定要相匹配。
Springboot使用Log4j2记录日志
- 修改依赖
需要排除Spring Boot
默认的Logback
,添加Log4j2
支持:
xml
<!-- 替换默认日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
- 改配置
yaml
mybatis:
mapper-locations: classpath:mapper/*.xml # Mapper XML 文件路径
type-aliases-package: com.example.work3.entity # 实体类所在包
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰
# 使用Log4j2实现
log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
logging:
level:
com.example.work3.mapper: DEBUG # 配置Mapper包路径,显示SQL语句
org.apache.ibatis: DEBUG # MyBatis相关日志
root: INFO
- 编写
log4j2-spring.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<!-- 定义带高亮的日志格式 -->
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss} %highlight{[%t] %-5level} %style{%logger{50}}{cyan} - %msg%n
</Property>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false"/>
</Console>
<!-- 数据源相关日志文件输出 -->
<RollingFile name="datasource" immediateFlush="true"
fileName="log/datasource/datasource.log"
filePattern="log/datasource/datasource - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies>
</RollingFile>
<!-- 框架相关日志文件输出 -->
<RollingFile name="framework" immediateFlush="true"
fileName="log/framework/framework.log"
filePattern="log/framework/framework - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<!-- MyBatis SQL日志 -->
<Logger name="com.example.work3.mapper" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="datasource"/>
</Logger>
<!-- MyBatis框架日志 -->
<Logger name="org.apache.ibatis" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<!-- Spring框架日志 -->
<Logger name="org.springframework" level="INFO" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<!-- 根Logger -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="framework"/>
</Root>
</Loggers>
</Configuration>
Log4j2格式化模式详解
Log4j2的日志格式与Logback类似但有细微差别:
xml
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} - %msg%n
</Property>
各部分说明:
%d{yyyy-MM-dd HH:mm:ss.SSS}
:时间戳格式[%t]
:线程名%-5level
:日志级别,左对齐5字符宽度%logger{50}
:Logger名称,最多50字符%msg
:日志消息%n
:换行符
日志级别
在log4j2中,共有8个级别,按照从低到高为:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
- All:最低等级的,用于打开所有日志记录.
- Trace:是追踪,就是程序推进一下.
- Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
- Info:消息在粗粒度级别上突出强调应用程序的运行过程.
- Warn:输出警告及warn以下级别的日志.
- Error:输出错误信息日志.
- Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
- OFF:最高等级的,用于关闭所有日志记录
程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少。