Spring Boot入门:通过简单的注册功能串联Controller,Service,Mapper。(含有数据库建立,连接,及一些关键注解的讲解)

一、准备工作

1.数据库的建立与连接

这里我们用主流数据库mysql,推荐使用docker建立数据库环境。

可视化,操作数据库的软件用DBeaver。

下面是数据库编辑器的代码:

sql 复制代码
CREATE DATABASE big_event;
USE big_event;

CREATE TABLE user (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
    username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
    password VARCHAR(32) COMMENT '密码',
    nickname VARCHAR(10) DEFAULT '' COMMENT '昵称',
    email VARCHAR(128) DEFAULT '' COMMENT '邮箱',
    user_pic VARCHAR(128) DEFAULT '' COMMENT '头像',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '用户表';

这里定义了用户的基本属性,之后在java当中我们也要创建一个User类与这个表一一对应。

最后,在IDE当中的resources的文件夹下建立application.yml文件,输入配置信息:

sql 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/big_event
    username: root
    password: 你的密码

至此,数据库就算是接上spring了。

2.spring依赖

除了数据库,web和mybatis的依赖,我还加入了valibation和lombok依赖,它们提供了减少代码复杂度的注解,用到的时候我会解释。

sql 复制代码
<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 http://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.1.3</version>
    </parent>
  <groupId>org.example</groupId>
  <artifactId>big-event</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>big-event</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
      <!-- lombok 依赖-->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.30</version> 
      </dependency>
      <!--web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
      <!--mybatis-->
      <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>3.0.0</version>
      </dependency>
      <!--数据库依赖 -->
      <dependency>
          <groupId>com.mysql</groupId>
          <artifactId>mysql-connector-j</artifactId>
      </dependency>
      <!--参数校验依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>
  </dependencies>
</project>

二、项目结构展示

可以先根据截图将类和接口都创建好。

1. Controller:接请求

负责接收前端传来的用户名和密码,并调用业务层。

2. Service:管业务

负责决定这件事该怎么做,比如先查用户,再决定是否注册。

3. Mapper:查数据库

负责执行 SQL,把查询和插入操作真正落到数据库上。

4. Result:统一返回格式

不管成功还是失败,都返回统一结构,方便前后端交互。

5. Md5Util:处理密码

让密码不要以明文形式直接存入数据库。

6. GlobalExceptionHandler:全局异常处理器

接收异常,处理异常。

三、User类

java 复制代码
package org.example.pojo;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {
    private Integer id;//主键ID
    private String username;//用户名
    private String password;//密码
    private String nickname;//昵称
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}

定义user类,属性要和数据库中一致,这里@Data是lombok提供的注解,运行程序后可以把这个类的所以属性都写上set和get方法。非常方便。

四、接口层

sql 复制代码
package org.example.controller;

import jakarta.validation.constraints.Pattern;
import org.example.pojo.Result;
import org.example.pojo.User;
import org.example.service.UserService;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
@Validated
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/register")
    public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){
        //查询用户
        User u= userService.findByUserName(username);
            if(u==null){
                //注册
                userService.register(username,password);
                return Result.success();
            }else{
                return Result.error("用户已被占用");
            }
    }
}

1.代码解释

常规注解解释:

@RestController:说明这是一个控制器类,返回的数据会直接变成 JSON

@RequestMapping("/user"):说明这个类里的接口统一以 /user 开头,避免代码耦合

@PostMapping("/register"):说明这是一个 POST 请求,对应地址是 /user/register

@Autowired:spring boot的关键设计之一,自动注入功能,接口层只负责接受,业务逻辑需要在Service层实现,所以这个需要借助注释来注入一个Service对象。

参数校验部分注解解释:

前端传过来的参数,后端不能直接信。需要通过判断逻辑来检查。

当然,你可以直接使用if else判断语句来驳回违法参数,但这样做会让代码变得复杂且不美观,通常,我们会使用注解来实现参数校验

@Validated:validated依赖提供的注解,该类开启参数校验功能。

@Pattern:限制用户名和密码必须符合指定规则。@Pattern(regexp = "^\\S{5,16}$")的意思就是用户名和密码必须是 5 到 16 位,并且不能包含空格等空白字符。

逻辑解释:

这段代码的逻辑十分简单,User u= userService.findByUserName(username);通过Service层提供的方法来判断用户名有没有被占用,具体的实现方法都写在Service和Mapper当中。

2.Controller 层的职责:

Controller 层一般只做三件事:

  1. 接收前端请求
  2. 接收参数并做基础校验
  3. 调用 Service,最后把结果返回给前端

也就是说,Controller 更像一个"前台接待":

  • 用户来办事,它先接待
  • 检查提交材料是否符合最基本要求
  • 然后把真正的业务交给后面的 Service 去处理

Controller 一般不要写太多业务逻辑

五、Service层

service接口

java 复制代码
package org.example.service;

import org.example.pojo.User;

public interface UserService {
    User findByUserName(String username);

    //注册
    void register(String username,String password);
}

service类

java 复制代码
package org.example.service.impl;

import org.example.mapper.UserMapper;
import org.example.pojo.User;
import org.example.service.UserService;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserserviceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public User findByUserName(String username) {
        User u= userMapper.findByUserName(username);
        return u;
    }

    @Override
    public void register(String username, String password) {
        //加密
        String md5String = Md5Util.getMD5String(password);
        //添加
        userMapper.add(username,md5String);
    }
}

1.代码解释

findByUserName方法实现了根据用户名查找用户的功能,但是service不能直接操作数据库,需要借助mapper中的方法。

register提供了注册方法,这里要注意,密码不能直接存到数据库中,要经过加密处理,这里用的Md5的加密方法,不用理解,直接复制即可。将数据添加到数据库当中,也需要操纵数据库,所以任然需要mapper中的方法。

java 复制代码
package org.example.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

2.Service 层是干什么的

Service 层就是"业务层"。

它和 Controller 的最大区别是:

  • Controller 关心"请求来了怎么办"
  • Service 关心"这件事到底该怎么做"

比如注册功能,业务逻辑可能包括:

  • 先查用户名是否存在
  • 如果不存在,再对密码加密
  • 再调用 Mapper 插入数据库
  • 以后也许还要发欢迎消息、记录日志、注册送积分......

这些都属于业务逻辑,最适合放在 Service。

Service层的业务逻辑很多,但操作数据库的实际上是mapper。

六、mapper层

java 复制代码
package org.example.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.pojo.User;


@Mapper
public interface UserMapper {
    //根据用户名查询用户
    @Select("select * from user where username=#{username}")
    User findByUserName(String username);

    //添加
    @Insert("insert into user(username,password,create_time,update_time)"+"values(#{username},#{md5String},now(),now())")
    void add(String username,String md5String);
}

1.代码解释

这里用到的是mybatis提供的注解,简单来说mybatis就是实现了在java中编写sql代码,实现操作数据库的功能。

@Mapper就是在告诉spring boot这是个mapper接口,请帮我生成实现类,你虽然只写了接口,但运行时 MyBatis 会帮你创建代理对象,所以你可以像调用普通方法一样调用它。

@Select因为这里是查找用户所以是select,括号里面写的是具体的查找方法

@Insert表示的是插入数据,除了要用户名和密码外还需要输入创建时间和更新时间,获取当前时间的方法是now()。

2.Mapper 层是干什么的

Mapper 层就是:

专门负责操作数据库。

在这段代码里,它做了两件事:

  1. findByUserName:去数据库查用户
  2. add:把新用户插入数据库

为什么数据库操作要单独放 Mapper

因为 SQL 和业务逻辑不是一回事。

例如:

  • "用户名是否允许重复"是业务规则
  • "怎么查 user 表"是数据库操作

所以 Service 负责业务判断,Mapper 负责数据库执行,这样分工清楚,不容易乱。

七、统一响应类Result

java 复制代码
@Data
public class Result<T> {
    private Integer code;//业务状态码  0-成功  1-失败
    private String message;//提示信息
    private T data;//响应数据

    public Result(Integer i, String m, T d) {
        this.code=i;
        this.message=m;
        this.data=d;
    }

    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result error(String message) {
        return new Result(1, message, null);
    }
}

这个类的作用很简单,就是为你返回的数据套上一层壳,统一返回数据的格式。

为什么要统一格式

如果不统一,今天你返回字符串,明天返回对象,后天返回布尔值,前端就会很难处理。

统一格式后,前端只需要约定:

  • code == 0 表示成功
  • code == 1 表示失败
  • message 用来看提示
  • data 用来放真正的数据

这样整个项目风格就统一了。

泛型 <T> 是干什么的

Result<T> 中的 T 表示:

data 里存的数据类型是不固定的。

比如:

  • 注册成功,可能没有数据,data = null
  • 查询用户信息时,data 可以是 User
  • 查询用户列表时,data 可以是 List<User>

所以泛型的作用是:

Result 这个类可以适配不同类型的数据,而不用为每种返回值都重新写一个类。

八、全局异常处理器

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e){
        e.printStackTrace();
        return Result.error(
            Boolean.parseBoolean(String.valueOf((e.getMessage()==null))) 
            ? "操作失败" 
            : e.getMessage()
        );
    }
}

这是一个兜底的设计。

作用就是统一处理项目中抛出的异常,避免报错时直接把一大堆错误信息甩给前端。

@ExceptionHandler(Exception.class)

这句表示:

只要出现 Exception 类型的异常,就执行这个方法。

然后在方法里返回 Result.error(...),这样前端收到的仍然是统一 JSON,而不是一堆不友好的报错。

九、总结

对于初学者来说,Spring Boot 的注册功能是一个非常好的入门案例。因为它虽然简单,却几乎把后端开发里最常见的几个核心点都串起来了:接口设计、三层架构、参数校验、统一返回、数据库操作、密码处理、异常处理。

当你把这个小功能真正吃透之后,后面再去学登录、JWT、拦截器、权限控制、Redis 缓存,其实都会更顺,因为你已经理解了后端接口最基本的运行方式。

这个案例真正教你的不是"怎么写一个注册接口",而是:

一个 Spring Boot 接口,从接收请求到返回结果,中间到底经历了什么。

相关推荐
AI应用实战 | RE2 小时前
014、索引高级实战:当单一向量库不够用的时候
数据库·人工智能·langchain
程序边界2 小时前
行标识符机制的技术演进与实践(下)——ROWID与实战应用
后端
YDS8292 小时前
大营销平台 —— 抽奖前置规则过滤
java·spring boot·ddd
清水白石0082 小时前
《Python 架构师的自动化哲学:从基础语法到企业级作业调度系统与 Airflow 止损实战》
数据库·python·自动化
Justin3go2 小时前
丢掉沉重的记忆:Codex、Claude Code 与 OpenCode 的上下文压缩术
前端·后端·架构
阿华田5122 小时前
MySQL性能优化大全
数据库·mysql·性能优化
不懂的浪漫2 小时前
mqtt-plus 架构解析(五):错误处理与 ErrorAction 聚合策略
java·spring boot·后端·物联网·mqtt·架构
kaico20182 小时前
python操作数据库
开发语言·数据库·python
被摘下的星星2 小时前
MySQL 别名使用规则详解
数据库·mysql