Spring Boot开发教程

  • Spring Boot可以轻松创建独立的、生产级的基于Spring的应用程序
  • Spring Boot直接嵌入TomcatJettyUndertow,可以直接运行Spring Boot应用程序

SpringBoot快速入门

  • pom.xml引入父工程
<!--导入SpringBoot父工程-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.7.10</version>
    </parent>
  • pom.xml引入web启动器
  • 导入web项目场景启动器,maven会自动导入与web开发相关的依赖jar包
<!--导入web项目场景启动器,会自动导入与web开发相关的依赖jar包-->
    <dependencies>
        <!--web场景启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--版本不用指定会与Springboot父工程保持一致-->
            <!--<version>2.5.3</version>-->
        </dependency>
    </dependencies>
  • SpringBoot启动器
package com.yangyi.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: MainApp
 * @Author: 杨逸
 * @Data:2023/9/25 17:15
 * @Description: web项目启动器
 */

//表示这是一个springboot项目
@SpringBootApplication
public class MainApp {
    public static void main(String[] args) {
        //启动springboot应用程序
        SpringApplication.run(MainApp.class,args);
    }
}

SpringBoot约定优于配置

  • 自动配置Spring

  • 自动配置Tomcat

  • 自动配置字符过滤器等等

  • 默认扫描主程序所在的包

  • 指定特定要扫描的包

//使用属性scanBasePackages指定特定要扫描的包
@SpringBootApplication(scanBasePackages = {"com.yangyi"})

SpringBott的核心配置文件application.properties配置文件

  • 通过该配置文件可以进行SpringBoot默认配置的修改,如果有需要
  • 通过注解@Value(value = "name")可以将核心配置中的值注入到bean的简单属性中,比如字符串或者数值
  • 通过注解@ConfigurationProperties(prefix = "monster")可以将核心配置中的值注入到bean的复杂属性中,比如对象类型的属性
  • 常用的配置
#端口号
server.port=10000
#应用的上下文路径(项目路径)
server.servlet.context-path=/allModel
#指定 POJO 扫描包来让 mybatis 自动扫描到自定义的 POJO
mybatis.type-aliases-package=com.cxs.allmodel.model

#指定 mapper.xml 的路径
#(application 上配置了@MapperScan(扫面 mapper 类的路径)和 pom.xml 中放行了 mapper.xml 后,
# 配 置 mapper-locations 没 有 意 义 。 如 果 mapper 类 和 mapper.xml 不 在 同 一 个 路 径 下 时 ,
mapper-locations 就有用了)
mybatis.mapper-locations=classpath:com/cxs/allmodel/mapper
#session 失效时间(单位 s)
spring.session.timeout=18000
#数据库连接配置
#mysql 数据库 url
mysql.one.jdbc-url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai&useSSL=false
#mysql 数据库用户名
mysql.one.username=
#数据库密码
mysql.one.password=
#线程池允许的最大连接数
mysql.one.maximum-pool-size=15
#日志打印:日志级别 trace<debug<info<warn<error<fatal 默认级别为 info,即默认打印 info 及其以
上级别的日志
#logging.level 设置日志级别,后面跟生效的区域,比如 root 表示整个项目,也可以设置为某个包下,
也可以具体到某个类名(日志级别的值不区分大小写)
logging.level.com.cxs.allmodel.=debug
logging.level.com.cxs.allmodel.mapper=debug
logging.level.org.springframework.web=info
logging.level.org.springframework.transaction=info
logging.level.org.apache.ibatis=info
logging.level.org.mybatis=info
logging.level.com.github.pagehelper = info
logging.level.root=info
#日志输出路径
logging.file=/tmp/api/allmodel.log
#配置 pagehelper 分页插件
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
#jackson 时间格式化
spring.jackson.serialization.fail-on-empty-beans=false
#指定日期格式,比如 yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
#指定日期格式化时区,比如 America/Los_Angeles 或者 GMT+10
spring.jackson.time-zone=GMT+8
#设置统一字符集
spring.http.encoding.charset=utf8

#redis 连接配置
# redis 所在主机 ip 地址
spring.redis.host=
#redis 服务器密码
spring.redis.password=
#redis 服务器端口号
spring.redis.port=
#redis 数据库的索引编号(0 到 15)
spring.redis.database=14
## 连接池的最大活动连接数量,使用负值无限制
#spring.redis.pool.max-active=8
#
## 连接池的最大空闲连接数量,使用负值表示无限数量的空闲连接
#spring.redis.pool.max-idle=8
#
## 连接池最大阻塞等待时间,使用负值表示没有限制
#spring.redis.pool.max-wait=-1ms
#
## 最小空闲连接数量,使用正值才有效果
#spring.redis.pool.min-idle=0

#
## 是否启用 SSL 连接. ##spring.redis.ssl=false
#
## 连接超时,毫秒为单位
#spring.redis.timeout= 18000ms
#
## 集群模式下,集群最大转发的数量
#spring.redis.cluster.max-redirects=
#
## 集群模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
#spring.redis.cluster.nodes=
#
## 哨兵模式下,Redis 主服务器地址
spring.redis.sentinel.master=
#
## 哨兵模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
spring.redis.sentinel.nodes= 127.0.0.1:5050,127.0.0.1:5060

SpringBoot中的注解

  • @Configuration:标注在类上表示是配置类,创建配置类注入容器
  • @Bean:标注在方法上向容器注入一个bean,默认以方法名为该bean的id,也可以通过属性value或者name配置bean的id
  • @Scope:通过该注解配置多实例
package com.yangyi.springboot.bean.config;

import com.yangyi.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: BeanConfig
 * @Author: 杨逸
 * @Data:2023/9/27 8:48
 * @Description: 配置类
 */
//通过注解 @Configuration 表示该类是配置类
@Configuration
public class BeanConfig {

    /**
     * 使用@Bean注解,将方法返回值注入到Spring容器中
     * 默认方法名是bean的id,也可以通过@Bean注解的name属性指定bean的id
     * 也可以通过属性value指定bean的id
     * 默认是单实例,如果需要多实例,可以配置scope属性
     * @return
     */
    @Bean
    public Monster monster01(){
        return new Monster(1,"牛魔王",100,"吹牛");
    }
}

  • @import:配合注解@Configration,通过该注解注入组件
  • 注入bean的默认id是全类名
//通过@Import导入Bean,value属性是一个Class类型的数组,注入bean的默认id是全类名
@Import(value = {Dog.class, Cat.class})
@Configuration
public class BeanConfig2 {
}
  • Conditional条件注解,与@Bean注解配合使用,只有满足特定条件才会注入该bean
  • 一般使用的是@Contitional注解的扩展注解,比如ContitionalOnBean注解
  • 标注在配置类上表示该配置类要注入的bean都要满足特定的条件才会注入
  • 标注在方法上表示该方法注入的bean要满足特定条件才会注入
package com.yangyi.springboot.bean.config;

import com.yangyi.springboot.bean.Monster;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: BeanConfig3
 * @Author: 杨逸
 * @Data:2023/9/27 9:45
 * @Description: 配置类
 */
@Configuration
public class BeanConfig3 {
    
    //通过 @ConditionalOnBean 注解指定只有当ioc容器存在id为monster01的bean时,才会注入该方法指定的bean
    @ConditionalOnBean(name = "monster01")
    @Bean
    public Monster monster02(){
        return new Monster(2,"孙悟空",200,"大闹天空");
    }
}

  • ImportResource注解关联springbean.xml配置文件
package com.yangyi.springboot.bean.config;

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

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: BeanConfig4
 * @Author: 杨逸
 * @Data:2023/9/27 10:16
 * @Description: 配置类
 */
//通过ImportResource导入bean.xml
@ImportResource(value = {"classpath:bean.xml"})
@Configuration
public class BeanConfig4 {

}

  • 使用@ConfigurationProperties注解进行配置绑定
package com.yangyi.springboot.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: Furn
 * @Author: 杨逸
 * @Data:2023/9/27 10:25
 * @Description: 测试实体
 */
//通过@ConfigurationProperties注解完成配置绑定,读取application.properties文件中的配置进行bean属性的配置
@ConfigurationProperties(prefix = "furn01")
@Component
public class Furn {
    private Integer id;
    private String name;
    private Double price;

    public Furn() {
    }

    public Furn(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Furn{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

  • application.properties配置文件
#配置绑定
furn01.id=1
furn01.name=空调
furn01.price=100.89

Lombok中注解的使用

  • 常用的注解
注解说明
@Getter在编译时,自动生成getter()方法
@Setter在编译时,自动生成setter()方法
@ToString在编译时,自动生成ToString()方法
@NoArgsConstructor在编译时,自动生成无参构造器
@AllArgsConstructor在编译时,自动生成全参构造器
@Data等价于@Getter,@Setter,@ToString,@EqualsAndHashCode注解的组合
@Log4j为类提供一个属性名为log的log4j日志对象
@CleanUp可以关闭流
@Buulder被注解的类加个建造者模式
@Synchronized加个同步锁
@SneakyThrows等价于try/catch捕获异常
@NotNull如果给参数加这个注解,参数为null时会抛出空指针异常
@Value@Data注解类似,区别在于会把所有属性定义为private final修饰,且没有setter方法
@Slf4j提供一个名为log的slf4j日志对象
  • 日志对象的使用
package com.yangyi;

import com.yangyi.springboot.bean.Furn;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

/**
 * @Projectname: SpringBootQiuckStart
 * @Filename: HiController
 * @Author: 杨逸
 * @Data:2023/9/26 15:48
 * @Description: 测试springboot默认扫描包的机制
 */
@Controller
@Slf4j
public class HiController {

    @Resource
    private Furn furn = null;

    @RequestMapping(value = "/hi")
    @ResponseBody
    public  String hi(){
        return "hi,springboot,test1";
    }


    @ResponseBody
    @RequestMapping(value = "/furn")
    public Furn getFurn(){
        log.info("furn=={}",furn);
        return furn;
    }

}

Spring Initailizr的使用

  • 创建SpringBoot Maven项目的模板
  • 创建项目时会自动引入父工程和场景启动器等,一些必要的配置,减少手动配置的麻烦
  • idea使用Spring Initializr模板创建项目

YAML语言

  • YAML以数据做为中心,而不是以标记语言为重点
  • YAML仍然是一种标记语言,但是和传统的标记语言不一样,是以数据为中心的标记语言
  • YAML非常适合用来做以数据为中心的配置文件,比如:springboot:application.yaml
  • Java使用YAML的文档

yaml的基本语法

  • 形式为key:value.注意:后面有空格,区分大小写
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格[有些地方也识别tab,推荐使用空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • 字符串无需加引号
  • 注释使用的是#,只支持单行注释

yaml的数据类型

  • 字面量:单个的、不可再分的值.比如:date、boolean、string、number、null
#字面量

id: 1
name: 牛魔王
  • 对象:键值对的集合.比如map、hash、set、object
#对象

#行内写法
k: {k1: v1,k2: v2}
monster: {id: 1,name: 牛魔王}

#行间写法
k: 
  k1: v1
  k2: v2
monster: 
  id: 2
  name: 狐狸精
  • 数组:一组按次序排列的值.比如:array、Iist、queue
#数组
#行内写法
array:{1,2,3}

#行间写法,需要在元素前加上字符 '-'
array: 
  - 1
  - 2
  - 3

yaml应用实例

  • 完成bean的数据绑定
package com.yangyi.springboot.bean;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Projectname: configuration
 * @Filename: Monster
 * @Author: 杨逸
 * @Data:2023/9/28 10:45
 * @Description: 演示yaml使用的实体类
 */
@ConfigurationProperties(prefix = "monster")
@Component
@Data
public class Monster {
    private Integer id;
    private String name;
    private Integer age;
    private Boolean isMarried;
    private Date birthday;
    private Cat cat;
    private String[] skills;
    private List<String> hobby;
    private Map<String,Object> wife;
    private Set<Double> salary;
    private Map<String,List<Cat>> cats;
}

package com.yangyi.springboot.bean;


import lombok.Data;

/**
 * @Projectname: configuration
 * @Filename: Cat
 * @Author: 杨逸
 * @Data:2023/9/28 10:43
 * @Description: 演示yaml使用的实体类
 */
@Data
public class Cat {
    private String name;
    private Double price;
}

  • 引入SpringBoot配置文件处理器
<!--引入配置绑定处理器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <!--版本不用指定会与Springboot父工程保持一致-->
            <!--防止依赖传递到其他模块-->
            <optional>true</optional>
        </dependency>
  • application.yaml文件
#对象
monster:
  #字面量
  id: 1
  name: 牛魔王
  age: 1000
  isMarried: true
  birthday: 2019/01/12
  #对象
  cat: {name:  兰博基尼, price: 10000000}
  #数组
  skills: [吹牛, 打篮球]
  hobby:
    - 吃饭
    - 喝水
  wife:
    key1: 爱丽丝
    key2: 鲍勃
  salary:
    - 1000
    - 200
    - 300
  cats:
   cat1:
     - {name: 宝马,price: 1001}
     - {name: 奔驰,price: 1901}
   cat2:
     - {name: 奥迪,price: 1002}
     - name: 劳斯莱斯
       price: 1299
server:
  port: 9999

SpringBoot中的静态资源的访问

  • 在类加载路径下的/static/public/resources/META-INF/resources下的静态资源可以被直接访问,对应WebPorperties.java文件中的配置
  • 静态资源访问的流程,先找控制器是否有匹配的路径,如果有就执行控制器的方法,如果没有就查找静态资源目录,如果找到就返回,没有则报错
  • 可以通过spring.mvc.static-path-pattern修改访问静态资源路径前缀
#修改静态资源访问前缀
spring:
  mvc:
    static-path-pattern: /resourceTest/**
  • 通过spring.web.resources.static-locations修改可以直接访问的静态资源路径
  • 注意修改会覆盖默认的路径,可以手动将默认的路径添加上
#修改静态资源访问前缀
spring:
  mvc:
    static-path-pattern: /resourceTest/**
#修改静态资源的存放路径
  web:
    resources:
#修改静态资源访问路径,会覆盖默认的路径,需要手动将默认的路径添加上
      static-locations: ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/",classpath:/imgs/]

SpringBoot中的Rest风格请求

  • 与springMVC中的语法一致,也需要配置HiddenMethodFilter过滤器
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true #开启隐藏HttpMethodFilter,支持表单的Restful风格请求
package com.yangyi.springboot.Controller;

import org.springframework.web.bind.annotation.*;

/**
 * @Projectname: SpringBootWeb
 * @Filename: MonsterController
 * @Author: 杨逸
 * @Data:2023/9/30 12:52
 * @Description: 演示Rest风格请求的控制器
 */
@RestController
public class MonsterController {

    @GetMapping("/monster")
    public String getMonster(){
        return "get---monster";
    }
    
    @PostMapping("/monster")
    public String postMonster(){
        return "post---monster";
    }
    
    @PutMapping("/monster")
    public String putMonster(){
        return "put---monster";            
    }
    
    @DeleteMapping("/monster")
    public String deleteMonster(){
        return "delete---monster";        
    }
}

SpringBoot中使用RestTemplate发起http请求

  • RestTemplateSpring提供的用于访问Rest服务的模板类,类似于JdbtTemplate用访问数据库
  • RestTemplate提供了多种便捷访问远程Http服务的方法
  • 通过RestTemplate,我们可以发出http请求(支持Restful,风格),去调用Controller提供的API接口,就像我们使用浏览器发出http请求,调用该API接口一样
package com.yangyi.springboot.controller;

import com.yangyi.springboot.entity.Member;
import com.yangyi.springboot.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @Projectname: e-commerce-center
 * @Filename: ConsumerController
 * @Author: 杨逸
 * @Data:2023/10/4 15:15
 * @Description: 控制器
 */
@RestController
@Slf4j
public class ConsumerController {
    //定义一个会员服务模块的基础url
    private final String MEMBER_SERVICE_URL = "http://127.0.0.1:10000";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/getMemberById/{id}")
    public Result getMemberById(@PathVariable("id") Long id){
        //使用RestTemplate向会员服务模块发起get类型的http请求
        //第一个参数是会员服务模块的url地址,第二个参数是响应参数的类型,第三个参数是返回值类型
        Result result = restTemplate.getForObject(MEMBER_SERVICE_URL + "/member/getMemberById/" + id, null, Result.class);

        //将会员服务模块返回的结果返回给调用者
        return result;
    }

    @PostMapping("/consumer/saveMember")
    public Result saveMember(@RequestBody Member member){
        //使用RestTemplate向会员服务模块发起post类型的http请求
        //第一个参数是会员服务模块的url地址,第二个参数是请求携带的参数,第三个参数是返回值类型
        Result result = restTemplate.postForObject(MEMBER_SERVICE_URL + "/member/saveMember", member, Result.class);
        return result;
    }
}

SpringBoot中配置视图解析器

  • 需要注意,视图解析器prefix前缀需要与static-path-pattern静态资源路径前缀保持一致
spring:
  mvc:
    static-path-pattern: /resourceTest/** #修改静态资源的存放路径
   view: #配置视图解析器
     prefix: /resourceTest/ #修改视图解析前缀,需要与static-path-pattern静态资源路径前缀保持一致
     suffix: .html

SpringBoot中接受参数的注解使用

  • 主要的注解有:@PathVariable,@RequestHeader,@ModelAtrribute,@RequestParam,@CookieValue,@RequestBody,@RequestAttribute,@SessionAttribute
  • 应用案例:
  • 测试的接口
package com.yangyi.springboot.Controller;

import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Projectname: SpringBootWeb
 * @Filename: ParamController
 * @Author: 杨逸
 * @Data:2023/9/30 14:09
 * @Description: 演示Springboot获取参数的控制器
 */
@RestController
public class ParamController {

    /**
     *
     * @param id 路径参数中的id参数
     * @param name  路径参数中的name参数
     * @param map 将路径参数和请求参数封装到map中
     * @return
     */
    @GetMapping("/pathVariable/{id}/{name}")
    public String pathVariable(@PathVariable("id") Integer id, @PathVariable("name") String name,@PathVariable Map<String,String> map){
        System.out.println("id = " + id);
        System.out.println("name = " + name);
        return "success";
    }

    /**
     *
     * @param host 发起请求的主机
     * @param headerMap 请求头的所有参数封装到一个map中
     * @return
     */
    @GetMapping("/header")
    public String header(@RequestHeader("Host") String host,@RequestHeader Map<String,String> headerMap){
        System.out.println("host = " + host);
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            System.out.println("entry key= " + entry.getKey() + " values= " + entry.getValue());
        }
        return "success";
    }

    /**
     *
     * @param name 请求中的name参数
     * @param hobby 请求中的hobby参数
     * @param map 将请求中的所有参数封装到map中,注意存在多个同名参数时,只能获取一个,因为同一个key只能有一个值
     * @return
     */
    @GetMapping("/param")
    public String param(@RequestParam("name") String name, @RequestParam("hobby") List<String> hobby, @RequestParam Map<String,String> map){
        System.out.println("name = " + name);
        for (String s : hobby) {
            System.out.println("s = " + s);
        }
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println("entry = " + entry.getKey() + " values = " + entry.getValue());
        }
        return "success";
    }

    /**
     *
     * @param cookie_val 指定cookie的值
     * @param cookie cookie对象
     * @return
     */
    @GetMapping("/cookie")
    public String cookie(@CookieValue(value = "name") String cookie_val, @CookieValue(value = "user") Cookie cookie){
        System.out.println("cookie_val = " + cookie_val);
        System.out.println("cookie = " + cookie.getName() + " =" + cookie.getValue());
        return "success";
    }

    /**
     * 使用 @RequestBody 注解将表单中参数按字符串获取,也可以将表单中参数封装为对象
     * @param name
     * @return
     */
    @PostMapping("/requestBody")
    public String requestBody(@RequestBody(required = false) String name){
        System.out.println("name = " + name);
        return "success";
    }
}

package com.yangyi.springboot.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttribute;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * @Projectname: SpringBootWeb
 * @Filename: RequestController
 * @Author: 杨逸
 * @Data:2023/9/30 14:43
 * @Description: 演示获取request域和session域的值
 */
@Controller
public class RequestController {

    /**
     * 设置参数的方法
     * @param request
     * @param session
     * @return
     */
    @RequestMapping("/login")
    public String login(HttpServletRequest request, HttpSession session){
        //设置request域的值
        request.setAttribute("name","杨逸");
        //设置session域的值
        session.setAttribute("name","韩顺平");
        return "forward:ok";
    }

    /**
     * 获取request域和session域的值
     * @param requestAttribute
     * @param sessionAttribute
     * @return
     */
    @RequestMapping("/ok")
    @ResponseBody
    public String ok(@RequestAttribute(value = "name",required = false) String requestAttribute, @SessionAttribute(value = "name",required = false) String sessionAttribute){
        System.out.println("requestAttribute = " + requestAttribute);
        System.out.println("sessionAttribute = " + sessionAttribute);
        return "success";
    }
}

  • 测试的页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index页面</title>
</head>
<body>
<h1>演示SpringBoot获取参数的页面</h1>
<a href="/pathVariable/100/king">@PathVariable注解获取路径参数</a><br>
<a href="/header">@RequestHeader注解获取请求头的参数</a><br>
<a href="/param?name=杨逸&hobby=篮球&hobby=跳舞">@RequestParam注解获取请求中的参数</a><br>
<a href="/cookie">@CookieValue注解获取请求中的Cookie</a><br>
<!--<a href="/requestBody?name=hsp">@RequestBody注解按字符串获取请求中的参数,也可以按json格式封装为对象</a><br>-->
<a href="/login">@RequestAttribute注解获取request域中的数据</a><br>
<a href="/login">@SessionAttribute注解获取session域中的数据</a><br>
<hr>
<hr>

<form action="/requestBody" method="post">
    姓名:<input type="text" name="name"><br>
    <input type="submit">
</form>
</body>
</html>

SpringBoot中获取复杂参数

  • 比如复杂参数:Map、Model、Errors/BindingResult、RedirectAttributes、ServletResponse、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder、HttpSession
  • 请求的参数都会方法request域中,Map和Modle中的数据也会放到request域中
 /**
     * 模拟登陆的接口
     * @return
     */
    @RequestMapping("/register")
    public String register(Map<String,String> map, Model model, HttpServletResponse response){
        //向map添加信息
        map.put("username","杨逸");
        map.put("password","123456");
        //向model添加信息
        model.addAttribute("salary","10000");
        //向response添加cookie
        response.addCookie(new Cookie("email","yangyi@163.com"));
        return "forward:/registerOk";
    }

    @RequestMapping("/registerOk")
    @ResponseBody
    public String registerOk(HttpServletRequest request){
        //从request域中获取信息
        System.out.println("request.getAttribute(\"username\") = " + request.getAttribute("username"));
        System.out.println("request.getAttribute(\"password\") = " + request.getAttribute("password"));
        System.out.println("request.getAttribute(\"salary\") = " + request.getAttribute("salary"));
        return "success";
    }

SpringBoot中获取自定义对象参数

  • 在开发中,SpringBoot在响应客户端/浏览器请求时,也支持自定义对象参数
  • 完成自动类型转换与格式化
  • 支持级联封装
  • 案例
 @RequestMapping("/save")
    public String saveMonster(Monster monster){
        System.out.println("monster = " + monster);
        return "success";
    }
  • 测试页面
  • 注意级联属性的赋值方式
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取自定义对象的测试页面</title>
</head>
<body>
<form action="/save" method="post">
    编号:<input type="text" name="id" value="1"><br>
    姓名:<input type="text" name="name" value="张三"><br>
    年龄:<input type="text" name="age" value="20"><br>
    婚姻:<input type="text" name="isMarried" value="true"><br>
    生日:<input type="text" name="birthday" value="1990/01/01"><br>
    坐骑:<input type="text" name="car.name" value="汽车"><br>
    价格:<input type="text" name="car.price" value="1000"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

自定义转换器

  • 转换器可以实现数据类型间的转换
  • Spring中内置多种基本转换器,如果有需要,我们也可以自定义转换器
  • 注意:转换器是保存在Map中的,所有同一种类型的转换器只有一个,加入多个同类型转换器会被覆盖,只保留最后一个注册的转换器
  • 实现自定义转换器,将String类型转换为Car类
package com.yangyi.springboot.config;

import com.yangyi.springboot.bean.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Projectname: SpringBootWeb
 * @Filename: WebConfig
 * @Author: 杨逸
 * @Data:2023/9/30 16:26
 * @Description: 演示注入自定义转换器的配置类
 */
//启用Lite轻量模式
@Configuration(proxyBeanMethods = false)
public class WebConfig {

    //转换器是保存在Map中的,所有同一种类型的转换器只有一个,加入多个同类型转换器会被覆盖,只保留最后一个注册的转换器
    //1.注入一个转换器
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        //2.返回WebMvcConfigurer接口的匿名内部类
        return new WebMvcConfigurer(){
            //3.通过addFormatters()方法注入一个转换器
            @Override
            public void addFormatters(FormatterRegistry registry) {
                //4.使用FormatterRegistry对象注册一个转换器
                //增添加一个字符串转Car类型的转换器

                registry.addConverter(new Converter<String, Car>() {
                    //5.实现转换方法
                    @Override
                    public Car convert(String source) {
                        //6.实现转换的的具体逻辑
                        if(!ObjectUtils.isEmpty(source)){
                            String[] split = source.split(",");
                            return new Car(split[0],Double.valueOf(split[1]));
                        }
                        return null;
                    }
                });
            }
        };
    }
}

  • 测试的页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取自定义对象的测试页面</title>
</head>
<body>
<form action="/save" method="post">
    编号:<input type="text" name="id" value="1"><br>
    姓名:<input type="text" name="name" value="张三"><br>
    年龄:<input type="text" name="age" value="20"><br>
    婚姻:<input type="text" name="isMarried" value="true"><br>
    生日:<input type="text" name="birthday" value="1990/01/01"><br>
    坐骑:<input type="text" name="car" value="好坐骑,3232"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
  • 测试的接口
 @RequestMapping("/save")
    public String saveMonster(Monster monster){
        System.out.println("monster = " + monster);
        return "success";
    }

处理JSON格式数据

  • 使用@ResponseBody注解可以返回客户端指定需要的数据,如果请求头中的Accept属性是*/*则默认返回JSON格式的数据
  • 使用@RequestBody注解可以接受并解析客户端发送过来指定格式的数据

内容协商

  • 客户端是通过请求头中的Accept属性指定可接受的数据类型,默认是*/*可以接受所有的数据类型,默认返回的是JSON格式的数据是因为后端无法处理其他格式的数据

  • text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7,Accept属性的的详解:数据类型的排列顺序表示可接受数据类型的优先级,q=0.9:表示权重为0.9,如果优先级高的数据类型无法处理,则处理优先级次一级的,如果都可以处理,则处理权重高的
  • 客户端是通过请求头中的ContentType属性指定发送的数据类型
  • 案例:通过指定Accept接受返回xml格式的数据
  • 返回xml格式的数据,需要引入处理xmljar
 <!--引入处理xml的jar包-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
  • 测试接口
 @RequestMapping("/getMonster")
    @ResponseBody
    public Monster  getMonster() {
        Monster monster = new Monster();
        monster.setId(1);
        monster.setName("小怪兽");
        monster.setAge(10);
        monster.setBirthday(new Date());
        Car car = new Car();
        monster.setCar(car);
        car.setName("小汽车");
        car.setPrice(7878.43);
        return monster;
    }
  • 测试结果

开启基于请求参数的内容协商功能

  • 浏览器的发起的请求我们无法修改其Accept的值,我们又要返回指定格式的数据时,可以使用基于请求参数的内容协商功能
  • 开启基于请求参数的内容协商功能,并指定请求参数的名称,默认是:format
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true #开启支持请求参数中包含MediaType
      parameter-name: hspformat #指定MediaType请求参数名称,默认是format,指定为hspformat
  • 测试默认的请求参数返回json格式数据:http://localhost:8080/getMonster?format=json

  • 测试指定的请求参数返回xml格式数据:http://localhost:8080/getMonster?hspformat=xml

Thymeleaf(服务端渲染技术)

  • 在SpringMVC中充当view视图
  • 若要使用Thymeleaf语法,首先要声明名称空间:xmIns:th="http://www.thymeleaf.org"
  • 设置文本内容th:text,设置input的值th:value,循环输出th:each,条件判断th:if,插入代码块th:insert,定义代码块th:fragment,声明变量th:object
  • th:each的用法需要格外注意,打个比方:如果你要循环一个div中的p标签,则th:each属性必须放在p标签上,若你将th:each属性放在div上,则循环的是将整个div。
  • 变量表达式中提供了很多的内置方法,该内置方法是用#开头,请不要与#{}消息表达式弄混。

Thymeleaf语法

  • 表达式
表达式名称语法用途
变量取值${}获取request域,session域,对象等的值
选择变量*{}获取上下文变量
消息#{}获取国际化等值
链接@{}生成链接
片段表达式~{}jsp:include的作用,引入公共页面片段
  • 运算符

    • 数学运算符
      1. +
      2. -
      3. *
      4. /
      5. %
    • 逻辑运算符
      1. 与:and
      2. 或:or
      3. 非:!,not
    • 比较运算符
    关键字运算符
    gt>
    ge>=
    eq==
    lt<
    le<=
    ne!=
    • 条件运算符
      1. If-then:(if)?(then)
      2. If-then-else:(if)?(then):(else)
      3. Deafult:(value)?:(defaultvalue)

Thymeleaf综合案例

  • 用户管理系统
  • 登陆页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登陆页面</title>
    <style>
        h1{
            text-align: center;
        }
        div{
            width: 500px;
            height: 500px;
            /*border: 1px solid red;*/
            margin: 50px auto;
            text-align: center;
        }
        h3{
            margin-top: 50px;
        }
        form{
            margin-top: 50px;
        }
    </style>
</head>
<body>
<h1>登陆页面</h1>
<div>
    <h3>用户登录</h3>
    <!--提交的超链接-->
    <form action="#" th:action="@{/login}" method="post">
        <!--错误信息的回显-->
        <label style="color:red" th:text="${msg}"></label><br>
        用户名:<input type="text" name="username"><br><br>
        密码:<input type="password" name="password"><br><br>
        <input type="submit" value="登录">
        <input type="reset" value="重新填写">
    </form>
</div>
</body>
</html>
  • 管理页面
<!DOCTYPE html>
<!--引入thymeleaf命名空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta charset="UTF-8">
    <title>管理页面</title>
    <style>
        h1{
            text-align: center;
        }
        div{
            width: 500px;
            /*border: 1px solid red;*/
            margin: 50px auto;
            text-align: center;
        }
        h3{
            margin-top: 50px;
        }
      th,td{
          text-align: center;
          width: 100px;
          border: antiquewhite solid 1px;
          background: aliceblue;
      }

    </style>
</head>
<body>
<h1>管理页面</h1>
<!--超链接表达式,以及条件判断-->
<pre> <a href="#" th:href="@{/login}">返回首页</a> <a  href="#" th:href="@{/logout}">安全退出</a>      欢迎:<span th:if="${session.admin}" th:text="${session.admin.username}">xxx</span></pre>
<hr>
<div>
    <h3>管理雇员</h3>
    <table>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>pwd</th>
            <th>email</th>
            <th>age</th>
        </tr>
        <!--遍历列表的显示-->
        <tr th:each="user:${users}">
            <td th:text="${user.id}">1</td>
            <td th:text="${user.username}">xx</td>
            <td th:text="${user.password}">xx</td>
            <td th:text="${user.email}">xx</td>
            <td th:text="${user.age}">xx</td>
        </tr>
    </table>
</div>
<hr>
</body>
</html>

Springboot中的拦截器

  • Spring Boot项目中,拦截器是开发中常用手段,要来做登陆验证、性能检查、日志记录等

  • 基本步骤

    1. 编写一个拦截器实现HandlerInterceptor接口
    2. 拦截器注册到配置类中(实现WebMvcConfigurer的addInterceptors)
    3. 指定拦截规则
    4. 回顾SpringMVC中讲解的Interceptor
  • 拦截器

package com.yangyi.springboot.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Projectname: springboot-usersys
 * @Filename: LoginInterceptor
 * @Author: 杨逸
 * @Data:2023/10/1 11:14
 * @Description: 登陆拦截器
 */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("拦截器拦截请求的URL:"+ requestURI);
        if (request.getSession().getAttribute("admin")!=null){
            //用户已登陆,放行
            return true;
        }
        //用户未登陆,跳转到登陆页面
        request.setAttribute("msg", "请先登录");
        request.getRequestDispatcher("/login").forward(request, response);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle方法被执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion方法被执行");
    }
}

  • 注册拦截器方式一:
  • 通过注入一个WebMvcConfigurer的内部类实现拦截器注册
  • 通过addPathPatterns()方法配置拦截路径
  • 通过excludePathPatterns()方法配置放行路径
package com.yangyi.springboot.config;

import com.yangyi.springboot.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Projectname: springboot-usersys
 * @Filename: WebConfig
 * @Author: 杨逸
 * @Data:2023/10/1 11:22
 * @Description: 配置类
 */
@Configuration
public class WebConfig {

    @Bean
     //注册拦截器方式一
    public WebMvcConfigurer addInterceptor(){
        return new WebMvcConfigurer() {
            //重写addInterceptors方法
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
               //创建拦截器
                LoginInterceptor interceptor = new LoginInterceptor();
               //注册拦截器
                registry.addInterceptor(interceptor)    //配置拦截规则
                        .addPathPatterns("/**")     //拦截所有请求
                        .excludePathPatterns("/","/login","/imgs/**");     //不拦截的路径

            }
        };
    }
}

  • 注册拦截器方式二:
  • 通过配置类实现WebMvcConfigurer接口,直接重写addInterceptors()方法注册拦截器
package com.yangyi.springboot.config;

import com.yangyi.springboot.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Projectname: springboot-usersys
 * @Filename: WebConfig
 * @Author: 杨逸
 * @Data:2023/10/1 11:22
 * @Description: 配置类
 */
@Configuration
//通过配置类直接实现WebMvcConfigurer接口也可以注册拦截器
public class WebConfig implements  WebMvcConfigurer{

   
    //注册拦截器方式二
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //创建拦截器
        LoginInterceptor interceptor = new LoginInterceptor();
        //注册拦截器
        registry.addInterceptor(interceptor)    //配置拦截规则
                .addPathPatterns("/**")     //拦截所有请求
                .excludePathPatterns("/","/login","/imgs/**");     //不拦截的路径
    }
}

URIURL的区别

  • URI = Universal Resource Identifier
  • ``URL = Universal Resource Locator`
  • Identifier:标识符,Locator:定位器
  • URI可以唯一标识一个资源,URL可以提供找到该资源的路径

SpringBoot中的文件上传

  • 页面
  • 表单使用enctype="multipart/form-data"类型,用于上传文件
  • 上传多个文件使用multiple属性
<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
    <style>
        h1{
            text-align: center;
        }
        div{
            width: 500px;
            height: 500px;
            /*border: 1px solid red;*/
            margin: 50px auto;
            text-align: center;
        }
        h3{
            margin-top: 50px;
        }
        form{
            margin-top: 50px;
        }
    </style>
</head>
<body>
<h1>用户注册</h1>
<div>
    <form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username"><br><br>
        密码:<input type="password" name="password"><br><br>
        邮件:<input type="email" name="email"><br><br>
        年龄:<input type="number" name="age"><br><br>
        头像:<input type="file" name="header"><br><br>
        <!--使用multiple,可以上传多个图片-->
        宠物:<input type="file" name="pets" multiple><br><br>
        <input type="submit" value="提交">
        <input type="reset" value="重置">
    </form>
</div>
</body>
</html>
  • 接口
  • 使用MultipartFile类型接收上传的文件,接受多个文件时使用MultipartFile数组即可
  • 使用transferTo方法将数据写入文件保存
package com.yangyi.springboot.controller;

import com.yangyi.springboot.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.util.UUID;

/**
 * @Projectname: springboot-usersys
 * @Filename: UploadController
 * @Author: 杨逸
 * @Data:2023/10/1 14:28
 * @Description: 文件上传控制器
 */
@Controller
@Slf4j
public class UploadController {

    @GetMapping("/upload.html")
    public String uploadPage(){
        log.info("uploadPage");
        //转发到文件上传页面
        return  "upload";
    }

    /**
     *
     * @param user 用信息
     * @param header 用户头像
     * @param pets 用户宠物图片
     * @return
     */
    @PostMapping("/upload")
    @ResponseBody
    public String upload(User user, @RequestParam("header") MultipartFile header, @RequestParam("pets")MultipartFile[] pets) throws IOException, URISyntaxException {
        //使用MultipartFile类型接受文件数据,多个文件使用数组接收
        log.info("接受到的信息user={},header={},pets={}",user,header,pets);

        //保存文件,保存到指定文件夹
        //String path = "D:\\2023\\Spring Boot\\upload\\";

        //动态创建文件夹保存文件,保存在类路径下的/static/imgs/文件下
        //String path = ResourceUtils.getURL("classpath:").toURI().getPath();
        String path = getClass().getClassLoader().getResource("").toURI().getPath();
        log.info("类路径path={}",path);
        path = path + "static/imgs/";
        //拼接日期创建文件夹
        for (String date : LocalDate.now().toString().split("-")) {
            path += date + "/";
        }
        log.info("文件夹路径path={}",path);

        //创建文件夹
        File folder = new File(path);
        if (!folder.exists())folder.mkdirs();
        //头像图片
        if (!header.isEmpty()){
            //创建文件对象,使用UUID和时间戳防止文件重名
            File file = new File(path + UUID.randomUUID().toString()+"-"+ System.currentTimeMillis() +"-"+ header.getOriginalFilename());
            //将数据写入文件
            header.transferTo(file);
        }

        //宠物图片
        if (pets.length>0){
            for (MultipartFile pet:pets){
                if (!pet.isEmpty()){
                    //创建文件对象
                    File file = new File(path + UUID.randomUUID().toString()+"-"+ System.currentTimeMillis() +"-"+ pet.getOriginalFilename());
                    //将数据写入文件
                    pet.transferTo(file);
                }
            }
        }
        log.info("保存文件成功");;
        return "upload success";
    }
}

  • 配置上传文件的大小限制
spring:
  servlet:
    multipart:
      max-file-size:  10MB # 单个文件大小,默认是1MB
      max-request-size: 100MB # 设置总上传的数据大小,默认是10MB

SpringBoot中的异常处理

  • 默认情况下,Spring Boot提供/error处理所有错误的映射,也就是说当出现错误时,SpringBoot底层会情求转发到/error这个映射
  • 局部异常
  • 全局异常
  • 默认异常

自定义异常页面

  • 编写错误页面,以错误代码为页面文件的名称,并存放在静态资源路径下的'/error'路径下,发生错误时Springboot会自动去寻找对应错误代码的页面,如果没有则寻找4xx.html页面或者5xx.html页面,找到则返回
  • 假如发生404错误,SpringBoot会先去寻找404.html错误页面,如果没有找到,则寻找4xx.html页面,如果找到则直接返回

  • 错误页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>404错误页面</title>
    <style>
        h1{
            text-align: center;
        }
        div{
            width: 500px;
            height: 500px;
            /*border: 1px solid red;*/
            margin: 50px auto;
            text-align: center;
        }
        h3{
            margin-top: 50px;
        }

    </style>
</head>
<body>
<h1>错误页面</h1>
<div>
    <h3>404 Not Found</h3>
    <h3>状态码:<span th:text="${status}"></span></h3>
    <h3>错误信息:<span th:text="${error}"></span></h3>
    <p>对不起,您访问的页面不存在或已被删除</p>
    <p>您可以:<a href="#" th:href="@{/}">返回首页</a></p>
</div>
</body>
</html>
  • 测试的接口
package com.yangyi.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Projectname: springboot-usersys
 * @Filename: MyErrorController
 * @Author: 杨逸
 * @Data:2023/10/1 16:53
 * @Description: 模拟发生错误的控制器
 */
@Controller
public class MyErrorController {
    /**
     * 模拟服务器发生错误 状态码为500
     * @return
     */
    @RequestMapping("/errorByZero")
    public String error(){
        int i = 10/0;
        return "/";
    }

    /**
     * 模拟请求方法错误 状态码为405
     * @return
     */
    @PostMapping("/errorByPost")
    public String error2(){
        return "loginPage";
    }
}

全局异常处理

  • @ControllerAdvice+@ExceptionHandler处理全局异常
  • 底层是ExceptionHandlerExceptionResolver异常解析器支持的
  • 案例:
  • 演示全局异常使用,当发生ArithmeticExceptionNullPointerException时,不使用默认异常机制匹配的XXX.html,而是通过全局异常机制显示指定的错误页面
package com.yangyi.springboot.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Projectname: springboot-usersys
 * @Filename: GlobalExceptionHandler
 * @Author: 杨逸
 * @Data:2023/10/1 17:10
 * @Description: 全局异常处理器
 */
//使用@RestControllerAdvice注解,表示该类是全局异常处理器
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 处理算术异常
     * @param e
     * @param model
     * @return
     */
    @ExceptionHandler(value = {ArithmeticException.class})
    public String handleAriteException(ArithmeticException e, Model model){
        log.info("算术异常{}",e.getMessage());
        //将异常信息传递给页面
        model.addAttribute("msg",e.getMessage());
        return "error/global";
    }
}

SpringBoot中自定义异常

  • 如果Spring Boot提供的异常不能满足开发需求,程序员也可以自定义异常
  • 使用@ResponseStatus注解自定义异常
  • 底层是ResponseStatusExceptionResolver响应状态处理器,底层调用response.sendError(statusCode,resolvedReason)
  • 当抛出自定义异常后,仍然会根据状态码,去匹配使用XXx.html显示
package com.yangyi.springboot.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * @Projectname: springboot-usersys
 * @Filename: AccessException
 * @Author: 杨逸
 * @Data:2023/10/1 19:54
 * @Description: 自定义访问异常
 */
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "没有权限,非法访问")
public class AccessException extends  RuntimeException{
    public AccessException() {
        super();
    }

    public AccessException(String message) {
        super(message);
    }
}

Interceptor拦截器与Filter过滤器的区别

  • 使用范围不同
    • 过滤器实现的是javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,Filter只能在web程序中使用
    • 拦截器(Interceptor)它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单
      独使用的。不仅能应用在web程序中,也可以用于Application等程序中
  • 触发时机不同
graph LR;
subgraph Tomcat;
	subgraph Filter;
		subgraph Servlet;
			subgraph Interceptor;
				subgraph Controller;

				end
			end
		end
	end
end
tomcat[(Tomcat)] --1--> filter[Filter];
filter --2--> servlet[Servlet];
servlet --3--> interceptor[Interceptor];
interceptor --4--> controller[(Controller)];
controller -.5.-> interceptor;
interceptor -.6.-> servlet;
servlet -.7.->filter;
filter -.8.->tomcat;

  • 过滤器不会处理请求转发,拦截器会处理请求转发,因为请求转发经过了拦截器而没有经过过滤器

SpringBoot中注入Servlet,Filter,Listener

  • 分别使用@WebServlet,@WebFilter,@WebListener注解注入Servlet,Filter,Listener,使用这种方式注入的Servlet不会被SpringBoot的拦截器拦截,因为不会经过disPatcherServlet前端分发控制器
  • 因为一个请求先通过tomcat,然后通过filter过滤器,然后通过servlet,如果在servlet匹配成功,就不需要通过disPatcherServlet前端分发控制器,所有就不会通过拦截器,拦截器是基于disPatcherServlet前端分发控制器的
  • 将自定义Servlet注入到Spring容器中,需要使用@ServletComponentScan注解配置扫描路径才能注入
@ServletComponentScan(value = "com.yangyi.springboot")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

使用@WebServlet注解注入Servlet

  • 使用@WebServlet注解来标识一个Servlet
  • urlPatterns属性指定要拦截的请求的路径
package com.yangyi.springboot.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Projectname: springboot-usersys
 * @Filename: MyServlet
 * @Author: 杨逸
 * @Data:2023/10/1 20:17
 * @Description: 自定义的servlet
 */
//使用@WebServlet注解来映射servlet
//urlPatterns属性指定要拦截的请求的路径
//将自定义Servlet注入到Spring容器中,需要使用@ServletComponentScan注解配置扫描路径才能注入
@WebServlet(urlPatterns = {"/myServlet01","/myServlet02"},name = "myServlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("自定义的servlet");
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("自定义的servlet");
    }
}

使用@WebFilter注解注入Filter

  • 在SpringBoot中,自定义过滤器需要实现Filter接口
  • 使用@WebFilter注解标注一个过滤器
  • urlPatterns属性指定需要拦截的请求路径
package com.yangyi.springboot.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Projectname: springboot-usersys
 * @Filename: Myfilter
 * @Author: 杨逸
 * @Data:2023/10/1 20:25
 * @Description: 自定义过滤器
 */
//在SpringBoot中,自定义过滤器需要实现Filter接口
//使用@WebFilter注解标注过滤器,
//urlPatterns属性指定需要拦截的请求路径
//注入spring容器需要使用@ServletComponentScan注解
@WebFilter(urlPatterns = {"/*"})
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("自定义过滤器执行了");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("自定义过滤器执行结束了");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("自定义过滤器初始化了");
    }

    @Override
    public void destroy() {
        System.out.println("自定义过滤器销毁了");
    }
}

使用@WebListener注入Listener

  • 实现具体的监听器接口
  • 使用@WebListener注解标注一个监听器
  • 将自定义Listener注入到Spring容器中,需要使用@ServletComponentScan注解配置扫描路径才能注入
package com.yangyi.springboot.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * @Projectname: springboot-usersys
 * @Filename: MyListener
 * @Author: 杨逸
 * @Data:2023/10/1 20:40
 * @Description: 自定义监听器
 */
//实现具体的监听器接口
//使用@WebListener注解标注一个Listener监听器
//将自定义Listener注入到Spring容器中,需要使用@ServletComponentScan注解配置扫描路径才能注入
@Slf4j
@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("contextInitialized初始化成功");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("contextDestroyed销毁成功");
    }
}

使用配置类注入原生Servlet,Filter,Listener

  1. 先编写Servlet,Filter,Listener
  2. 通过注入对应的XxxRegistrationBean
    1. 实例化对应的servlet
    2. 设置拦截url
    3. 返回对应的XxxRegistrationBean
  • 注册原生Servlet,将原生Servlet传入ServletRegistrationBean,然后将其返回即可
  • 注册原生Filter,使用FilterRegistrationBean
  • 注册原生Listener,使用ServletListenerRegistrationBean
package com.yangyi.springboot.config;

import com.yangyi.springboot.servlet.MyFilter;
import com.yangyi.springboot.servlet.MyListener;
import com.yangyi.springboot.servlet.MyServlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: springboot-usersys
 * @Filename: MyRegisterConfig
 * @Author: 杨逸
 * @Data:2023/10/1 21:21
 * @Description: 注入原生Servlet,Filter,Listener的配置类
 */
@Configuration
public class MyRegisterConfig {

    /**
     *  注册原生Servlet,使用ServletRegistrationBean
     * @return
     */
    @Bean
    public ServletRegistrationBean<MyServlet> myServlet(){
        //实例化servlet
        MyServlet myServlet = new MyServlet();
        //实例化servlet注册bean,配置拦截url
        ServletRegistrationBean<MyServlet> servletRegistrationBean = new ServletRegistrationBean<>(myServlet, "/myServlet03","/myServlet04");
        //返回
        return servletRegistrationBean;
    }

    /**
     *   注册原生Filter,使用FilterRegistrationBean
     * @return
     */
    @Bean
    public FilterRegistrationBean<MyFilter> myFilter(){
        //实例化filter
        MyFilter myFilter = new MyFilter();
        //实例化filter注册bean,
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>(myFilter);
        //配置拦截url
        filterRegistrationBean.addUrlPatterns("/*","/template/*");
        //返回
        return  filterRegistrationBean;
    }

    /**
     *   注册原生Listener,使用ServletListenerRegistrationBean
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<MyListener> myListener(){
        //实例化listener
        MyListener myListener = new MyListener();
        //实例化listener注册bean
        ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(myListener);
        //返回
        return servletListenerRegistrationBean;
    }
}

SpringBootTomcat常用配置

  • 通过配置文件配置Tomcat
  • Tomcat的配置与ServerPropesties.java配置类相关联
server:
  port: 9999 #配置服务端口
  tomcat: #配置tomcat常用配置
    threads: #配置工作线程数
      min-spare: 10 #最小工作线程数,默认是10
      max: 200 #最大工作线程数,默认是200
    accept-count: 100 #最大等待队列数,默认是100
    max-connections: 8192 #最大连接数(并发数),默认是8192
    connection-timeout: 10000 #连接超时时间,默认没有设置,单位为毫秒
  • 通过配置类配置Tomcat
package com.yangyi.springboot.config;

import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: springboot-usersys
 * @Filename: MyTomcatConfig
 * @Author: 杨逸
 * @Data:2023/10/2 10:50
 * @Description: 通过配置类配置tomcat
 */
@Configuration
public class MyTomcatConfig implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
        //设置服务端口
        factory.setPort(8888);
        //可以通过ConfigurableServletWebServerFactory类的一系列方法配置tomcat
    }
}

切换Web服务器

  • SpringBoot的默认Web服务器是Tomcat
  • web启动器中排除tomcat服务器的jar
  • 引入undertow服务器的jar
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--    在web启动器中排除tomcat服务器的jar-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--    引入undertow服务器的jar-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

SpringBoot中的数据库操作

使用默认的数据源

  • 引入需要的jar
  • SpringBoot默认使用的数据源是HikariDataSource
<!--    进行数据库操作,引入spring-boot-starter-data-jdbc启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
    <!--    引入数据库对应的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
  • 配置数据库源
spring:
  datasource: #配置数据源
    url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: yangyi
    password: 2004
    driver-class-name: com.mysql.jdbc.Driver
  • 测试
  • SpringBoot程序的测试需要引入spring-boot-starter-test测试启动器,并使用@SpringbootTest注解标注测试类
<!--引入starter-test,测试SpringBoot项目    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
package com.yangyi.springboot;


import com.yangyi.springboot.bean.Furn;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Projectname: springboot-usersys
 * @Filename: ApplicationTest
 * @Author: 杨逸
 * @Data:2023/10/2 12:17
 * @Description: springboot测试类
 */
@SpringBootTest
public class ApplicationTest {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Test
    public void jdbcTest(){
        //创建一个映射类,帮助spring把数据库中的数据映射到java对象中
        BeanPropertyRowMapper<Furn> rowMapper = new BeanPropertyRowMapper<>(Furn.class);
        List<Furn> furns = jdbcTemplate.query("select * from furn", rowMapper);
        for (Furn furn : furns) {
            System.out.println("furn = " + furn);
        }
        System.out.println("jdbcTemplate.getDataSource() = " + jdbcTemplate.getDataSource());
    }
}

Druid数据源手动整合到Springboot

  • Druid:性能优秀,Druid提供性能卓越的连接池功能外,还集成了SQL监控,黑名单拦截等功能,强大的监控特性,通过Duid提供的监控功能,可以清楚知道连接池和SQL的工作情况

  • 官方文档

  • 引入druid数据源的jar

   <!--    引入druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>
  • 注入druid数据源到spring
  • 注入druid数据源,覆盖默认数据源
  • 使用@ConfigurationProperties注解注入数据源的连接信息
package com.yangyi.springboot.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @Projectname: springboot-usersys
 * @Filename: DruidDataSourceConfig
 * @Author: 杨逸
 * @Data:2023/10/2 12:37
 * @Description: druid数据源配置类
 */
@Configuration
public class DruidDataSourceConfig {
    //注入druid数据源,覆盖默认数据源
    //使用@ConfigurationProperties注解注入数据源的连接信息
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();

        return dataSource;
    }
}

开启Druid的监控功能

  • Druid的监控功能通过StatViewServlet实现,我们需要注入一个StatViewServlet
  • 通过StatViewServletaddInitParameter方法设置初始化参数,包括访问监控的用户名和密码
  /**
     * 注入一个druid的监控视图servlet,开启监控页面的访问
     * @return
     */
    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*");
        //通过addInitParameter方法设置初始化参数
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
        //设置访问druid的用户名和密码
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","2004");
        return servletRegistrationBean;
    }

启用Druid的sql监控

  • 在注入数据源时,添加名为stat过滤器
 @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        //开启sql监控功能
        dataSource.setFilters("stat");
        return dataSource;
    }

配置druidweb关联监控

  • 启用druidweb关联健康,可以监控请求的URI
  • 注入一个WebStatFilter的监控过滤器,开启监控过滤器
  • 通过setUrlPatterns方法设置监控的路径
  • 通过addInitParameter方法添加初始化参数,设置不监控的路径
/**
     * 启用druid的web关联健康,可以监控请求的URI
     * 注入一个druid的监控过滤器,开启监控过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilterFilter(){
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
        //配置监控的路径
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
        //配置不监控的路径
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.html,*.swf");
        return filterRegistrationBean;
    }

配置druidsql防火墙

  • 在注入数据源时,添加名为wall过滤器
@ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        //开启sql监控功能,开启sql防火墙功能
        dataSource.setFilters("stat,wall");
        return dataSource;
    }

通过starter-druid启动器整合Druid数据源

   <!--    引入starter-druid启动器-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
  • 配置druid,并开启监控的功能
spring:
  datasource: #配置数据源
    url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: yangyi
    password: 2004
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      enable: true #1.是否启用druid,默认是false
      stat-view-servlet: #2.配置druid的监控功能
        login-username: admin #登录druid监控界面的用户名,默认是null
        login-password: 2004 #登录druid监控界面的密码,默认是null
        url-pattern: /druid/* #访问监控页面的路径,默认是/druid/*
        enabled: true
      web-stat-filter: #3.配置druid的web关联监控功能
        enabled: true #是否启用web监控,默认是false
        url-pattern: /* #配置监控的页面路径
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.html,*.swf" #配置忽略的页面
      filter:
        stat: #4.sql监控功能
          enabled: true
          slow-sql-millis: 1000 #配置sql慢查询的时间
          log-slow-sql: true          #是否开启慢查询日志
        wall: #5.sql防火墙功能
          enabled: true
          config:
            drop-table-allow: false #是否允许使用drop table语句
            select-all-column-allow: false #是否允许使用select *语句
  1. 启用Druid

    spring:
      datasource: #配置数据源
        url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: yangyi
        password: 2004
        driver-class-name: com.mysql.jdbc.Driver
        druid:
          enable: true #1.是否启用druid,默认是false
    
  2. 配置监控功能

     stat-view-servlet: #2.配置druid的监控功能
            login-username: admin #登录druid监控界面的用户名,默认是null
            login-password: 2004 #登录druid监控界面的密码,默认是null
            url-pattern: /druid/* #访问监控页面的路径,默认是/druid/*
            enabled: true
    
  3. web关联监控

     web-stat-filter: #3.配置druid的web关联监控功能
            enabled: true #是否启用web监控,默认是false
            url-pattern: /* #配置监控的页面路径
            exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.html,*.swf" #配置忽略的页面
    
  4. sql监控功能

    filter:
            stat: #4.sql监控功能
              enabled: true
              slow-sql-millis: 1000 #配置sql慢查询的时间
              log-slow-sql: true          #是否开启慢查询日志
    
  5. sql防火墙功能

    filter:
            wall: #5.sql防火墙功能
              enabled: true
              config:
                drop-table-allow: false #是否允许使用drop table语句
                select-all-column-allow: false #是否允许使用select *语句
    

    SpringBoot整合Mybatis

    • 引入mybatis启动器
     <!--    引入mybatis启动器-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.2</version>
            </dependency>
    
    • 配置连接信息,数据源
    server:
      port: 9999
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: yangyi
        password: 2004
        driver-class-name: com.mysql.jdbc.Driver
        druid:
          enable: true
    
    • 配置XxxMapper.xml映射文件路径
    mybatis:
      #  指定mapper文件位置
      mapper-locations:  classpath:mapper/*.xml
    
    • 编写XxxMapper接口
    • SpringBoot中使用@Mapper注解标注一个mybatis映射接口,将接口注册到Spring容器中
    package com.yangyi.springboot.mapper;
    
    import com.yangyi.springboot.bean.Monster;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * @Projectname: springboot_mybatis
     * @Filename: MonsterMapper
     * @Author: 杨逸
     * @Data:2023/10/2 15:50
     * @Description: 映射接口
     */
    //在SpringBoot中使用@Mapper注解标注一个mybatis映射接口,将接口注册到Spring容器中
    @Mapper
    public interface MonsterMapper {
        Monster getMonsterById(Integer id);
    }
    
    
    • 编写XxxMapper.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.yangyi.springboot.mapper.MonsterMapper">
    
        <sql id="base_fileds">id,name,age,sex,birthday,email,salary</sql>
        <select id="getMonsterById" parameterType="java.lang.Integer" resultType="com.yangyi.springboot.bean.Monster">
            select <include refid="base_fileds"/> from monster where id=#{id}
        </select>
    
    </mapper>
    

    SpringBootmybatis的配置

    • 即可配置传统的mybatis-config.xml进行开发,也可以直接在application.yaml文件上直接配置mybatis的相关配置
    mybatis:
      #  指定mapper文件位置
      mapper-locations:  classpath:mapper/*.xml
      #指定mybatis核心配置文件的配置,按照传统的方式进行开发,
      #也可以直接在application.yaml文件中直接进行mybatis的配置
      #config-location:   classpath:mybatis-config.xml
      
      #配置开启别名
      type-aliases-package:  com.yangyi.springboot.bean
      #配置mybatis的全局配置
      configuration:
        #配置标准日志输出
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 解决时区问题,通过@JsonFormat注解
        //通过@JsonFormat注解实现日期格式化
        //GMT表示格林尼治标准时间,+8,是因为中国在东八区
        @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
        private Date birthday;
    
    • 测试
    package com.yangyi.springboot;
    
    
    import com.yangyi.springboot.bean.Monster;
    import com.yangyi.springboot.mapper.MonsterMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import javax.annotation.Resource;
    
    /**
     * @Projectname: springboot_mybatis
     * @Filename: ApplicationTest
     * @Author: 杨逸
     * @Data:2023/10/2 15:35
     * @Description: 测试类
     */
    @SpringBootTest
    @Slf4j
    public class ApplicationTest {
        @Resource
        private JdbcTemplate jdbcTemplate;
        @Resource
        private MonsterMapper monsterMapper;
    
        @Test
        public void main() {
            log.info("数据源={}",jdbcTemplate.getDataSource());
        }
    
        @Test
        public void getMonster(){
            Monster monster = monsterMapper.getMonsterById(1);
            log.info("monster={}",monster);
        }
    }
    

SpringBoot整合Mybatis-plus

  • MyBatis-PIus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生
  • 强大的CRUD操作:内置通用Mapper、通用Service,通过少量配置即可实现单表大部分CRUD操作,再有强大的条件构浩器,满足各类使用需求
  • 引入mybatis-plus启动器
    <!--    引入mybatis-plus启动器-->
        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.4</version>
        </dependency>
  • 配置数据源
server:
  port: 9999
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: yangyi
    password: 2004
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      enable: true
  • 配置mybatis-plus,配置方式与mybatis基本一致
mybatis-plus:
  configuration:
    # 配置日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 配置mapper文件路径,与mybatis一致
  mapper-locations: classpath:mapper/*.xml

开发Mapper接口

  • 使用@Mapper标注为一个mybatis映射接口并注入Spring
  • mybatis不同的是,需要继承一个BaseMapper基础类,并指定对应的泛型
  • BaseMapper中实现了基本的curd操作,简化开发
package com.yangyi.springboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yangyi.springboot.bean.Monster;
import org.apache.ibatis.annotations.Mapper;

/**
 * @Projectname: springboot_mybatis_plus
 * @Filename: MonsterMapper
 * @Author: 杨逸
 * @Data:2023/10/2 17:21
 * @Description: 映射接口
 */
//BaseMapper中已经定义了基本的增删改查方法,可以直接继承使用
//如果默认定义的方法不能满足需求,可以重写方法,也可以在新增方法,在对应Xxx.Mapper实现即可
@Mapper
public interface MonsterMapper extends BaseMapper<Monster> {
}

开发Service层接口

  • mybatis-plusIService定义了基础的crud操作
  • 使用mybatis-plusService层需要继承IService接口,并指定泛型
package com.yangyi.springboot.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.yangyi.springboot.bean.Monster;

/**
 * @Projectname: springboot_mybatis_plus
 * @Filename: MonsterService
 * @Author: 杨逸
 * @Data:2023/10/2 17:34
 * @Description: 映射接口
 */
//mybatis-plus的IService定义基础的crud操作
//使用mybatis-plus的Service层需要继承IService,并指定泛型
public interface MonsterService extends IService<Monster> {
}

实现Service层的接口

  • 使用mybatis-plus的服务实现类需要继承ServiceImpl,并指定泛型为对应的映射接口和对应的实体类
  • ServiceImpl实现了IService中的所有crud方法,所以直接继承即可,继承后就不需要我们手动实现
package com.yangyi.springboot.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yangyi.springboot.bean.Monster;
import com.yangyi.springboot.mapper.MonsterMapper;
import com.yangyi.springboot.service.MonsterService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Projectname: springboot_mybatis_plus
 * @Filename: MonsterServiceImpl
 * @Author: 杨逸
 * @Data:2023/10/2 17:49
 * @Description: 接口实现
 */
//使用mybatis-plus的服务实现类需要继承ServiceImpl,并指定泛型为对应的映射接口和对应的实体类
// 再实现服务接口
//ServiceImpl实现了IService中的所有crud方法,所以直接继承即可
@Service
public class MonsterServiceImpl extends ServiceImpl<MonsterMapper, Monster> implements MonsterService {
    @Resource
    private MonsterMapper monsterMapper;
}

配置扫描mapper接口

  • 使用@MapperScan注解扫描所有mybatisdao接口
  • 如果不想使用扫描的方式,也可以使用@Mapper注解进行单独的注入
package com.yangyi.springboot;

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

/**
 * @Projectname: springboot_mybatis_plus
 * @Filename: Application
 * @Author: 杨逸
 * @Data:2023/10/2 17:18
 * @Description: 主程序
 */
//使用@MapperScan注解扫描所有mybatis的dao接口
@MapperScan(value = {"com.yangyi.springboot.mapper"})
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new SpringApplication(Application.class).run(args);
        System.out.println("(♥◠‿◠)ノ゙  启动成功   ლ(´ڡ`ლ)゙  \n" +
                "  ____                _           _   \n" +
                " / ___|  ___ __ _ _ __| |__   ___ | |_ \n" +
                " \\___ \\ / __/ _` | '__| '_ \\ / _ \\| __|\n" +
                "  ___) | (_| (_| | |  | | | | (_) | |_ \n" +
                " |____/ \\___\\__,_|_|  |_| |_|\\___/ \\__|\n" +
                "                                       ");
        System.out.println("项目启动成功!");

    }
}

@TableName注解的使用

  • 用于配置实体类对应的表名,用于解决实体名与表名不一致的情况
  • 默认实体类对应的表名为类名首字母小写,比如:Monster --> monster
  • 当类与表保持一致时,可以不添加该注解
package com.yangyi.springboot.bean;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @Projectname: springboot_mybatis_plus
 * @Filename: Monster
 * @Author: 杨逸
 * @Data:2023/10/2 17:20
 * @Description: 实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
//@TableName注解用于配置实体类对应的表名,用于解决实体名与表名不一致的情况
@TableName(value = "monster")
public class Monster {
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    //通过@JsonFormat注解实现日期格式化
    //GMT表示格林尼治标准时间,+8,是因为中国在东八区
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    private Date birthday;
    private double salary;
    private Character sex;
}

@TableId注解的使用

  • 使用该注解标注一个属性为表的主键
  • 通过type属性设置主键的类型为自动增长类型
 /**
     * 使用@TableId(type = IdType.AUTO)注解标识一个属性为表的主键,
     * 通过type属性设置主键的类型为自动增长类型
     * 
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

Mtbatis-plus中的分页查询

  • 注入一个Myabtis-plus拦截器,并指定数据库的类型
    1. 创建一个Myabtis-plus拦截器
    2. Myabtis-plus拦截器注入一个分页拦截器
    3. 指定数据库的类型
package com.yangyi.springboot.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: springboot_furn
 * @Filename: MybatisPlusConfig
 * @Author: 杨逸
 * @Data:2023/10/3 14:38
 * @Description: 配置类
 */
@Configuration
public class MybatisPlusConfig {
    //注入一个分页插件,本质是一个拦截器

    @Bean
    public MybatisPlusInterceptor  mybatisPlusInterceptor() {
        //创建分页拦截器
        MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
        //创建一个内部拦截器,指定数据库类型
        plusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return plusInterceptor;
    }

}

  • 进行分页查询
    1. 创建一个分页对象
    2. 使用mybatis-plus自带的分页查询方法进行分页查询
    //分页查询的接口
    @GetMapping("/furniture/list/page")
    public Result listFurnitureByPage(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "5") Integer pageSize){
        //通过mybatis-plus自带的分页查询方法进行分页查询
        Page<Furniture> page = new Page(pageNum, pageSize);
        Page<Furniture> furniturePage = furnitureService.page(page);
        return Result.success(furniturePage);
    }
  • 带条件的分页查询
    1. 创建分页模型
    2. 创建条件查询器
    3. 加入条件
    4. 进行查询
 @GetMapping("/furniture/list/condition")
    public Result listFurnitureByCondition(@RequestParam(value = "pageSize",defaultValue = "5") Integer pageSize,@RequestParam(value = "pageNum",defaultValue = "1")Integer pageNum,@RequestParam(value = "name",defaultValue = "")String name){
        //使用mybatis-plus自带的条件查询
        Page<Furniture> page = new Page(pageNum, pageSize);
        //创建查询条件构造器
        QueryWrapper<Furniture> wrapper = new QueryWrapper<>();
        //按名字模糊查询
        QueryWrapper<Furniture> queryWrapper = wrapper.like("name", name);
        //进行查询
        Page<Furniture> furniturePage = furnitureService.page(page, queryWrapper);
        return Result.success(furniturePage);
    }

mybatisPlus字段自动填充Handler

  • 实现MetaObjectHandler接口,写一个自己的字段填充Handler
  • 加上Component注解,注入spring容器
  • 注意需要在对应的实体类上指定要自动填充的时机,不然不会生效
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Integer userId = null;
        try {
            userId = SecurityUtils.getUserId();
        } catch (Exception e) {
            e.printStackTrace();
            userId = -1;//表示是自己创建
        }
        System.out.println("插入拦截器触发");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("createBy",userId , metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
        this.setFieldValByName("updateBy", userId, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
        try {
            this.setFieldValByName("updateBy", SecurityUtils.getUserId(), metaObject);
        } catch (Exception e) {
            this.setFieldValByName("updateBy", -1, metaObject);
            //e.printStackTrace();
        }
    }
}

  • 在对应实体上的字段加上@TableField注解
  • 通过注解的fill属性指定什么情况下进行填充,使用FieldFill的枚举值
    • FieldFill.INSERT:插入数据时,进行字段填充
    • FieldFill.UPDATE:修改数据时,进行字段填充
    • FieldFill.INSERT_UPDATE:插入或修改数据时,进行字段填充
    • FieldFill.DELETE:删除数据时,进行字段填充
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_user")
public class User implements Serializable {
    //主键
    @TableId(type = IdType.AUTO)
    private Integer userId;
    //用户名
    private String userName;
    //账号
    private String userAccount;
    //密码
    private String userPassword;
    //用户角色
    private String userRole;
    //状态
    private String userState;

    @TableField(fill = FieldFill.INSERT)
    private Long createBy;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    //删除标志(0代表未删除,1代表已删除)
    private Integer delFlag;
}