Skip to content

员工管理系统

一个基于SpringbootMybatis实现的员工管理系统,使用PageHelper分页和事务处理。

github

需求

  1. 接口一:支持按照姓名(模糊查询)、工号、机构id、 机构名称(模糊查询)、 状态查询员工信息 (以上四个条件可以输入一个也可以输入多个)。结果支持分页展示。
  2. 接口二: 支持输入机构列表、 查询多个机构下的员工信息、结果支持分页展示。
  3. 接口三:更新指定员工的状态值、机构号、机构名称(以上三个信息可以输入一个也可以输入多个)
  4. 接口四:指定工号删除员工
  5. 接口五:新增员工信息
  6. 接口六:使用@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项目

Alt text

2. 添加Maven依赖

Alt text

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. 删除多余的文件,完善目录结构

Alt text

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]'

💡排查方法

  1. 检查XML语法是否有误 检查所有 <if>、<set>、<foreach> 标签是否成对闭合。
    检查<set>内是否有多余的 ,
  2. 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

💡排查方法

  1. Mapper 没有被正确扫描
    如果你使用了 @MapperScan("com.example.work3.mapper"),Spring 会自动扫描接口并生成 Mapper Bean
    如果在 Mapper 接口上用 @Mapper 注解,也可以单独被扫描。 如果配置冲突或路径错误,就可能出现 Invalid value type for attribute 'factoryBeanObjectType'
  2. 依赖版本不兼容
    例如 MyBatis 的版本与 Spring Boot 版本不匹配,也可能报这个错。

IMPORTANT

特别是Spring Boot 2.x/3.xMyBatis 3.x的版本一定要相匹配。

Springboot使用Log4j2记录日志

  1. 修改依赖

需要排除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>
  1. 改配置
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
  1. 编写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:最高等级的,用于关闭所有日志记录

程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少。