目录
[一、环境依赖(pom.xml 核心依赖)](#一、环境依赖(pom.xml 核心依赖))
[1. 实体类(Entity)](#1. 实体类(Entity))
[2. DTO 类(数据传输对象)](#2. DTO 类(数据传输对象))
[(1)新增 / 修改请求 DTO](#(1)新增 / 修改请求 DTO)
[(2)条件查询相关 DTO: 涵盖单条件精确查询、多条件组合查询、模糊查询、范围查询等常见场景](#(2)条件查询相关 DTO: 涵盖单条件精确查询、多条件组合查询、模糊查询、范围查询等常见场景)
[(3)响应 DTO](#(3)响应 DTO)
[3. MapStruct 映射接口](#3. MapStruct 映射接口)
[4. Mapper 接口(MyBatis-Plus)](#4. Mapper 接口(MyBatis-Plus))
[5. Service 层(业务逻辑)](#5. Service 层(业务逻辑))
[(1)Service 接口](#(1)Service 接口)
[(2)Service 实现类](#(2)Service 实现类)
[6. Controller 层(接口暴露)](#6. Controller 层(接口暴露))
[1. MyBatis-Plus 配置](#1. MyBatis-Plus 配置)
[2. application.yml 配置](#2. application.yml 配置)
基于 Spring Boot + MyBatis-Plus + MapStruct 的完整 CRUD 实现,包含实体、DTO、映射、Service、Controller 全流程。
一、环境依赖(pom.xml 核心依赖)
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MapStruct(DTO与Entity映射) -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
<!-- 数据库驱动(以MySQL为例) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二、数据库表设计(以sys_user为例)
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
三、核心代码实现
1. 实体类(Entity)
与数据库表字段一一对应,用于 MyBatis-Plus 操作数据库
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_user")
public class UserEntity {
@TableId(type = IdType.AUTO)
private Long id; // 主键ID
private String username; // 用户名
private String password; // 密码
private String email; // 邮箱
private LocalDateTime createTime; // 创建时间
private LocalDateTime updateTime; // 更新时间
}
2. DTO 类(数据传输对象)
用于接收前端请求参数和返回响应数据,避免直接暴露 Entity。
(1)新增 / 修改请求 DTO
// 新增用户请求DTO
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Email;
import javax.validation.constraints.Size;
@Data
public class UserAddDTO {
@NotBlank(message = "用户名不能为空")
@Size(max = 50, message = "用户名长度不能超过50字符")
private String username; // 用户名
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度需在6-20字符之间")
private String password; // 密码
@Email(message = "邮箱格式不正确")
private String email; // 邮箱
}
// 修改用户请求DTO
@Data
public class UserUpdateDTO {
@NotBlank(message = "用户ID不能为空")
private Long id; // 主键ID(修改需传)
@Size(max = 50, message = "用户名长度不能超过50字符")
private String username; // 用户名(可选修改)
@Size(min = 6, max = 20, message = "密码长度需在6-20字符之间")
private String password; // 密码(可选修改)
@Email(message = "邮箱格式不正确")
private String email; // 邮箱(可选修改)
}
(2)条件查询相关 DTO: 涵盖单条件精确查询、多条件组合查询、模糊查询、范围查询等常见场景
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户表条件查询DTO
* 支持:分页参数、模糊查询、精确查询、范围查询、多值查询、排序参数
* 说明:所有参数均为可选,不传则不参与筛选
*/
@Data
public class UserQueryDTO {
// ======================== 分页参数(用于分页查询,非分页查询可忽略) ========================
/**
* 页码(默认1,最小1)
*/
@Min(value = 1, message = "页码不能小于1")
private Integer pageNum = 1;
/**
* 每页条数(默认10,最小1,最大100)
*/
@Min(value = 1, message = "每页条数不能小于1")
@Max(value = 100, message = "每页条数不能大于100")
private Integer pageSize = 10;
// ======================== 模糊查询条件 ========================
/**
* 用户名(模糊匹配,例如输入"张"可查询所有姓张的用户)
*/
private String username;
/**
* 邮箱(模糊匹配,例如输入"example.com"可查询所有该域名的邮箱)
* 格式要求:为空或符合邮箱格式(如xxx@xxx.com)
*/
@Pattern(regexp = "^$|^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$", message = "邮箱格式不正确(可为空)")
private String email;
// ======================== 精确查询条件 ========================
/**
* 用户状态(精确匹配:1-正常,2-禁用,不传则不筛选)
*/
private Integer status;
// ======================== 范围查询条件 ========================
/**
* 创建时间起始(>=,格式:yyyy-MM-dd HH:mm:ss,例如"2023-01-01 00:00:00")
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeStart;
/**
* 创建时间结束(<,格式:yyyy-MM-dd HH:mm:ss,例如"2023-12-31 23:59:59")
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
/**
* 用户ID范围起始(>=,例如100表示查询ID>=100的用户)
*/
private Long idStart;
/**
* 用户ID范围结束(<=,例如200表示查询ID<=200的用户)
*/
private Long idEnd;
// ======================== 多值查询条件 ========================
/**
* 用户ID列表(精确匹配列表中的ID,例如[1,2,3]表示查询ID为1、2、3的用户)
*/
private List<Long> idList;
/**
* 排除的用户ID列表(排除列表中的ID,例如[4,5,6]表示查询ID不是4、5、6的用户)
*/
private List<Long> excludeIdList;
// ======================== 排序参数 ========================
/**
* 排序字段(默认createTime,可选值:id、username、createTime)
*/
@Pattern(regexp = "^$|id|username|createTime", message = "排序字段只能是id、username、createTime")
private String sortField = "createTime";
/**
* 排序方式(asc-升序,desc-降序,默认desc)
*/
@Pattern(regexp = "^(asc|desc)$", message = "排序方式只能是asc或desc")
private String sortOrder = "desc";
}
(3)响应 DTO
// 用户响应DTO(前端展示用,隐藏敏感字段如密码)
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserRespDTO {
private Long id; // 主键ID
private String username; // 用户名
private String email; // 邮箱
private LocalDateTime createTime; // 创建时间
}
3. MapStruct 映射接口
用于 DTO 与 Entity 之间的自动转换,替代手动BeanUtils.copyProperties。
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
// 映射接口,componentModel = "spring"表示交给Spring管理
@Mapper(componentModel = "spring")
public interface UserMapper {
// 单例实例(也可通过Spring注入)
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// UserAddDTO -> UserEntity(新增时转换)
UserEntity addDtoToEntity(UserAddDTO dto);
// UserUpdateDTO -> UserEntity(更新时转换,覆盖已有字段)
void updateDtoToEntity(UserUpdateDTO dto, @MappingTarget UserEntity entity);
// UserEntity -> UserRespDTO(查询响应时转换)
UserRespDTO entityToRespDto(UserEntity entity);
}
4. Mapper 接口(MyBatis-Plus)
继承BaseMapper,无需手动写 SQL(简单 CRUD)。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper<UserEntity> {
// 复杂查询可在此添加注解SQL或XML映射
}
5. Service 层(业务逻辑)
(1)Service 接口
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface UserService extends IService<UserEntity> {
// 新增用户
boolean addUser(UserAddDTO dto);
// 修改用户
boolean updateUser(UserUpdateDTO dto);
// 根据ID删除用户
boolean removeUserById(Long id);
// 根据ID查询用户
UserRespDTO getUserById(Long id);
// 查询所有用户
List<UserRespDTO> listAllUsers();
}
(2)Service 实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {
@Autowired
private UserMapper userMapper; // MapStruct映射接口
@Override
public boolean addUser(UserAddDTO dto) {
// DTO转Entity
UserEntity entity = userMapper.addDtoToEntity(dto);
// 保存到数据库
return save(entity);
}
@Override
public boolean updateUser(UserUpdateDTO dto) {
// 先查询原实体
UserEntity entity = getById(dto.getId());
if (entity == null) {
return false; // 用户不存在
}
// DTO数据更新到Entity
userMapper.updateDtoToEntity(dto, entity);
// 保存更新
return updateById(entity);
}
@Override
public boolean removeUserById(Long id) {
return removeById(id);
}
@Override
public UserRespDTO getUserById(Long id) {
UserEntity entity = getById(id);
return entity != null ? userMapper.entityToRespDto(entity) : null;
}
@Override
public List<UserRespDTO> listAllUsers() {
List<UserEntity> entityList = list();
// 批量Entity转DTO
return entityList.stream()
.map(userMapper::entityToRespDto)
.collect(Collectors.toList());
}
}
6. Controller 层(接口暴露)
接收前端请求,调用 Service 处理,返回响应结果。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// 新增用户
@PostMapping
public ResponseEntity<Boolean> addUser(@Validated @RequestBody UserAddDTO dto) {
boolean success = userService.addUser(dto);
return new ResponseEntity<>(success, HttpStatus.CREATED);
}
// 修改用户
@PutMapping
public ResponseEntity<Boolean> updateUser(@Validated @RequestBody UserUpdateDTO dto) {
boolean success = userService.updateUser(dto);
return ResponseEntity.ok(success);
}
// 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<Boolean> removeUser(@PathVariable Long id) {
boolean success = userService.removeUserById(id);
return ResponseEntity.ok(success);
}
// 根据ID查询用户
@GetMapping("/{id}")
public ResponseEntity<UserRespDTO> getUserById(@PathVariable Long id) {
UserRespDTO dto = userService.getUserById(id);
return dto != null ? ResponseEntity.ok(dto) : ResponseEntity.notFound().build();
}
// 查询所有用户
@GetMapping
public ResponseEntity<List<UserRespDTO>> listAllUsers() {
List<UserRespDTO> dtoList = userService.listAllUsers();
return ResponseEntity.ok(dtoList);
}
}
@Validated 分组校验
新增用户时无需校验 id ,修改时必须校验 id ,可通过分组实现:
7. 全局异常处理(捕获校验失败信息)
添加全局异常处理器,统一返回校验失败的提示,避免直接返回 400 原始错误:
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器:处理参数校验失败异常
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕获 @Validated 分组校验失败的异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
// 获取所有校验失败的字段信息
BindingResult bindingResult = ex.getBindingResult();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
// 字段名 -> 错误提示
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return errors;
}
}
校验失败示例响应:
{
"username": "用户名不能为空",
"password": "密码长度需在6-20字符之间"
}
四、关键配置
1. MyBatis-Plus 配置
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.example.demo.mapper") // Mapper接口所在包
public class MyBatisPlusConfig {
// 分页插件(可选,用于复杂查询分页)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2. application.yml 配置
spring:
# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
# 实体类包名(用于自动扫描)
type-aliases-package: com.example.demo.entity
# mapper.xml文件路径(若有自定义SQL)
mapper-locations: classpath:mapper/*.xml
# 日志配置(打印SQL,开发环境用)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl