SpringBoot02

1. 学习目标(了解)

2. Mybatis整合&数据访问(操作)

使用SpringBoot开发企业项目时,持久层数据访问是前端页面数据展示的基础,SpringBoot支持市面上常见的关系库产品(Oracle,Mysql,SqlServer,DB2等)对应的相关持久层框架,当然除了对于关系库访问的支持,也支持当下众多的非关系库(Redis,Solr,MongoDB等)数据访问操作,这里主要介绍SpringBoot集成Mybatis并实现持久层数据基本增删改查操作。

2.1 SpringBoot 整合Mybatis

环境整合配置
  • Idea 下创建Maven 普通工程 springboot_mybatis

  • pom.xml 添加核心依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
​
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>
<dependencies>
    <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>2.1.1</version>
    </dependency>
    <!-- springboot分页插件 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.13</version>
    </dependency>
​
    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
      
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.5</version>
    </dependency>
</dependencies>
​
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
  • application.yml 整合配置

## 端口号
server:
  port: 9999
​
## 数据源配置
spring:
  datasource:
    type: com.mchange.v2.c3p0.ComboPooledDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password:
​
## mybatis 配置
mybatis:
  mapper-locations: classpath:/mappers/*.xml
  type-aliases-package: org.example.springboot.vo
  configuration:
    ## 下划线转驼峰配置
    map-underscore-to-camel-case: true
​
## pageHelper
pagehelper:
  helper-dialect: mysql
​
#显示dao 执行sql语句
logging:
  level:
    org:
      example:
        springboot:
          dao: debug
源代码添加
  • 在org.example.springboot.vo 包中,添加User.java

package org.example.springboot.vo;
​
public class User {
    private Integer id;
    private String userName;
    private Integer password;
    private String realName;
    
    public User() {
    }
​
    public User(Integer id, String userName, Integer password, String realName) {
        this.id = id;
        this.userName = userName;
        this.password = password;
        this.realName = realName;
    }
​
    /**
     * 获取
     * @return id
     */
    public Integer getId() {
        return id;
    }
​
    /**
     * 设置
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }
​
    /**
     * 获取
     * @return userName
     */
    public String getUserName() {
        return userName;
    }
​
    /**
     * 设置
     * @param userName
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }
​
    /**
     * 获取
     * @return password
     */
    public Integer getPassword() {
        return password;
    }
​
    /**
     * 设置
     * @param password
     */
    public void setPassword(Integer password) {
        this.password = password;
    }
​
    /**
     * 获取
     * @return realName
     */
    public String getRealName() {
        return realName;
    }
​
    /**
     * 设置
     * @param realName
     */
    public void setRealName(String realName) {
        this.realName = realName;
    }
​
    public String toString() {
        return "User{id = " + id + ", userName = " + userName + ", password = " + password + ", realName = " + realName + "}";
    }
}
  • 数据库User

  • Dao层接口方法定义

org.example.springboot.dao 包下创建UserDao.java 接口声明查询方法

package org.example.springboot.dao;
​
import org.example.springboot.vo.User;
​
public interface UserMapper  {
    // 根据用户名查询用户记录
    User queryUserByUserName(String userName);
}
  • SQL映射文件添加

    resources/mappers 目录下添加UserMapper.xml 配置查询statetment

<?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.springboot.dao.UserMapper">
    <select id="queryUserByUserName" parameterType="string" resultType="org.example.springboot.vo.User">
        select
            id,username,password,realname
        from users
        where username=#{userName}
    </select>
</mapper>
  • 添加service 、controller 对应代码

UserService.java

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
​
    public User queryUserByUserName(String userName){
        return userMapper.queryUserByUserName(userName);
    }
}

UserController.java

@RestController
public class UserController {
​
    @Resource
    private UserService userService;
​
​
    @GetMapping("user/{userName}")
    public User queryUserByUserName(@PathVariable String userName){
        return userService.queryUserByUserName(userName);
    }
}
  • 添加应用启动入口

@SpringBootApplication
@MapperScan("org.example.springboot.dao")
public class Starter {
​
    public static void main(String[] args) {
        SpringApplication.run(Starter.class);
    }
}
启动测试

运行Starter main方法,启动应用浏览器测试查询

后端日志打印效果:

2.2 SpringBoot数据访问操作

完成SpringBoot 与Mybatis 集成后,接下来以用户表为例实现一套用户模块基本数据维护。

接口方法 & Sql映射文件
  • UserDao 接口方法定义

UserDao 接口添加数据访问基本方法

public interface UserMapper  {

    public User queryById(Integer id);

    User queryUserByUserName(String userName);

    public int save(User user);

    public int update(User user);

    public List<User> selectByParams(UserQuery userQuery);

}
  • 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.springboot.dao.UserMapper">
    <select id="queryById" parameterType="int" resultType="org.example.springboot.vo.User">
        select *
        from users
        where id = #{id,jdbcType=INTEGER}
    </select>

    <select id="queryUserByUserName" parameterType="string" resultType="org.example.springboot.vo.User">
        select *
        from users
        where username=#{userName}
    </select>
    <insert id="save" parameterType="org.example.springboot.vo.User" useGeneratedKeys="true" keyProperty="id">
        insert into users(id,username,password) values(#{id},#{userName},#{password})
    </insert>
    <update id="update" parameterType="org.example.springboot.vo.User">
        update users set username =#{userName},password=#{password}
        where id = #{id}
    </update>
    <select id="selectByParams" parameterType="org.example.springboot.query.UserQuery" resultType="org.example.springboot.vo.User">
        select *
        from users
        <where>
            <if test="null !=userName and userName !=''">
                and username like concat('%',#{userName},'%')
            </if>
        </where>
    </select>
</mapper>
添加commons-lang3依赖

如果需要使用StringUtils工具类,需要引入commons-lang3依赖。

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
</dependency>
AssertUtil工具类
public class AssertUtil {

    public static void isTrue(boolean flag, String msg) {
        if (flag) {
            throw new ParamsException(msg);
        }
    }
}
ParamsException异常类
package org.example.springboot.exceptions;

public class ParamsException extends RuntimeException {

    private Integer code = 500;
    private String msg = "参数异常";


    public ParamsException(String msg) {
        this.msg = msg;
    }

    public ParamsException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 获取
     * @return code
     */
    public Integer getCode() {
        return code;
    }

    /**
     * 设置
     * @param code
     */
    public void setCode(Integer code) {
        this.code = code;
    }

    /**
     * 获取
     * @return msg
     */
    public String getMsg() {
        return msg;
    }

    /**
     * 设置
     * @param msg
     */
    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String toString() {
        return "ParamsException{code = " + code + ", msg = " + msg + "}";
    }
}
ResultInfo.java方法实现
package org.example.springboot.model;

public class ResultInfo {

    private Integer code = 1; // 状态码  1=成功 0=失败
    private String msg = "操作成功!"; // 提示信息
    private Object result; // 需要返回的结果

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
UserQuery.java方法实现
package org.example.springboot.query;

import com.github.pagehelper.Page;
import org.example.springboot.vo.User;

/**
 * 多条件查询复合类
 */
public class UserQuery {
    private User user;

    // 页码,通常从1开始
    private int pageNum;

    // 页面大小,即每页显示的记录数
    private int pageSize;

    private String userName;

    // 其他查询条件可以在这里添加,如年龄范围、邮箱等

    // 默认构造函数
    public UserQuery() {
    }

    // 带参数的构造函数(可选)
    public UserQuery(int pageNum, int pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }

    // Getter 和 Setter 方法
    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}
UserService.java方法实现
public User queryUserByUserName(String userName){
    return userMapper.queryUserByUserName(userName);
}

public User queryUserByUserId(Integer userId){
    return userMapper.queryById(userId);
}

public void saveUser(User user) {
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空!");
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空!");
    User temp = userMapper.queryUserByUserName(user.getUserName());
    AssertUtil.isTrue(null != temp, "该用户已存在!");
    AssertUtil.isTrue(userMapper.save(user)<1,"用户记录添加失败!");
}

public void updateUser(User user) {
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空!");
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空!");
    User temp = userMapper.queryUserByUserName(user.getUserName());
    AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "该用户已存在!");
    AssertUtil.isTrue(userMapper.update(user)<1,"用户记录添加失败!");
}

public  void deleteUser(Integer id){
    AssertUtil.isTrue(null == id || null ==userMapper.queryById(id),"待删除记录不存在!");
    AssertUtil.isTrue(userMapper.delete(id)<1,"用户删除失败!");
}

public PageInfo<User> queryUserByParams(UserQuery userQuery){
    PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
    return new PageInfo<User>(userMapper.selectByParams(userQuery));
}
UserController.java 接口方法
@GetMapping("user/uname/{userName}")
public User queryUserByUserName(@PathVariable String userName) {
    return userService.queryUserByUserName(userName);
}

@GetMapping("user/uid/{userId}")
public User queryUserByUserId(@PathVariable  Integer userId){
    return userService.queryUserByUserId(userId);
}

@GetMapping("user/list")
public PageInfo<User> list(UserQuery userQuery){
    return userService.queryUserByParams(userQuery);
}

@PostMapping("user")
public ResultInfo saveUser(User user){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.saveUser(user);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录添加失败!");
    }
    return resultInfo;
}


@PutMapping("user")
public ResultInfo updateUser(User user){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.updateUser(user);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录更新失败!");
    }
    return resultInfo;
}


@DeleteMapping("user/duid/{userId}")
public ResultInfo deleteUser(@PathVariable  Integer  userId){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.deleteUser(userId);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录删除失败!");
    }
    return resultInfo;
}

PostMan 接口测试工具下载与使用

在企业web 应用开发中,对服务器端接口进行测试,通常借助接口测试工具,这里使用Postman 接口测试工具来对后台restful接口进行测试,Postman 工具下载地址: https://www.getpostman.com/apps 选中对应平台下载即可。

下载安装后,启动Postman 根据后台接口地址发送响应请求即可对接口进行测试。

3. API 文档构建工具-Swagger2(了解)

由于Spring Boot能够快速开发、便捷部署等特性,通常在使用Spring Boot构建Restful 接口应用时考虑到多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。对于不同的终端公用一套接口API时对于联调测试时就需要知道后端提供的接口Api 列表文档,对于服务端开发人员来说就需要编写接口文档,描述接口调用地址参数结果等,这里借助第三方构建工具Swagger2来实现Api文档生成功能。

环境整合配置

  • pom.xml 依赖添加

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.9.2</version>
</dependency>
  • 配置类添加

@Configuration
@EnableSwagger2
public class Swagger2 {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xxxx.springboot.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("用户管理接口API文档参考")
                .version("1.0")
                .build();
    }
}

Swagger2 常用注解说明

@Api
@Api:用在请求的类上,说明该类的作用
    tags="说明该类的作用"
@Api(tags="APP用户注册Controller")
@ApiOperation
@ApiOperation:"用在请求的方法上,说明方法的作用"
    value="说明方法的作用"
    notes="方法的备注说明"
@ApiOperation(value="用户注册",notes="手机号、密码都是必输项,年龄随边填,但必须是数字")
@ApiImplicitParams
@ApiImplicitParams:用在请求的方法上,包含一组参数说明
    @ApiImplicitParam:用在 @ApiImplicitParams 注解中,指定一个请求参数的配置信息       
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)    
        dataType:参数类型,默认String,其它值dataType="Integer"       
        defaultValue:参数的默认值
@ApiImplicitParams({
    @ApiImplicitParam(name="mobile",value="手机号",required=true,paramType="form"),
    @ApiImplicitParam(name="password",value="密码",required=true,paramType="form"),
    @ApiImplicitParam(name="age",value="年龄",required=true,paramType="form",dataType="Integer")
})
@ApiResponses
@ApiResponses:用于请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类
@ApiOperation(value = "select请求",notes = "多个参数,多种的查询参数类型")
@ApiResponses({
    @ApiResponse(code=400,message="请求参数没填好"),
    @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对")
})
@ApiModel
@ApiModel:用于响应类上,表示一个返回响应数据的信息
            (这种一般用在post创建的时候,使用@RequestBody这样的场景,
            请求参数无法使用@ApiImplicitParam注解进行描述的时候)
    @ApiModelProperty:用在属性上,描述响应类的属性
@ApiModel(description= "返回响应数据")
public class RestMessage implements Serializable{
    @ApiModelProperty(value = "是否成功")
    private boolean success=true;
    @ApiModelProperty(value = "返回对象")
    private Object data;
    @ApiModelProperty(value = "错误编号")
    private Integer errCode;
    @ApiModelProperty(value = "错误信息")
    private String message;
    /* getter/setter */
}

用户模块注解配置

UserController.java 接口方法注解使用
@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根据用户名查询用户记录")
@ApiImplicitParam(name = "userName",value = "查询参数",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
    return userService.queryUserByUserName(userName);
}

@ApiOperation(value = "根据用户id查询用户记录")
@ApiImplicitParam(name = "userId",value = "查询参数",required = true,paramType = "path")
@GetMapping("user/{userId}")
public User queryUserByUserId(@PathVariable  Integer userId, HttpServletRequest request){
    return userService.queryUserByUserId(userId);
}

@GetMapping("user/list")
@ApiOperation(value = "多条件查询用户列表记录")
public PageInfo<User> list(UserQuery userQuery){
    return userService.queryUserByParams(userQuery);
}

@PutMapping("user")
@ApiOperation(value = "用户添加")
@ApiImplicitParam(name = "user",value = "用户实体类",dataType = "User")
public ResultInfo saveUser(@RequestBody  User user){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.saveUser(user);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录添加失败!");
    }
    return resultInfo;
}

@PostMapping("user")
@ApiOperation(value = "用户更新")
@ApiImplicitParam(name = "user",value = "用户实体类",dataType = "User")
public ResultInfo updateUser(@RequestBody  User user){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.updateUser(user);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录更新失败!");
    }
    return resultInfo;
}

@PutMapping("user/{userId}")
@ApiOperation(value = "根据用户id删除用户记录")
@ApiImplicitParam(name = "userId",value = "查询参数",required = true,paramType = "path")
public ResultInfo deleteUser(@PathVariable  Integer  userId){
    ResultInfo resultInfo=new ResultInfo();
    try {
        userService.deleteUser(userId);
    } catch (ParamsException e) {
        e.printStackTrace();
        resultInfo.setCode(e.getCode());
        resultInfo.setMsg(e.getMsg());
    }catch (Exception e) {
        e.printStackTrace();
        resultInfo.setCode(300);
        resultInfo.setMsg("记录删除失败!");
    }
    return resultInfo;
}
JavaBean 使用
  • User.java

@ApiModel(description = "响应结果-用户信息")
public class User {
    @ApiModelProperty(value = "用户id",example = "0")
    private Integer id;
    @ApiModelProperty(value = "用户名")
    private String userName;
    @ApiModelProperty(value = "用户密码")
    private String userPwd;
    /*
       省略get|set
    */
}
  • UserQuery.java

@ApiModel(description = "用户模块条件查询类")
public class UserQuery {
    @ApiModelProperty(value = "分页页码",example = "1")
    private Integer pageNum=1;
    @ApiModelProperty(value = "每页大小",example = "10")
    private Integer pageSize=10;
    @ApiModelProperty(value = "用户名")
    private String userName;
    /*
       省略get|set
    */
 }
  • ResultInfo.java

@ApiModel(description = "响应结果-Model信息")
public class ResultInfo {

    @ApiModelProperty(value = "响应状态码",example = "200")
    private Integer code=200;
    @ApiModelProperty(value = "响应消息结果")
    private String msg="success";

    @ApiModelProperty(value = "响应具体结果信息")
    private Object result;
     /*
       省略get|set
    */
}

Swagger2 接口文档访问

启动工程,浏览器访问:http://localhost:9999/swagger-ui.html

4. SpringBoot应用热部署(了解)

什么是热部署?

热部署,就是在应用正在运行的时候升级软件(增加业务/修改bug),却不需要重新启动应用

大家都知道在项目开发过程中,常常会改动页面数据或者修改数据结构,为了显示改动效果,往往需要重启应用查看改变效果,其实就是重新编译生成了新的 Class 文件,这个文件里记录着和代码等对应的各种信息,然后 Class 文件将被虚拟机的 ClassLoader 加载。

而热部署正是利用了这个特点,它监听到如果有 Class 文件改动了,就会创建一个新的 ClaassLoader 进行加载该文件,经过一系列的过程,最终将结果呈现在我们眼前,Spring Boot通过配置DevTools 工具来达到热部署效果。

在原理上是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。

热部署环境配置与测试

配置 DevTools 环境
  • 修改 Pom 文件,添加 DevTools 依赖

<!-- DevTools 的坐标 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
     <!--当前这个项目被继承之后,这个不向下传递-->
	<optional>true</optional>	
</dependency>

同时在plugin中添加devtools生效标志

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
      <fork>true</fork><!-- 如果没有该配置,热部署的devtools不生效 -->
  </configuration>
</plugin>

devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),注意:因为其采用的虚拟机机制,该项重启是很快的。配置了后在修改java文件后也就支持了热启动,不过这种方式是属于项目重启(速度比较快的项目重启),会清空session中的值,也就是如果有用户登陆的话,项目重启后需要重新登陆。

默认情况下,/META-INF/maven,/META-INF/resources,/resources,/static,/templates,/public这些文件夹下的文件修改不会使应用重启,但是会重新加载(devtools内嵌了一个LiveReload server,当资源发生改变时,浏览器刷新)

全局配置文件配置

在application.yml中配置spring.devtools.restart.enabled=false,此时restart类加载器还会初始化,但不会监视文件更新。

spring:
  ## 热部署配置
  devtools:
    restart:
      enabled: true
      # 设置重启的目录,添加目录的文件需要restart
      additional-paths: src/main/java
      # 解决项目自动重新编译后接口报404的问题
      poll-interval: 3000
      quiet-period: 1000
Idea 配置

当我们修改了Java类后,IDEA默认是不自动编译的,而spring-boot-devtools又是监测classpath下的文件发生变化才会重启应用,所以需要设置IDEA的自动编译

  • 自动编译配置

File-Settings-Compiler-Build Project automatically

  • Registry 属性修改

ctrl + shift + alt + /,选择Registry,勾上 Compiler autoMake allow when app running

热部署效果测试
  • 第一次访问 user/uname/{uname} 接口

@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根据用户名查询用户记录")
@ApiImplicitParam(name = "userName",value = "查询参数",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
    return userService.queryUserByUserName(userName);
}

  • 修改接口代码 控制台打印接收的uname参数 ctrl+f9 键重新编译 浏览器访问

@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根据用户名查询用户记录")
@ApiImplicitParam(name = "userName",value = "查询参数",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
    System.out.println("查询参数-->userName:"+userName);
    return userService.queryUserByUserName(userName);
}

5. SpringBoot单元测试(操作)

做过web项目开发的对于单元测试都并不陌生了,通过它能够快速检测业务代码功能的正确与否,SpringBoot框架对单元测试也提供了良好的支持,来看SpringBoot应用中单元测试的使用。

pom.xml 测试依赖添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

Service业务方法测试

这里以UserService为例,src/tets/java 目录下添加测试包 com.xxxx.sprinboot.service 定义测试类代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
public class TestUserService {
    private Logger log = LoggerFactory.getLogger(TestUserService.class);

    @Resource
    private UserService userService;

    @Before
    public void before(){
        log.info("单元测试开始...");
    }
    @Test
    public  void test01(){
        log.info(userService.queryUserByUserId(10).toString());
    }
    @Test
    public  void test02(){
        log.info(userService.queryUserByParams(new UserQuery()).toString());
    }
    @After
    public void after(){
        log.info("单元测试结束...");
    }
}

控制层接口方法测试

视图层代码使用MockMvc 进行测试,这里以UserCntroller 为例,src/tets/java 目录下添加测试包 com.xxxx.sprinboot.controller 定义测试类代码如下:

package org.example.springboot.controller;

import org.example.springboot.Starter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
@AutoConfigureMockMvc
public class TestUserController {
    private Logger log = LoggerFactory.getLogger(TestUserController.class);

    @Resource
    private MockMvc mockMvc;

    //用户列表查询
    @Test
    public void apiTest01() throws Exception{
        MvcResult mvcResult=mockMvc.perform(MockMvcRequestBuilders.get("/user/list")).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        log.info("响应状态:{}",mvcResult.getResponse().getStatus());
        log.info("响应内容:{}",mvcResult.getResponse().getContentAsString());;
    }
    // 用户名记录查询
    @Test
    public void apiTest02()throws Exception{
        MvcResult mvcResult=mockMvc.perform(MockMvcRequestBuilders.get("/user/uname/admin")).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        log.info("响应状态:{}",mvcResult.getResponse().getStatus());
        log.info("响应内容:{}",mvcResult.getResponse().getContentAsString());;
    }
}

6. 分布式缓存Ehcache整合(了解)

EhCache是一个比较成熟的Java缓存框架,最早从hibernate发展而来, 是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,快速简单。

Spring Boot对Ehcache的使用提供支持,所以在Spring Boot中只需简单配置即可使用Ehcache实现数据缓存处理。

Spring Cache 相关注解说明

SpringBoot 内部使用SpringCache 来实现缓存控制,这里集成Ehcache实际上是对SpringCache 抽象的其中一种实现,这里在使用Ehcache实现缓存控制时相关注解说明如下

@CacheConfig

用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字。

@CacheConfig(cacheNames = "users")
public class UserService {。。。}

这里也可以不使用该注解,直接使用@Cacheable配置缓存集的名字。

@Cacheable

应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中。

该注解主要有下面几个参数:

  • value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了

  • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档

  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存。

  • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。

  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的

  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用

  • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

@Cacheable(value = "user", key = "#id")
User selectUserById(final Integer id);
@CachePut

应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存,@CachePut的参数与@Cacheable类似,示例如下:

@CachePut(value = "user", key = "#user.id")  
public User save(User user) {  
    users.add(user);  
    return user;  
}  
@CacheEvict

应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据,示例如下:

@CacheEvict(value = "user", key = "#id")
void delete(final Integer id);

除了同@Cacheable一样的参数之外,@CacheEvict还有下面两个参数:

  • allEntries:非必需,默认为false。当为true时,会移除所有数据

  • beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。

@Caching

组合多个Cache注解使用。示例:

@Caching( 
    put = { 
        @CachePut(value = "user", key = "#user.id"), 
        @CachePut(value = "user", key = "#user.username"), 
        @CachePut(value = "user", key = "#user.age") 
   } 
} 

将id-->user;username--->user;age--->user进行缓存。

用户管理模块缓存引入

环境配置
  • pom.xml 依赖添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Ehcache 坐标 -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
  • ehcahe.xml 文件添加

src/main/resources 目录下添加ehcache.xml 文件,内容如下:

<ehcache name="mycache">
    <diskStore path="C:\java\cache"/>
    <!--
        name:缓存名称。
        maxElementsInMemory:缓存最大数目
        maxElementsOnDisk:硬盘最大缓存个数。
        eternal:对象是否永久有效,一但设置了,timeout将不起作用。
        overflowToDisk:是否保存到磁盘,当系统宕机时
        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。
               仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
        timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。
             最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。
             默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
        clearOnFlush:内存数量最大时是否清除。
        memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
            FIFO,first in first out,这个是大家最熟的,先进先出。
            LFU, Less Frequently Used,最近最少被访问的。
            LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,
               当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
        -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>

    <cache
            name="users"
            eternal="false"
            maxElementsInMemory="100"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="300"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>
  • application.yml 添加缓存配置

spring:
  datasource:
    type: com.mchange.v2.c3p0.ComboPooledDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: 
  devtools:
    restart:
      enabled: true
      # 设置重启的目录,添加目录的文件需要restart
      additional-paths: src/main/java
      # 解决项目自动重新编译后接口报404的问题
      poll-interval: 3000
      quiet-period: 1000
  cache:
    ehcache:
      config: classpath:ehcahe.xml
  • Starter 启动入口类启动缓存

@MapperScan("org.example.springboot.dao")
@EnableCaching
@SpringBootApplication
public class Starter {

    public static void main(String[] args) {
        SpringApplication.run(Starter.class);
    }
}
  • 缓存User 对象实现序列化接口

@ApiModel(description = "用户实体对象")
public class User implements Serializable {
    @ApiModelProperty(value = "用户id主键")
    private Integer id;
    @ApiModelProperty(value = "用户名")
    private String userName;
    @ApiModelProperty(value = "用户密码")
    private String userPwd;
    /*
      省略 get|set方法
    */
}
缓存代码添加

这里以UserService 方法为例

用户详情查询缓存添加
@Cacheable(value = "users",key = "#userId")
public User queryUserByUserId(Integer userId){
    return userMapper.queryById(userId);
}
用户列表查询缓存
@Cacheable(value = "users",key="#userQuery.userName+'-'+#userQuery.pageNum+'-'+#userQuery.pageSize")
public PageInfo<User> queryUserByParams(UserQuery userQuery){
    PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
    return new PageInfo<User>(userMapper.selectByParams(userQuery));
}
用户更新&删除缓存清除
@Transactional(propagation = Propagation.REQUIRED)
@CacheEvict(value = "users",key="#user.id")
public void updateUser(User user) {
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空!");
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空!");
    User temp = userMapper.queryUserByUserName(user.getUserName());
    AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "该用户已存在!");
    AssertUtil.isTrue(userMapper.update(user)<1,"用户记录添加失败!");
}

@Transactional(propagation = Propagation.REQUIRED)
@CacheEvict(value = "users",allEntries=true)
public  void deleteUser(Integer userId){
    AssertUtil.isTrue(null == userId || null ==userMapper.queryById(userId),"待删除记录不存在!");
    AssertUtil.isTrue(userMapper.delete(userId)<1,"用户删除失败!");
}

7. 定时调度集成-Quartz(操作)

在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量等。

在Spring Boot中有Java自带的java.util.Timer类,SpringBoot自带的Scheduled来实现,也有强大的调度器Quartz。Scheduled 在Spring3.X 引入,默认SpringBoot自带该功能,使用起来也很简单,在启动类级别添加@EnableScheduling注解即可引入定时任务环境。但遗憾的是Scheduled 默认不支持分布式环境,这里主要讲解Quartz 时钟调度框架与Spring Boot 集成。

环境整合配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

源代码添加

定义job

org.example.springboot下添加jobs包,定义待执行job任务

public class MyFirstJob implements Job {
    
    private Logger log = LoggerFactory.getLogger(MyFirstJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TriggerKey triggerKey =  context.getTrigger().getKey();
        log.info("触发器:"+triggerKey.getName()+"-->所属组:"+triggerKey.getGroup()+"-->"+sdf.format(new Date())+"-->"+"hello Spring Boot Quartz...");
    }
}
构建调度配置类
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail jobDetail1(){
        return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
    }

    @Bean
    public Trigger trigger1(){
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                //每一秒执行一次
                .withIntervalInSeconds(1)
                //永久重复,一直执行下去
                .repeatForever();
        return TriggerBuilder.newTrigger()
                .withIdentity("trigger1","group1")
                .withSchedule(scheduleBuilder)
                .forJob(jobDetail1())
                .build();
    }

    // 每两秒触发一次任务
    @Bean
    public Trigger trigger2(){
        return TriggerBuilder.newTrigger()
                .withIdentity("trigger2", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
                .forJob(jobDetail1())
                .build();
    }
}
启动StarterApplication 查看控制台打印效果

8. 全局异常与事物控制(强制)

Spring Boot事物支持

在使用Jdbc 作为数据库访问技术时,Spring Boot框架定义了基于jdbc 的PlatformTransactionManager 接口的实现DataSourceTransactionManager,并在Spring Boot 应用启动时自动进行配置。如果使用jpa 的话 Spring Boot 同样提供了对应实现。

这里Spring Boot 集成了Mybatis框架,Mybatis底层数据访问层实现基于jdbc 来实现,所以在Spring Boot 环境下对事物进行控制,事物实现由Spring Boot实现并自动配置,在使用时通过注解方式标注相关方法加入事物控制即可

  • 声明式事物配置

@Transactional(propagation = Propagation.REQUIRED)
public void saveUser(User user) {
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空!");
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空!");
    User temp = userMapper.queryUserByUserName(user.getUserName());
    AssertUtil.isTrue(null != temp, "该用户已存在!");
    AssertUtil.isTrue(userMapper.save(user)<1,"用户记录添加失败!");
}

@Transactional(propagation = Propagation.REQUIRED)
public void updateUser(User user) {
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空!");
    AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空!");
    User temp = userMapper.queryUserByUserName(user.getUserName());
    AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "该用户已存在!");
    AssertUtil.isTrue(userMapper.update(user)<1,"用户记录添加失败!");
}

@Transactional(propagation = Propagation.REQUIRED)
public  void deleteUser(Integer id){
    AssertUtil.isTrue(null == id || null ==userMapper.queryById(id),"待删除记录不存在!");
    AssertUtil.isTrue(userMapper.delete(id)<1,"用户删除失败!");
}

Spring Boot 全局异常处理

SpringMvc 中对异常统一处理提供了相应处理方式,推荐大家使用的是实现接口HandlerExceptionResolver的方式,对代码侵入性较小。

在Spring Boot 应用中同样提供了对异常的全局性处理,相关注解如下:

@ControllerAdvice

该注解组合了@Component注解功能,最常用的就是作为全局异常处理的切面类,同时通过该注解可以指定包扫描的范围。@ControllerAdvice约定了几种可行的返回值,如果是直接返回model类的话,需要使用@ResponseBody进行json转换

@ExceptionHandler
  该注解在Spring 3.X 版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些 具体应用以Restful 接口为例,测试保存用户接口

全局异常应用

异常抛出与全局捕捉
  • UserController 查询接口

@ApiOperation(value = "根据用户id查询用户记录")
@ApiImplicitParam(name = "userId",value = "查询参数",required = true,paramType = "path")
@GetMapping("user/{userId}")
public User queryUserByUserId(@PathVariable  Integer userId){
	return userService.queryUserByUserId(userId);
}
  • UserService 查询业务方法,抛出ParamExceptions 异常

public User queryUserByUserId(Integer userId){
    AssertUtil.isTrue(true,"异常测试...");
    return userMapper.queryById(userId);
}
  • 全局异常处理类GlobalExceptionHandler定义

@ControllerAdvice
public class GlobalExceptionHandler{
    /**
     * 全局异常处理 返回json
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultInfo exceptionHandler(Exception e){
        ResultInfo resultInfo=new ResultInfo();
        resultInfo.setCode(300);       //这些值表示当异常不是 ParamsException 类型时返回的通用错误信息。
        resultInfo.setMsg("操作失败!"); //
        if(e instanceof ParamsException){
            ParamsException pe= (ParamsException) e;
            resultInfo.setMsg(pe.getMsg());
            resultInfo.setCode(pe.getCode());
        }
        return resultInfo;
    }
}
  • Postman 执行测试效果

特定异常处理

通过@ExceptionHandler 标注方法可以处理特定异常,这里以用户未登录异常为例,通过全局异常进行统一处理

/**
 * 用户未登录异常特殊处理 返回json
 * @param authExceptions
 * @return
 */
@ExceptionHandler(value = NoLoginException.class)
@ResponseBody
public  ResultInfo userNotLoginHandler(NoLoginException authExceptions){
    System.out.println("用户未登录异常处理。。。");
    return new ResultInfo(authExceptions.getCode(),authExceptions.getMsg());
}

在用户添加接口中抛出未登录异常为例进行测试

@PutMapping("user")
@ApiOperation(value = "用户添加")
@ApiImplicitParam(name = "user",value = "用户实体类",dataType = "User")
public ResultInfo saveUser(@RequestBody  User user){
    if(1==1){
        throw  new NoLoginException();
    }
    ResultInfo resultInfo=new ResultInfo();
        userService.saveUser(user);
    return resultInfo;
}

9. SpringBoot 数据校验-Validation(操作)

日常项目开发中,对于前端提交的表单,后台接口接收到表单数据后,为了程序的严谨性,通常后端会加入业务参数的合法校验操作来避免程序的非技术性bug,这里对于客户端提交的数据校验,SpringBoot通过spring-boot-starter-validation 模块包含了数据校验的工作。

这里主要介绍Spring Boot中对请求数据进行校验,相关概念如下

  • JSR303/JSR-349: JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其升级版本,添加了一些新特性。

  • Hibernate Validation:Hibernate Validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等

  • Spring Validation:Spring Validation对Hibernate Validation进行了二次封装,在Spring Mvc模块中添加了自动校验,并将校验信息封装进了特定的类中

环境配置

实现参数校验,程序必须引入spring-boot-starter-validation 依赖,只是在引入spring-boot-starter-web依赖时,该模块会自动依赖spring-boot-starter-validation,所以程序中引入spring-boot-starter-web 会一并依赖spring-boot-starter-validation到项目中。

校验相关注解

注解功能
@AssertFalse可以为null,如果不为null的话必须为false
@AssertTrue可以为null,如果不为null的话必须为true
@DecimalMax设置不能超过最大值
@DecimalMin设置不能超过最小值
@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future日期必须在当前日期的未来
@Past日期必须在当前日期的过去
@Max最大不得超过此最大值
@Min最大不得小于此最小值
@NotNull不能为null,可以是空
@Pattern必须满足指定的正则表达式
@Size集合、数组、map等的size()值必须在指定范围内
@Email必须是email格式
@Length长度必须在指定范围内
@NotBlank字符串不能为null,字符串trin()后也不能等于“”
@NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trin()后可以等于“”
@Range值必须在指定范围内
@URL必须是一个URL

参数校验注解使用

  • User实体类参数校验注解

public class User  implements Serializable {
    private Integer id;
    @NotBlank(message = "用户名不能为空!")
    private String userName;

    @NotBlank(message = "用户密码不能为空!")
    @Length(min = 6, max = 10,message = "密码长度至少6位但不超过10位!")
    private String userPwd;
    @Email
    private String email;
    
    /*
      省略get set 方法  
    */
}
  • 接口方法形参@Valid注解添加

@PostMapping("user02")
@ApiOperation(value = "用户添加")
@ApiImplicitParam(name = "user02",value = "用户实体类",dataType = "User")
public ResultInfo saveUser02(@Valid  User user){
    ResultInfo resultInfo=new ResultInfo();
    //userService.saveUser(user);
    return resultInfo;
}
  • 全局异常错误信息捕捉

/**
 * 全局异常处理 返回json
 * @param e
 * @return
 */
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultInfo exceptionHandler(Exception e){
    ResultInfo resultInfo=new ResultInfo();
    resultInfo.setCode(300);
    resultInfo.setMsg("操作失败!");
    if(e instanceof ParamsException){
        ParamsException pe= (ParamsException) e;
        resultInfo.setMsg(pe.getMsg());
        resultInfo.setCode(pe.getCode());
    }else if(e instanceof BindException){
        BindException be = (BindException) e;
        resultInfo.setResult(be.getBindingResult().getFieldError().getDefaultMessage());
    }
    return resultInfo;
}
  • PostMan 接口测试

##

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/939941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

答:C++需要学到什么程度再开始学 qt 比较合理?

有网友问&#xff1a;C需要学到什么程度再开始学 qt 比较合理&#xff1f; 南老师回答如下。 在我看来&#xff0c;这确实是一个好问题&#xff0c;但我的回答&#xff0c;大概很难成为一个好回答。 但我还是想回答&#xff0c;所以诚恳谢妖&#xff01; 如果有人问我&…

Elasticsearch8.17.0在mac上的安装

1、下载并安装 下载8.17版本es(目前最新版本)&#xff1a;Download Elasticsearch | Elastic 也可以通过历史版本列表页下载&#xff1a;Past Releases of Elastic Stack Software | Elastic 当然也可以指定具体版本号进行下载&#xff1a;Elasticsearch 8.17.0 | Elastic …

爬取Q房二手房房源信息

文章目录 1. 实战概述2. 网站页面分析3. 编写代码爬取Q房二手房房源信息3.1 创建项目与程序3.2 运行程序&#xff0c;查看结果 4. 实战小结 1. 实战概述 本次实战项目旨在通过编写Python爬虫程序&#xff0c;抓取深圳Q房网上的二手房房源信息。我们将分析网页结构&#xff0c;…

易语言OCR银行卡文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

6.3.1 MR实战:计算总分与平均分

在本次实战中&#xff0c;我们的目标是利用Apache Hadoop的MapReduce框架来处理和分析学生成绩数据。具体来说&#xff0c;我们将计算一个包含五名学生五门科目成绩的数据集的总分和平均分。这个过程包括在云主机上准备数据&#xff0c;将成绩数据存储为文本文件&#xff0c;并…

MongoDB、Mongoose使用教程

文章目录 一&#xff1a;MongoDB 简介1.1 什么是 MongoDB1.2 特点1.3 与关系数据库的区别&#xff1a;1.4 资源链接&#xff1a; 二&#xff1a;安装 MongoDB2.1 安装前的准备2.2 安装、启动 MongoDB2.3 创建用户 MongoDB 三、连接四&#xff1a;MongoDB 基础操作4.1 库操作&am…

【2024/12最新】CF罗技鼠标宏分享教程与源码

使用效果&#xff1a; 支持的功能 M4 7发一个点HK417 连点瞬狙炼狱加特林一个圈 下载链接 点击下载

vue2组件

文章目录 组件注册全局注册局部注册 组件中的props格式单向数据校验 组件中的事件使用传参声明事件校验 组件上的v-model使用携带参数多个v-model处理修饰符 透传 Attributes简单使用禁用透传多个继承 动态组件介绍使用KeepAlive包含缓存生命周期 插槽使用默认内容具名插槽条件…

【C++】用哈希表封装myunordered_map和myunordered_set

前言 本篇博客我们来用哈希表模拟实现一下STL库里的unordered_map与unordered_set &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;C 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 1.源码及框架分析 2.模…

在linux系统的docker中安装GitLab

一、安装GitLab&#xff1a; 在安装了docker之后就是下载安装GitLab了&#xff0c;在linux系统中输入命令&#xff1a;docker search gitlab就可以看到很多项目&#xff0c;一般安装第一个&#xff0c;它是英文版的&#xff0c;如果英文不好可以安装twang2218/gitlab-ce-zh。 …

Restaurants WebAPI(一)—— clean architecture

文章目录 项目地址一、Restaurants.Domain 核心业务层1.1 Entities实体层1.2 Repositories 数据操作EF的接口二、Restaurants.Infrastructure 基础设施层2.1 Persistence 数据EF CORE配置2.2 Repositories 数据查询实现2.3 Extensions 服务注册三、Restaurants.Application用例…

全栈开发----Mysql基本配置与使用

本篇是在已下载Mysql的情况下进行的&#xff0c;若还未下载或未创建Mysql服务&#xff0c;请转到这篇: 2024 年 MySQL 8.0.40 安装配置、Workbench汉化教程最简易&#xff08;保姆级&#xff09;_mysql8.0.40下载安装教程-CSDN博客 本文对于mysql的操作均使用控制台sql原生代码…

AI可信论坛亮点:合合信息分享视觉内容安全技术前沿

前言 在当今科技迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正以前所未有的速度改变着我们的生活与工作方式。作为AI领域的重要盛会&#xff0c;CSIG青年科学家会议AI可信论坛汇聚了众多青年科学家与业界精英&#xff0c;共同探讨AI技术的最新进展、挑…

逆变器中自举电路设计要点及其取值分析

自举电源具有电路简单&#xff0c;成本低等优点。可以减小变压器尺寸&#xff0c;可以使用较小的磁芯骨架即可满足整机对电源的需求。不过其也有不足之处&#xff0c;比如只能用于小功率设计&#xff08;驱动器已验证到11KW50A模块的驱动设计&#xff09;&#xff0c;对输出响应…

如何测量分辨率

一、什么是分辨率&#xff1f; 分辨率指的是分清物体细节的能力。分辨率是一个成像系统还原空间频率的能力。一些人只是简单的用分辨率去描述极限分辨率&#xff0c;但是相机在在不同的对比度的情况下还原低&#xff0c;中和高频率的能力&#xff0c;也可以显示全面综合的信息。…

springboot中——Logback介绍

程序中的日志&#xff0c;是用来记录应用程序的运行信息、状态信息、错误信息等。 Logback基本使用 springboot的依赖自动传递了logback的依赖&#xff0c;所以不用再引入依赖 之后在resources文件下创建logback.xml文件&#xff0c;写入 <?xml version"1.0" …

git 删除鉴权缓存及账号信息

在Windows系统下 清除凭证管理器中的Git凭据 按下Win R键&#xff0c;打开“运行”对话框&#xff0c;输入control&#xff0c;然后回车&#xff0c;打开控制面板。在控制面板中找到“用户账户”&#xff0c;然后点击“凭据管理器”。在凭据管理器中&#xff0c;找到“Windows…

Apache Solr RCE(CVE-2017-12629)--vulhub

Apache Solr 远程命令执行漏洞&#xff08;CVE-2017-12629&#xff09; Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发&#xff0c;主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个…

vue预览和下载 pdf、ppt、word、excel文档,文件类型为链接或者base64格式或者文件流,

** 方法1&#xff1a;word、xls、ppt、pdf 这些文件&#xff0c; 如果预览的文件是链接可以直接打开&#xff0c;可用微软官方的预览地址 ** <iframe width"100%" :src"textVisibleURl " id"myFramePPT" style"border: none;backgroun…

Postbot使用教程

1.什么是Postbot&#xff1f; Postbot 是 Postman 中 API 工作流的 AI 助手。您可以让 Postbot 帮助您排查 API 请求问题、编写测试脚本和文档以及理解大型数据集。如果您需要有关使用 Postman 的帮助或不确定下一步该怎么做&#xff0c;也可以向 Postbot 询问。 2.开始使用 …