{YouAsk 诞生记 2} Spring Boot项目接入MyBatis Plus

为了简化开发、提高效率,YouAsk 项目采用了 MyBatis-Plus 作为数据层的持久层框架,提供了主键自动生成、内置代码生成器、分页插件等强大功能。
项目地址:https://gitee.com/obsidian15/youask/tree/v0.2/
Tag:v2.0

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

YouAsk使用MyBatis Plus作为项目数据层的持久层框架,相比于MyBatis提供很多增强功能,比较有用的增强功能包括

  • 支持主键自动生成
  • 内置代码生成器
  • 内置分页插件
  • 内置全局拦截插件

这些功能在后续项目中都会用带,我也会逐一介绍如何开发和使用。

MyBaits-Plus 引用

  1. 父模块pom中增加mybatis-plus的引用
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.12</version>
</dependency>
  1. youask-admin-api项目的pom中增加mybatis-plus的引用,但是不需要指定version
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
  1. application.yaml文件中增加数据库连接配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/youask?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: p@ssword!

# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. YouAskAdminApiApplication.java启动类上增加MapperScan注释
package com.obsidian.youask;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.obsidian.youask.mapper")
public class YouAskAdminApiApplication {

	public static void main(String[] args) {
		SpringApplication.run(YouAskAdminApiApplication.class, args);
	}

}

开发User数据库访问层和服务层

  1. 在介绍具体代码开发前,我先介绍一下API的层次结构

项目从上至下分为6层,分别为Web层、Controller层、Service层、Mapper层和数据库

  • Web层是页面展示层
  • Controller层提供对外的API访问请求,请求主要来自Web、App或者小程序
  • Service是服务层,主要包含业务处理逻辑
  • Mapper是数据持久层,主要生产SQL语句实现数据库操作
  • Database是数据库,用于存储数据

服务分层

我们将按照上面的分层,逐层搭建

  1. 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="com.obsidian.youask.mapper.UserMapper">

</mapper>

MyBatis Plus需要编写mapper来实现数据库的操作,mapper由两部分组成,mapper接口定义和mapper的实现。其中mapper的实现可以直接在mapper接口定义的方法上,用注释(annotation)的方式定义,也可以使用mapper.xml的形式,使用xml格式定义。使用下来觉得,使用xml的方式虽然比较啰嗦,但是比较好用,直接写的是SQL语句,更加直观。个人比较推荐的做法是,一些常用的增、删、改和简单参数的查询直接用service定义即可,xml中主要包含复杂的查询,可以直接使用SQL语句实现。

  1. mapper文件夹中增加UserMapper.java
package com.obsidian.youask.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.obsidian.youask.model.entity.SysUser;

public interface UserMapper extends BaseMapper<SysUser> {

}
  1. service文件夹中增加IUserService.java接口
package com.obsidian.youask.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.obsidian.youask.model.entity.SysUser;

public interface IUserService extends IService<SysUser> {

}
  1. service/impl文件夹中增加UserService.java的实现
package com.obsidian.youask.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.obsidian.youask.model.entity.SysUser;

public interface IUserService extends IService<SysUser> {

}
  1. DBeaver连接MySQL数据,创建sys_user数据表
CREATETABLE`sys_user` (
	`user_id`bigintNOT NULLAUTO_INCREMENT COMMENT '用户ID',
	`user_name`varchar(20)NOT NULL COMMENT '用户账号',
	`password`varchar(32)NOT NULL COMMENT '加密后密码',
	`dept_id`bigintNOT NULL COMMENT '部门ID',
	`employee_no`varchar(20)CHARACTERSET utf8mb4COLLATE utf8mb4_0900_ai_ciDEFAULT NULL COMMENT '工号',
	`position`intDEFAULT NULL COMMENT '职位ID',
	`name`varchar(50)DEFAULT NULL COMMENT '姓名',
	`nick_name`varchar(50)NOT NULL COMMENT '用户昵称',
	`email`varchar(50)DEFAULT NULL COMMENT '用户邮箱地址',
	`phone_number`varchar(20)DEFAULT NULL COMMENT '手机号码',
	`gender`intDEFAULT NULL COMMENT '性别',
	`avatar`varchar(100)DEFAULT NULL COMMENT '用户头像URL',
	`last_login_time`datetimeDEFAULT NULL COMMENT '最后登陆时间',
	`last_login_ip`varchar(15)DEFAULT NULL COMMENT '最后登陆IP',
	`pwd_last_update_time`datetimeDEFAULT NULL COMMENT '密码最后更新时间',
	`status`intDEFAULT NULL COMMENT '状态',
	`is_deleted`intNOT NULLDEFAULT '0' COMMENT '是否删除',
	`updated_by`bigintDEFAULT NULL COMMENT '更新人ID',
	`updated_at`datetimeDEFAULT NULL COMMENT '更新时间',
	`created_by`bigintNOT NULL COMMENT '创建人ID',
	`created_at`datetimeNOT NULLDEFAULTCURRENT_TIMESTAMP COMMENT '创建时间',
	PRIMARYKEY (`user_id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci;
  1. 修改UserController中的getAll方法,实现从sys_user表中获取所有数据
@Autowired
private IUserService userService;

@GetMapping()
public YaResult<List<SysUser>> getAll() {
    return YaResult.success(userService.list());
}

@Autowired是 Spring 框架中的一个注解,主要用于实现依赖注入(Dependency Injection, DI)。它的核心作用是自动装配,即让 Spring 容器自动为类中的属性、构造方法或 setter 方法注入所需的依赖对象,无需手动创建和赋值。

简单说就是对象不需要自己new了,SpringBoot帮助我们自动new了个对象,比如这里的IUserService是个接口,具体的实现是UserService,SpringBoot就会自动用UserService类来实例化。如果IUserService这个接口有多个类实现了,可以使用@Primary,@Qualifier来指定具体使用哪个实现。

  1. 启动项目调试,在数据库里增加几条数据,在OpenAPI页面中打开getAll方法,在调试面板中点击“发送”,正常情况下就能看到从数据库获取到的结果。

getAll方法调试
这样从API获取请求,到数据库之间的打通了。

实现逻辑删除

从MyBatis输出的日志中可以看到实际生成的查询语句。

MyBatis 生成的 SQL 语句
生成的SQL语句比较简单,主要是字段,没有包含where语句。从User实际应用来看,至少应该包含2个默认的过滤

  • is_deleted=0,is_deleted=1表示记录逻辑删除
  • tenant_id租户过滤,这个默认应该获取当前登陆用户所在的租户,并添加where过滤(后续会加)

逻辑删除有2种实现方法,第一种是使用mybatis的QueryWrapper实现,手动增加is_deleted字段不等于0的判断,这样做的问题是所有的查询都需要添加这样语句。第二种实现方法是使用mybatis plus提供的逻辑删除

逻辑删除是一种优雅的数据管理策略,它通过在数据库中标记记录为“已删除”而非物理删除,来保留数据的历史痕迹,同时确保查询结果的整洁性。MyBatis-Plus 提供了便捷的逻辑删除支持,使得这一策略的实施变得简单高效。

Mybatis Plus实现逻辑删除也很简单,在YaEntity类中增加@TableLogic的注释。0表示记录正常,1表示记录删除。

/** 
 * 删除标志
 * 0:正常
 * 1:删除
*/
@Schema(description = "逻辑删除标志")
@TableLogic(value = "0", delval = "1")
private Integer isDeleted;

在OpenAPI上重新发送请求,再看日志,可以发现增加了WHERE is_deleted=0,说明逻辑删除生效了。

SQL语句中包含了逻辑删除条件

实现添加和更新

  1. UserController中增加add方法用户新增用户
@PostMapping()
public YaResult<SysUser> add(@Valid @RequestBody SysUser user) {
    if (userService.save(user)) {
        return YaResult.success(user);
    }
    return YaResult.failed("Add user failed");
}
  1. 浏览器打开OpenAPI页面,定位到add方法,在body中输入完整的体
{
  "status": 0,
  "isDeleted": 0,
  "createdBy": 0,
  "createdAt": "",
  "updatedBy": 0,
  "updatedAt": "",
  "userId": 0,
  "userName": "ammey",
  "password": "123567",
  "deptId": 0,
  "employeeNo": "",
  "position": 0,
  "nickName": "",
  "name": "",
  "gender": 1,
  "email": "",
  "phoneNumber": "",
  "avatar": "",
  "lastLoginTime": "",
  "lastLoginIp": "",
  "pwdLastUpdateTime": ""
}

查看请求的SQL语句,发现辅助字段,比如status、isDeleted、createdBy、createdAt这些字段,如果再请求时不填写,新建对象是会报错的。我们可以用Mybatis Plus的自动填充字段功能,在insert或者update时,自动填充辅助字段。

  1. 在YaEntity类中,需要使用 @TableField 注解来标记哪些字段需要自动填充,并指定填充的策略。
@Data
public abstract class YaEntity implements Serializable {
    /** 
     * 状态
     * 0:停用
     * 1:正常
     */
    @Schema(description = "状态")
    @TableField(fill = FieldFill.INSERT)
    private Integer status;

    /** 
     * 删除标志
     * 0:正常
     * 1:删除
     */
    @Schema(description = "逻辑删除标志")
    @TableLogic(value = "0", delval = "1")
    @TableField(fill = FieldFill.INSERT)
    private Integer isDeleted;

    /**
     * 创建人ID
     */
    @Schema(description = "创建人ID")
    @TableField(fill = FieldFill.INSERT)
    private Integer createdBy;

    /**
     * 创建时间
     */
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;

    /**
     * 更新人ID
     */
    @Schema(description = "更新人ID")
    @TableField(fill = FieldFill.UPDATE)
    private Integer updatedBy;

    /**
     * 更新时间
     */
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updatedAt;
}
  1. config文件夹中创建EntityMetaObjectHandler.java,主要用来指定每个字段分别在insert和update的时候如何
package com.obsidian.youask.config;

import java.time.LocalDateTime;

import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

@Component
public class EntityMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "status", Integer.class, 1);
        this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
        this.strictInsertFill(metaObject, "createdBy", Long.class, 1L);
        this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "updatedBy", Long.class, 1L);
        this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
    }
}
  1. 重新发送请求,删除请求数据中的status、isDeleted、createdBy、createdAt、updatedBy、updatedAt字段,可以看到生成的SQL语句中自动填充了数据。
{
  "userId": 0,
  "userName": "ammey",
  "password": "123567",
  "deptId": 0,
  "employeeNo": "",
  "position": 0,
  "nickName": "",
  "name": "",
  "gender": 1,
  "email": "",
  "phoneNumber": "",
  "avatar": "",
  "lastLoginTime": "",
  "lastLoginIp": "",
  "pwdLastUpdateTime": ""
}

执行add方法生成的SQL语句

实现queryByCondition方法

之所以把queryByCondition方法单独提出来讲,主要是这个方法相对来说比较复杂,它不包含具体业务(因为这个是通用方法,按照输入的查询条件,生成SQL的WHERE查询条件),但是流程覆盖从controller到service到mapper到database。

queryByCondition方法的作用是根据参数中的condition,自动拼接where条件,实现复杂条件的查询,主要用在列表页面的展示和查询功能上。

  1. resources/mapper文件夹中创建EntityMapper.xml,对应YaEntity.java对象

EntityMapper中的resultMap和Sql都是作为其他mapper的引用,并不直接执行,这个也是我觉得mapper.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.obsidian.youask.mapper.EntityMapper">
    <resultMap id="EntityMap" type="com.obsidian.youask.model.YaEntity">
        <result column="status" property="status" />
        <result column="is_deleted" property="isDeleted" />
        <result column="created_by" property="createdBy" />
        <result column="created_at" property="createdAt" />
        <result column="updated_by" property="updatedBy" />
        <result column="updated_at" property="updatedAt" />
    </resultMap>
    <sql id="entityCondtion">
        <if test="condition.status != null">
            and status = #{condition.status}
        </if>
        <if test="condition.statuses != null and condition.statuses.size() > 0">
            and status in
            <foreach collection="condition.statuses" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
        <if test="condition.createdBy != null">
            and created_by = #{condition.createdBy}
        </if>
        <if test="condition.createUserIds != null and condition.createUserIds.size() > 0">
            and created_by in
            <foreach collection="condition.createUserIds" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
        <if test="condition.startCreateTime != null">
            and created_at &gt;= #{condition.startCreateTime}
        </if>
        <if test="condition.endCreateTime != null">
            and created_at &lt; #{condition.endCreateTime}
        </if>
        <if test="condition.updatedBy != null">
            and updated_by = #{condition.updatedBy}
        </if>
        <if test="condition.updateUserIds != null and condition.updateUserIds.size() > 0">
            and updated_by in
            <foreach collection="condition.updateUserIds" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
        <if test="condition.startUpdateTime != null">
            and updated_at &gt;= #{condition.startUpdateTime}
        </if>
        <if test="condition.endUpdateTime != null">
            and updated_at &lt; #{condition.endUpdateTime}
        </if>
    </sql>
</mapper>

EntityMapper定义了通用的查询条件,包活以下的字段:

  • status - 单状态查询
  • statuses - 状态列表查询
  • createdBy - 单创建人Id查询
  • createUserIds - 创建人Id列表查询
  • startCreateTime,endCreateTime - 开始、结束创建时间查询
  • updatedBy - 单更新人Id查询
  • updateUserIds - 更新人Id列表查询
  • startUpdateTime,endUpdateTime - 开始、结束更新时间查询
  1. resources/mapper文件夹中创建UserMapper.xml,对应SysUser.java对象
<?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.obsidian.youask.mapper.UserMapper">
    <resultMap id="UserMap" 
               extends="com.obsidian.youask.mapper.EntityMapper.EntityMap"
               type="com.obsidian.youask.model.entity.SysUser">
        <id column="user_id" property="userId"/>
        <result column="user_name" property="userName"/>
        <result column="password" property="password"/>
        <result column="dept_id" property="deptId"/>
        <result column="employee_no" property="employeeNo"/>
        <result column="position" property="position"/>
        <result column="nick_name" property="nickName"/>
        <result column="name" property="name"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="avatar" property="avatar"/>
        <result column="last_login_time" property="lastLoginTime"/>
        <result column="last_login_ip" property="lastLoginIp"/>
        <result column="pwd_last_update_time" property="pwdLastUpdateTime"/>
    </resultMap>

    <select id="selectByCondition" resultMap="UserMap" parameterType="java.util.Map">
        select * from sys_user where is_deleted = 0
        <if test="condition.userId != null">
            and user_id = #{condition.userId}
        </if>
        <if test="condition.userIds != null and condition.userIds.size() > 0">
            and user_id in
            <foreach collection="condition.userIds" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
        <if test="condition.userName != null and condition.userName != ''">
            and user_name like concat('%', #{condition.userName}, '%')
        </if>
        <if test="condition.deptId != null">
            and dept_id = #{condition.deptId}
        </if>
        <if test="condition.deptIds != null and condition.deptIds.size() > 0">
            and dept_id in
            <foreach collection="condition.deptIds" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
        <include refid="com.obsidian.youask.mapper.EntityMapper.EntityCondtion" />
    </select>
</mapper>
  • UserMapper中的resultMap从EntityMap扩展
  • UserMapper中selectByCondition引用了EntityMap的EntityCondtion
  1. IUserMapper中增加selectByCondition方法的定义
package com.obsidian.youask.mapper;

import java.util.Map;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.obsidian.youask.model.entity.SysUser;

public interface UserMapper extends BaseMapper<SysUser> {
    IPage<SysUser> selectByCondition(IPage<SysUser> page, Map<String, Object> condition);
}

IUserMapper定义了selectByCondition方法,输入参数和输出参数都为IPage对象,IPage对象是MyBatis Plus的内部模块,用于在数据库查询时提供分页能力。

MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。

这里的条件参数使用了Map<String, Object>类型,主要是因为后续mapper希望通过Mybatis Generator自动生成,根据不同表生成Condition对象不是很方便,所以这里选择了直接使用map方式。在很多SpringBoot项目标准中还是不建议使用这种方式的。

  1. IUserService的改造
package com.obsidian.youask.service;

import java.util.Map;

import com.baomidou.mybatisplus.extension.service.IService;
import com.obsidian.youask.model.YaPageResult;
import com.obsidian.youask.model.entity.SysUser;

public interface IUserService extends IService<SysUser> {

    public YaPageResult<SysUser> queryByCondition(Map<String, Object> condition, Integer pageIndex, Integer pageSize);
}
  1. UserServiceImpl的改造
package com.obsidian.youask.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.obsidian.youask.mapper.UserMapper;
import com.obsidian.youask.model.YaPageResult;
import com.obsidian.youask.model.entity.SysUser;
import com.obsidian.youask.service.IUserService;

import java.util.Map;

import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, SysUser> implements IUserService {

    @Override
    public YaPageResult<SysUser> queryByCondition(Map<String, Object> condition, Integer pageIndex, Integer pageSize) {
        IPage<SysUser> page = new Page<>(pageIndex, pageSize);
        IPage<SysUser> result = baseMapper.selectByCondition(page, condition);
        return YaPageResult.success(result.getRecords(), pageIndex, pageSize, result.getTotal());
    }

}
  1. UserController改造
@PostMapping("query")
public YaPageResult<SysUser> queryByCondition(@RequestBody Map<String, Object> condition, 
    @RequestParam(defaultValue = YaConst.DEFAULT_PAGE_INDEX) Integer pageIndex, 
    @RequestParam(defaultValue = YaConst.DEFAULT_PAGE_SIZE) Integer pageSize) {
        return userService.queryByCondition(condition, pageIndex, pageSize);
}
  1. 配置Mybatis Plus分页插件

Mybatis Plus分页插件

在父项目pom项目中增加mybatis-plus-jsqlparser的引用

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
    <version>3.5.12</version>
</dependency>

在youask-admin-api项目增加mybatis-plus-jsqlparser的引用

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>

config目录中创建MybatisPlusConfig配置文件

package com.obsidian.youask.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

@Configuration
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 
        return interceptor;
    }
}
  1. 调试queryByCondition方法

在浏览器中打开OpenAPI页面,定位到queryByCondition方法,在URL中增加pageIndex和pageSize参数

/users/query?pageIndex=1&pageSize=10

设置body参数

{
    "userIds": [1,2]
}

点击发送后可以看到查询结果,其中包含了page对象,保存了分页信息

queryByCondition方法执行结果

我们到后台看一下生成的SQL语句,可以看到查询条件user_id in 已经包含了,并且包含了LIMIT的限制用于实现分页。

queryByCondition方法生成的SQL语句

完善UserController的方法

package com.obsidian.youask.admin.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.obsidian.youask.admin.YaConst;
import com.obsidian.youask.admin.service.IUserService;
import com.obsidian.youask.common.model.YaPageResult;
import com.obsidian.youask.common.model.YaResult;
import com.obsidian.youask.common.model.YaResultCode;
import com.obsidian.youask.common.model.entity.SysUser;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;

@ApiSupport(author = YaConst.AUTHOR)
@Tag(name = "UserController", description = "用户服务")
@Slf4j
@RestController
@RequestMapping("users")
public class UserController {

    @Autowired
    private IUserService userService;

    /**
     * 获取所有用户
     * @return 用户列表
     */
    @GetMapping()
    public YaResult<List<SysUser>> getAll() {
        return YaResult.success(userService.list());
    }

    /**
     * 根据用户ID获取用户详情
     * @param userId 用户ID
     * @return 用户详情
     */
    @GetMapping("{userId}")
    public YaResult<SysUser> getById(@PathVariable Long userId) {
        return YaResult.success(userService.getById(userId));
    }

    /**
     * 根据条件查询用户列表
     * @param condition 查询条件对象
     * @return 用户列表
     */
@PostMapping("query")
public YaPageResult<SysUser> queryByCondition(@RequestBody Map<String, Object> condition, 
    @RequestParam(defaultValue = YaConst.DEFAULT_PAGE_INDEX) Integer pageIndex, 
    @RequestParam(defaultValue = YaConst.DEFAULT_PAGE_SIZE) Integer pageSize) {
        return userService.queryByCondition(condition, pageIndex, pageSize);
} 

    /**
     * 添加用户
     * @param user 待添加的用户对象
     * @return 添加后的用户对象
     */
    @PostMapping()
    public YaResult<SysUser> add(@Valid @RequestBody SysUser user) {
        if (userService.save(user)) {
            return YaResult.success(user);
        }
        return YaResult.failed(YaConst.MSG_TMPL_ADD_FAILED, "user");
    }

    /**
     * 根据用户ID更新用户信息
     * @param userId 用户ID
     * @param user 待更新的用户对象
     * @return 更新后的用户对象
     */
    @PutMapping("{userId}")
    public YaResult<SysUser> modifyById(@PathVariable Long userId, @RequestBody SysUser user) {
        user.setUserId(userId);
        if (userService.updateById(user)) {
            return YaResult.success(user);
        }
        return YaResult.failed(YaConst.MSG_TMPL_UPDATE_FAILED, "user", userId);
    }

    /**
     * 停用用户
     * @param userId 用户ID
     * @return 更新后的用户对象
     */
    @PutMapping("{userId}/deactive")
    public YaResult<SysUser> deactivateById(@PathVariable Long userId) {
        return updateStatus(userId, YaConst.STATUS_DEACTIVE);
    }

    /**
     * 激活用户
     * @param userId 用户ID
     * @return 更新后的用户对象
     */
    @PutMapping("{userId}/active")
    public YaResult<SysUser> activateById(@PathVariable Long userId) {
        return updateStatus(userId, YaConst.STATUS_ACTIVE);
    }

    /**
     * 更新用户状态
     * @param userId 用户ID
     * @param status 状态
     * @return 更新后的用户对象
     */
    private YaResult<SysUser> updateStatus(Long userId, Integer status) {
        SysUser user = userService.getById(userId);
        if (user == null) {
            return YaResult.failed(YaResultCode.NOT_FOUND, YaConst.MSG_TMPL_NOT_FOUND, "User", userId);
        }
        user.setStatus(status);
        if (userService.updateById(user)) {
            return YaResult.success(user);
        }
        return YaResult.failed(YaConst.MSG_TMPL_UPDATE_FAILED, "user", userId);
    }

    /**
     * 根据用户ID删除用户
     * @param userId 用户ID
     * @return 删除结果
     */
    @DeleteMapping("{userId}")
    public YaResult<Boolean> removeById(@PathVariable Long userId) {
        if (userService.removeById(userId)) {
            return YaResult.success(true);
        }
        return YaResult.failed(YaConst.MSG_TMPL_DELETE_FAILED, "user", userId);
    }
}

一般的Controller中会包含一些通用的API,这些API不包含具体的业务逻辑,是针对数据库中数据的CRUD操作,包括以下方法:

  • getAll - 获取所有(非删除)数据
  • getById - 根据Id获取数据
  • queryByCondition - 根据条件获取数据
  • add - 添加数据
  • modifyById - 根据Id修改数据
  • activateById - 根据Id启用数据
  • deactivateById - 根据Id停用数据
  • removeById - 根据Id删除数据

这说明一下我自己的一个命名规则:
Controller层、Service层更贴近业务,把业务数据看成是列表的形式,CRUD的命名使用add、remove、modify、get或者query。get表达简单条件,可以包含缓存,获取开销较低;query需要有复杂查询,获取开销较高;
Mapper层贴近数据库,是数据库的抽象层,CRUD的命名使用insert、delete、update和select;
方法命名会包含一些特定的格式,比如:
XXXById - 根据Id(主键)进行操作
XXXByCode - 根据Code进行操作
XXXByCondition - 根据复杂条件进行操作
XXXAsVO - 返回数据转换为VO对象
XXXWithXXX - 返回复杂的嵌套对象

后续工作

考虑到从API到数据库中的层次较多,同时数据表也会很多,每张数据表都需要这样逐层开发将会很费时间,所有我们会使用MyBatis Code Generator来自动生成这些模块话的代码。

YouAsk系列目录:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值