在Java后端开发中,搭建一个规范的RESTful API服务是每个开发者必须掌握的技能。本文将以Spring Boot 3.x为核心,整合MyBatis和MySQL,从零开始构建一个完整的用户管理系统API。无论你是刚入门的新手还是想梳理最佳实践的进阶开发者,都能从这篇文章中获得实用的参考。

1. 项目初始化与工程结构

推荐使用 Spring Initializr 来初始化项目。选择以下依赖:

  • Spring Web:提供RESTful接口支持
  • Spring Boot Starter JDBC:数据库连接基础
  • MyBatis Spring Boot Starter:ORM框架
  • MySQL Connector:MySQL驱动
  • Lombok:简化实体类代码
  • Spring Boot Starter Validation:参数校验

项目创建后的目录结构如下:

src/main/java/com/example/userapi/
├── UserApiApplication.java          // 启动类
├── controller/
│   └── UserController.java          // 控制器层
├── service/
│   ├── UserService.java             // 接口
│   └── impl/
│       └── UserServiceImpl.java     // 实现类
├── repository/
│   └── UserRepository.java          // 数据访问层
├── entity/
│   └── User.java                    // 数据库实体
├── dto/
│   ├── CreateUserRequest.java       // 创建用户请求DTO
│   └── UpdateUserRequest.java       // 更新用户请求DTO
├── vo/
│   ├── UserVO.java                  // 用户响应VO
│   └── PageResult.java              // 分页结果封装
├── common/
│   ├── Result.java                  // 统一响应封装
│   └── GlobalExceptionHandler.java  // 全局异常处理
└── config/
    └── SwaggerConfig.java           // Swagger配置

pom.xml 中的核心依赖配置如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath/>
</parent>

<properties>
    <java.version>17</java.version>
</properties>

<dependencies>
    <!-- Spring 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.3</version>
    </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>

    <!-- Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Knife4j (Swagger增强) -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        <version>4.5.0</version>
    </dependency>
</dependencies>

application.yml 中配置数据源和MyBatis:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user_api?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.userapi.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

server:
  port: 8080

2. 数据库设计与MyBatis集成

以用户管理为例,设计一张基础的用户表:

CREATE TABLE `user` (
    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `username`    VARCHAR(50)  NOT NULL COMMENT '用户名',
    `email`       VARCHAR(100) NOT NULL COMMENT '邮箱',
    `phone`       VARCHAR(20)  DEFAULT NULL COMMENT '手机号',
    `avatar`      VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
    `status`      TINYINT      NOT NULL DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
    `created_at`  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updated_at`  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_username` (`username`),
    UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

对应的实体类 User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String username;
    private String email;
    private String phone;
    private String avatar;
    private Integer status;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

MyBatis的Mapper XML文件 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="com.example.userapi.repository.UserRepository">

    <resultMap id="BaseResultMap" type="User">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="email" property="email" />
        <result column="phone" property="phone" />
        <result column="avatar" property="avatar" />
        <result column="status" property="status" />
        <result column="created_at" property="createdAt" />
        <result column="updated_at" property="updatedAt" />
    </resultMap>

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (username, email, phone, avatar, status)
        VALUES (#{username}, #{email}, #{phone}, #{avatar}, #{status})
    </insert>

    <select id="findById" resultMap="BaseResultMap">
        SELECT * FROM user WHERE id = #{id}
    </select>

    <select id="findPage" resultMap="BaseResultMap">
        SELECT * FROM user
        ORDER BY created_at DESC
        LIMIT #{offset}, #{limit}
    </select>

    <select id="count" resultType="long">
        SELECT COUNT(*) FROM user
    </select>

    <update id="update">
        UPDATE user
        SET username = #{username},
            email = #{email},
            phone = #{phone},
            avatar = #{avatar},
            status = #{status}
        WHERE id = #{id}
    </update>

    <delete id="deleteById">
        DELETE FROM user WHERE id = #{id}
    </delete>
</mapper>

对应的 UserRepository.java 接口:

@Mapper
public interface UserRepository {
    int insert(User user);
    User findById(Long id);
    List<User> findPage(@Param("offset") int offset, @Param("limit") int limit);
    long count();
    int update(User user);
    int deleteById(Long id);
}

3. Controller-Service-Repository三层架构

三层架构是Spring Boot项目中最核心的组织方式,每一层各有分工:

层次 职责 注解 注意事项
Controller 接收请求、参数校验、调用Service、返回响应 @RestController / @RequestMapping 只做路由和校验,不做业务逻辑
Service 业务逻辑编排、事务管理 @Service / @Transactional 接口+实现类分离,便于测试
Repository 数据库CRUD操作 @Mapper 只做数据访问,不做业务判断

UserController.java 完整示例:

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户CRUD相关接口")
public class UserController {

    private final UserService userService;

    @PostMapping
    @Operation(summary = "创建用户")
    public Result<UserVO> create(@Valid @RequestBody CreateUserRequest request) {
        UserVO userVO = userService.createUser(request);
        return Result.success(userVO);
    }

    @GetMapping("/{id}")
    @Operation(summary = "查询用户详情")
    public Result<UserVO> getById(@PathVariable Long id) {
        UserVO userVO = userService.getUserById(id);
        return Result.success(userVO);
    }

    @GetMapping
    @Operation(summary = "分页查询用户列表")
    public Result<PageResult<UserVO>> list(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        PageResult<UserVO> pageResult = userService.listUsers(page, size);
        return Result.success(pageResult);
    }

    @PutMapping("/{id}")
    @Operation(summary = "更新用户信息")
    public Result<UserVO> update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        UserVO userVO = userService.updateUser(id, request);
        return Result.success(userVO);
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户")
    public Result<Void> delete(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success();
    }
}

UserServiceImpl.java 业务逻辑实现:

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    @Override
    @Transactional
    public UserVO createUser(CreateUserRequest request) {
        // 1. 校验用户是否已存在
        User existingUser = userRepository.findByUsername(request.getUsername());
        if (existingUser != null) {
            throw new BusinessException("用户名已存在");
        }

        // 2. 创建实体并保存
        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setPhone(request.getPhone());
        user.setStatus(1);
        userRepository.insert(user);

        // 3. 转换为VO返回
        return UserVO.fromEntity(user);
    }

    @Override
    public UserVO getUserById(Long id) {
        User user = userRepository.findById(id);
        if (user == null) {
            throw new ResourceNotFoundException("用户不存在");
        }
        return UserVO.fromEntity(user);
    }

    @Override
    public PageResult<UserVO> listUsers(int page, int size) {
        int offset = (page - 1) * size;
        List<User> users = userRepository.findPage(offset, size);
        long total = userRepository.count();
        List<UserVO> voList = users.stream()
                .map(UserVO::fromEntity)
                .collect(Collectors.toList());
        return new PageResult<>(voList, total, page, size);
    }

    @Override
    @Transactional
    public UserVO updateUser(Long id, UpdateUserRequest request) {
        User user = userRepository.findById(id);
        if (user == null) {
            throw new ResourceNotFoundException("用户不存在");
        }
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setPhone(request.getPhone());
        userRepository.update(user);
        return UserVO.fromEntity(user);
    }

    @Override
    @Transactional
    public void deleteUser(Long id) {
        User user = userRepository.findById(id);
        if (user == null) {
            throw new ResourceNotFoundException("用户不存在");
        }
        userRepository.deleteById(id);
    }
}

4. DTO与VO设计模式

在实际项目中,直接将实体类 User 暴露给前端是非常危险的做法。正确的做法是使用DTO(数据传输对象)和VO(视图对象)进行隔离:

  • DTO:接收前端传入的请求参数,通常配合 javax.validation 注解做参数校验
  • VO:返回给前端的响应数据,只包含前端需要看到的字段

CreateUserRequest.java(请求DTO):

@Data
@Schema(description = "创建用户请求")
public class CreateUserRequest {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 50, message = "用户名长度3-50个字符")
    @Schema(description = "用户名", example = "zhangsan")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String email;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    @Schema(description = "手机号", example = "13800138000")
    private String phone;
}

UserVO.java(响应VO):

@Data
@Schema(description = "用户信息")
public class UserVO {

    @Schema(description = "用户ID")
    private Long id;

    @Schema(description = "用户名")
    private String username;

    @Schema(description = "邮箱")
    private String email;

    @Schema(description = "手机号")
    private String phone;

    @Schema(description = "头像URL")
    private String avatar;

    @Schema(description = "状态:0-禁用 1-启用")
    private Integer status;

    @Schema(description = "创建时间")
    private String createdAt;

    public static UserVO fromEntity(User user) {
        if (user == null) return null;
        UserVO vo = new UserVO();
        vo.setId(user.getId());
        vo.setUsername(user.getUsername());
        vo.setEmail(user.getEmail());
        vo.setPhone(user.getPhone());
        vo.setAvatar(user.getAvatar());
        vo.setStatus(user.getStatus());
        vo.setCreatedAt(user.getCreatedAt()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        return vo;
    }
}

设计原则:永远不要直接将 Entity 返回给前端。通过 VO 做一层转换,你可以安全地隐藏敏感字段(如密码)、格式化时间、拼接展示字段,而不影响数据库模型。

5. 统一异常处理

没有统一异常处理的API就像没有安全网的高空作业。使用 @RestControllerAdvice 可以集中处理所有异常:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /** 参数校验失败 */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidation(MethodArgumentNotValidException ex) {
        String msg = ex.getBindingResult().getFieldErrors().stream()
                .map(e -> e.getField() + ": " + e.getDefaultMessage())
                .collect(Collectors.joining("; "));
        return Result.error(400, msg);
    }

    /** 业务异常 */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException ex) {
        return Result.error(400, ex.getMessage());
    }

    /** 资源不存在 */
    @ExceptionHandler(ResourceNotFoundException.class)
    public Result<Void> handleNotFound(ResourceNotFoundException ex) {
        return Result.error(404, ex.getMessage());
    }

    /** 未预期的异常 */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleUnknown(Exception ex) {
        log.error("未预期异常", ex);
        return Result.error(500, "服务器内部错误");
    }
}

统一响应封装类 Result.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private int code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }

    public static <T> Result<T> success() {
        return new Result<>(200, "success", null);
    }

    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }
}

这种设计保证了所有API响应都遵循统一的JSON格式,前端可以根据 code 字段做全局拦截处理:

// 成功响应
{ "code": 200, "message": "success", "data": { ... } }

// 失败响应
{ "code": 400, "message": "用户名不能为空", "data": null }

// 未找到
{ "code": 404, "message": "用户不存在", "data": null }

6. Swagger / Knife4j接口文档

没有文档的API很难被他人使用。Knife4j 是 Swagger 的增强版,提供更友好的UI界面。配置方式如下:

@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("用户管理系统 API")
                        .version("1.0.0")
                        .description("基于Spring Boot + MyBatis的用户管理RESTful API"));
    }
}

启动项目后访问 http://localhost:8080/doc.html 即可看到可视化的接口文档页面。Knife4j的优势包括:

  • 在线调试:直接在文档页面填写参数并发送请求
  • 全局参数:支持统一添加Token等认证参数
  • 分组展示:按Controller自动分组,结构清晰
  • 导出功能:支持导出 Markdown / OpenAPI 格式

配合 @Operation@Schema 注解,可以让生成的文档更加完善。这些注解已经在前面Controller和DTO的代码中使用过。

完整的API端点一览:

HTTP方法 路径 描述 请求体 响应
POST /api/users 创建用户 CreateUserRequest UserVO
GET /api/users/{id} 查询用户详情 - UserVO
GET /api/users 分页查询用户列表 page, size (query) PageResult<UserVO>
PUT /api/users/{id} 更新用户信息 UpdateUserRequest UserVO
DELETE /api/users/{id} 删除用户 - -

7. 部署与发布

完成开发后,常用的部署方式有两种:

7.1 Jar包部署(传统方式)

# 构建项目
mvn clean package -DskipTests

# 启动服务
java -jar target/user-api-1.0.0.jar \
    --spring.profiles.active=prod \
    --server.port=8080

7.2 Docker容器化部署(推荐方式)

# Dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/user-api-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]
# docker-compose.yml
version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: user_api
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/user_api?useUnicode=true&characterEncoding=utf-8
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root123
    depends_on:
      - mysql

volumes:
  mysql_data:

7.3 生产环境优化建议

  • 配置外置化:将数据库配置、日志级别等通过环境变量或配置中心管理
  • 健康检查:引入 spring-boot-starter-actuator 添加 /actuator/health 端点
  • 日志收集:使用Logback + ELK或Loki集中管理日志
  • 限流熔断:引入 Sentinel 或 Resilience4j 做流量防护
  • CI/CD:配置GitHub Actions或Jenkins自动化构建与部署

从项目初始化到完整部署,一个标准的RESTful API服务需要关注的点确实不少。但只要遵循本文介绍的架构模式 -- 清晰的分层设计、严谨的DTO/VO隔离、统一的异常处理和规范的接口文档,你就能构建出易于维护、扩展和协作的后端服务。这套模式在淘宝、京东等大型互联网公司中被广泛采用,也是面试中考察后端架构能力的高频考点。

建议你在实际项目中反复实践这些模式,逐渐形成自己的架构风格。代码的优雅不在于用了多复杂的技术,而在于每个细节都经得起推敲。