Spring Boot 用户注册接口(含事务 + 参数校验)

张开发
2026/4/19 1:37:29 15 分钟阅读

分享文章

Spring Boot 用户注册接口(含事务 + 参数校验)
一、前言当我们把查询接口跑通之后下一步最自然的就是❗实现一个真正的用户注册接口很多人一开始写注册接口会直接在 Controller 里一把梭接参数判空查数据库插数据返回结果这样能跑但很快就会乱。这篇文章我带你用一个更规范、更接近企业项目的方式完成一个最小可运行版本的用户注册接口并且加上两个关键能力参数校验事务控制二、最终目标实现接口POST /user/register请求体{ username: admin, password: 123456 }返回{ code: 0, message: 成功, data: 注册成功 }三、先说最终结构这篇文章采用最小工程化结构controller service mapper entity dto common四、数据库准备表结构如下CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL, password VARCHAR(128) NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );五、准备 DTO接收前端参数路径dto/UserRegisterDTO.java代码package org.example.arkbackend.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; Data public class UserRegisterDTO { NotBlank(message 用户名不能为空) Size(min 3, max 20, message 用户名长度必须在3到20之间) private String username; NotBlank(message 密码不能为空) Size(min 6, max 20, message 密码长度必须在6到20之间) private String password; }六、准备实体类路径entity/User.java代码package org.example.arkbackend.entity; import lombok.Data; import java.time.LocalDateTime; Data public class User { private Long id; private String username; private String password; private LocalDateTime createTime; }七、统一返回体路径common/Result.java代码package org.example.arkbackend.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(0); r.setMessage(成功); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(1); r.setMessage(message); r.setData(null); return r; } }八、编写 Mapper 接口路径mapper/UserMapper.java代码package org.example.arkbackend.mapper; import org.apache.ibatis.annotations.Mapper; import org.example.arkbackend.entity.User; Mapper public interface UserMapper { User selectByUsername(String username); int insert(User user); }九、编写 XML路径resources/mapper/UserMapper.xml代码?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespaceorg.example.arkbackend.mapper.UserMapper select idselectByUsername resultTypeUser select id, username, password, create_time from user where username #{username} limit 1 /select insert idinsert useGeneratedKeystrue keyPropertyid insert into user(username, password) values(#{username}, #{password}) /insert /mapper十、编写 Service路径service/UserService.java代码package org.example.arkbackend.service; import org.example.arkbackend.dto.UserRegisterDTO; public interface UserService { String register(UserRegisterDTO dto); }路径service/impl/UserServiceImpl.java代码package org.example.arkbackend.service.impl; import lombok.RequiredArgsConstructor; import org.example.arkbackend.dto.UserRegisterDTO; import org.example.arkbackend.entity.User; import org.example.arkbackend.mapper.UserMapper; import org.example.arkbackend.service.UserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; Service RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserMapper userMapper; Override Transactional public String register(UserRegisterDTO dto) { User existUser userMapper.selectByUsername(dto.getUsername()); if (existUser ! null) { throw new RuntimeException(用户名已存在); } User user new User(); user.setUsername(dto.getUsername()); user.setPassword(dto.getPassword()); int rows userMapper.insert(user); if (rows ! 1) { throw new RuntimeException(注册失败); } return 注册成功; } }十一、编写 Controller路径controller/UserController.java代码package org.example.arkbackend.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.example.arkbackend.common.Result; import org.example.arkbackend.dto.UserRegisterDTO; import org.example.arkbackend.service.UserService; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/user) RequiredArgsConstructor public class UserController { private final UserService userService; PostMapping(/register) public ResultString register(RequestBody Valid UserRegisterDTO dto) { return Result.success(userService.register(dto)); } }十二、为什么要加 Valid这一句非常关键RequestBody Valid UserRegisterDTO dto意思是在进入 Controller 方法之前先对 DTO 做参数校验比如用户名为空密码太短这些问题根本不该进入业务层。十三、为什么要加 Transactional这一句也非常关键Transactional它的意义是让register()方法里的数据库操作处于同一个事务中也就是说如果都成功 → 提交如果中途报错 → 回滚虽然你现在注册逻辑只有一条 insert看起来事务好像“没必要”。但你要形成工程思维❗只要一个业务动作涉及数据库修改就应该开始建立事务意识以后注册逻辑很可能变成插入用户表插入用户角色表发送注册消息这时候事务就非常重要了。十四、全局异常处理推荐补上如果参数校验失败默认报错会比较丑。我们可以统一拦一下。路径common/GlobalExceptionHandler.java代码package org.example.arkbackend.common; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(RuntimeException.class) public ResultString handleRuntimeException(RuntimeException e) { return Result.fail(e.getMessage()); } ExceptionHandler(MethodArgumentNotValidException.class) public ResultString handleValidException(MethodArgumentNotValidException e) { String message e.getBindingResult() .getFieldError() .getDefaultMessage(); return Result.fail(message); } }十五、测试一下1. 正常请求{ username: admin123, password: 123456 }返回{ code: 0, message: 成功, data: 注册成功 }2. 用户名为空{ username: , password: 123456 }返回{ code: 1, message: 用户名不能为空, data: null }3. 用户名重复返回{ code: 1, message: 用户名已存在, data: null }十六、这一版代码的优点这套注册接口虽然简单但已经具备了企业项目的基本味道DTO 和 Entity 分离Controller 只接请求不写业务Service 负责核心逻辑MyBatis 负责数据库访问Transactional保障事务Valid保障参数校验统一返回体全局异常处理十七、一句话总结❗ 一个合格的注册接口不只是“能插入数据库”而是要同时考虑参数是否合法事务是否安全返回是否统一异常是否可控十八、下一篇预告下一篇可以继续写《Spring Boot 用户登录接口JWT 拦截器 登录状态校验》这就能把“注册 登录”两个最基础的用户系统闭环跑起来了。

更多文章