后端开发CRUD实现
目录
一、环境依赖(pom.xml 核心依赖)
二、数据库表设计(以sys_user为例)
三、核心代码实现
1. 实体类(Entity)
2. DTO 类(数据传输对象)
(1)新增 / 修改请求 DTO
(2)条件查询相关 DTO: 涵盖单条件精确查询、多条件组合查询、模糊查询、范围查询等常见场景
(3)响应 DTO
3. MapStruct 映射接口
4. Mapper 接口(MyBatis-Plus)
5. Service 层(业务逻辑)
(1)Service 接口
(2)Service 实现类
6. Controller 层(接口暴露)
四、关键配置
1. MyBatis-Plus 配置
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; // 主键IDprivate 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; // 主键IDprivate 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 {@Autowiredprivate UserMapper userMapper; // MapStruct映射接口@Overridepublic boolean addUser(UserAddDTO dto) {// DTO转EntityUserEntity entity = userMapper.addDtoToEntity(dto);// 保存到数据库return save(entity);}@Overridepublic boolean updateUser(UserUpdateDTO dto) {// 先查询原实体UserEntity entity = getById(dto.getId());if (entity == null) {return false; // 用户不存在}// DTO数据更新到EntityuserMapper.updateDtoToEntity(dto, entity);// 保存更新return updateById(entity);}@Overridepublic boolean removeUserById(Long id) {return removeById(id);}@Overridepublic UserRespDTO getUserById(Long id) {UserEntity entity = getById(id);return entity != null ? userMapper.entityToRespDto(entity) : null;}@Overridepublic List<UserRespDTO> listAllUsers() {List<UserEntity> entityList = list();// 批量Entity转DTOreturn 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 {@Autowiredprivate UserService userService;// 新增用户@PostMappingpublic ResponseEntity<Boolean> addUser(@Validated @RequestBody UserAddDTO dto) {boolean success = userService.addUser(dto);return new ResponseEntity<>(success, HttpStatus.CREATED);}// 修改用户@PutMappingpublic 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();}// 查询所有用户@GetMappingpublic 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 {// 分页插件(可选,用于复杂查询分页)@Beanpublic 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=trueusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivermybatis-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
