ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/8/22 21:18:25 拓冰建站 浏览量
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 Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(0); r.setMessage("成功"); r.setData(data); return r; } public static <T> Result<T> fail(String message) { Result<T> 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 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="org.example.arkbackend.mapper.UserMapper"> <select id="selectByUsername" resultType="User"> select id, username, password, create_time from user where username = #{username} limit 1 </select> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> 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 Result<String> 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 Result<String> handleRuntimeException(RuntimeException e) { return Result.fail(e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result<String> 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 + 拦截器 + 登录状态校验)》

这就能把“注册 + 登录”两个最基础的用户系统闭环跑起来了。