首页 > 其他分享 >深入学习SpringBoot

深入学习SpringBoot

时间:2022-09-28 13:55:12浏览次数:86  
标签:String class private public 学习 book 深入 id SpringBoot

1. 快速上手SpringBoot

1.1 SpringBoot入门程序开发

  • SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程

1.1.1 IDEA创建SpringBoot步骤

  • 创建Spring Initializr模块

  • 开发一个基于RESTful风格的简单控制器

    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @GetMapping
        public String getById(){
            System.out.println("SpringBoot is running");
            return "SpringBoot is running";
        }
    }
    
  • 运行Application

注意:

  • 开发SpringBoot:程序可以根据向导进行联网快速制作

  • SpringBoot程序需要基于JDK8进行制作

  • SpringBoot程序中需要使用何种功能通过勾选选择技术

  • 运行SpringBoot程序通过运行Application程序入口进行

  • 在使用IDEA创建SpringBoot项目时,由于https://start.spring.io/连接不稳定,因此可以使用阿里云的仓库:https://start.aliyun.com/

    • 注意:
      • 阿里云提供的坐标版本较低,如果需要使用高版本,进入工程后手工切换SpringBoot版本
      • 阿里云提供的工程模板与Spring官网提供的工程模板略有不同

1.1.2 Spring官网创建SpringBoot项目步骤

  • 打开SpringBoot官网,选择Quickstart Your Project
  • 创建工程,并保存项目
  • 解压项目,通过IDE导入项目

1.1.3 手工制作SpringBoot项目步骤

  • 创建无骨架的Maven工程

  • 修改pom文件:添加继承和依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
    </parent>
    
    org.springframework.boot spring-boot-starter-web ​```
  • 创建Application类

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

在通过网络创建好SpringBoot项目之后,会出现很多我们目前不需要的文件,如.mvn文件夹、.gitignore等文件。因此我们可以设置隐藏文件或文件夹

image

1.2 浅谈入门程序的工作原理

  • parent

    • 开发SpringBoot程序要继承spring-boot-starter-parent
    • spring-boot-starter-parent中定义了若干个依赖管理
    • 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
    • 也可以采用引入依赖的形式实现效果
      • 通过阿里云创建的SpringBoot项目中的pom文件中没有使用parent继承,而是直接通过dependencyManagement导入spring-boot-dependencies依赖
  • starter

    • 在starter中还包含了其他的starter,如spring-boot-starter-web中包含了spring-boot-starter-tomcat

      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
          <version>2.7.3</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>5.3.22</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.3.22</version>
          <scope>compile</scope>
        </dependency>
      </dependencies>
      
    • starter是SpringBoot中常见的项目名称,定义了当前项目中使用的所有依赖坐标,以达到减少依赖配置的目的

      • parent
        • 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
        • 在spring-boot-starter-parent各版本间存在着诸多坐标版本不同
    • 开发SpringBoot程序需要导入坐标时通常导入对应的starter

    • 每个不同的starter根据功能不同,通常包含多个依赖坐标

    • 在实际开发中

      • 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本V
      • 如发生坐标错误,再指定Version(要小心版本冲突)
  • 引导类

    • SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目

      @SpringBootApplication
      public class Springboot0101QuickstartApplication {
      
          public static void main(String[] args)   {
              SpringApplication.run(Springboot0101QuickstartApplication.class, args);
          }
      
      }
      
    • SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean

  • 内嵌tomcat

    在配置中引入了spring-boot-starter-web坐标,spring-boot-starter-web中引入了spring-boot-starter-tomcat坐标,在其中使用了tomcat-embed-core:内嵌的tomcat核心

    • 将tomcat的容器执行过程抽取出来编程一个对象,将这个对象交给了Spring容器去管理

    • 可以替换内嵌tomcat为jetty服务器:使用exclusions可以排除依赖

      <exclusions>
          <exclusion>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-tomcat</artifactId>
          </exclusion>
      </exclusions>
      
    • 这时再添加要使用的服务器依赖

    SpringBoot内置了三款服务器:

    • tomcat:apache出品,粉丝多,应用面广,负载了若干较重的组件
    • jetty:更轻量级,负载性能远不及tomcat
    • undertow:负载性能勉强跑赢tomcat

2. 基础配置

  • 小技巧:复制模块
    • 制作模板:
      • 打开工作空间,复制一份对应工程,改名为springboot_0x_0x_xxxxxxxx
      • 打开该文件夹,打开pom,修改artifactId为springboot_01_0x_xxxxxxxxxx
      • 删除name标签(可选)
      • 删除文件夹中不需要的文件,使得最终只剩src目录和pom文件
    • 使用模板工程:
      • 修改pom文件中artifactId与新工程/模块名相同

2.1 属性配置

  • 修改服务器端口

    • 在SpringBoot默认配置文件application.properties中修改,使用键值对修改

      # 服务器端口配置
      server.port=80
      
  • banner配置

    # 隐藏banner
    spring.main.banner-mode=off
    
    #修改banner
    spring.banner.image.location=logo.png
    
  • log日志配置

    #日志级别设置
    #logging.level.root=debug
    #logging.level.root=error
    logging.level.root=info
    #logging.level.com.mark=warn
    

2.2 配置文件分类

  • SpringBoot提供了多种属性配置方式

    • application.properties

      # 服务器端口配置
      server.port=80
      
    • application.yml(主流格式)

      server:
        port: 81
        servlet:
        context-path: /test1
      
    • application.yaml

      server:
        port: 82
      
  • 配置文件加载优先级:

    • 如果三个文件都存在,properties为第一配置文件,yml为第二配置文件,yaml为第三配置文件。即如果三个文件有相同的配置,优先级为properties->yml->yaml

2.3 yaml数据格式

  • YAML(YAML Ain't Markup Language),一种数据序列化格式

  • 优点:

    • 容易阅读
    • 容易与脚本语言交互
    • 以数据为核心,重数据轻格式
  • YAML文件扩展名

    • .yml(主流)
    • .yaml
  • yaml语法格式

    • 大小写敏感
    • 属性层级关系使用多行描述,每行结尾使用冒号结束
    • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tb键)
    • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
    • #表示注释
    country: china
    province: beijing
    city: beijing
    area: haidian
    
    party: true
    
    birthday: 1949-10-01
    
    #多级
    user:
      name: zhangsan
      age: 21
    
    a:
      b:
        c:
          d:
            e: 666
    
    #数组格式
    likes:
      - game
      - music
      - java
    #换种写法:
    likes2: [ game,music,java ]
    
    #对象数组格式
    users:
      - name: zhangsan
        age: 21
      - name: lisi
        age: 22
    #换种写法:
    users2: [ { name:zhangsan,age:21 },{ name:lisi,age:22 } ]
    
    server:
      port: 80  
    

    ​```

    核心规则:数据前面要加空格与冒号隔开

2.4 yaml数据读取

package com.mark.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName BookController
 * @Description TODO 基于REST模式的控制器
 * @Author Mark
 * @Date 2022/9/17 19:56
 * @Version 1.0
 */
@RestController
@RequestMapping("/books")
public class BookController {

    //读取yaml中的单一数据
    @Value("${country}")
    private String country;

    //读取多级
    @Value("${user.name}")
    private String name1;

    //读取数组
    @Value("${likes[1]}")
    private String likes1;

    //读取数组对象
    @Value("${users[1].age}")
    private String age1;

    @GetMapping
    public String getById() {
        System.out.println("SpringBoot is running");
        System.out.println("country===>" + country);
        System.out.println("name1===>" + name1);
        System.out.println("likes1===>" + likes1);
        System.out.println("age1===>" + age1);
        return "SpringBoot is running";
    }
}

使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名....}

  • yaml文件中的变量引用

    baseDir: c:\windows
    
    #使用${属性名}引用数据
    tempDir: ${baseDir}\temp
    #使用引号包裹的字符串,其中的转义字符可以生效
    tempDir1: "${baseDir}\temp"
    
    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @Value("${tempDir}")
        private String tempDir;
    
        @GetMapping
        public String getById() {
            System.out.println("tempDir===>" + tempDir);
            return "SpringBoot is running";
        }
    }
    
  • ​读取yaml的全部属性数据

    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        //使用自动装配,将所有的数据封装到一个对象Environment
        @Autowired
        private Environment env;
    
        @GetMapping
        public String getById() {
            String name2 = env.getProperty("users2[0].name");
            System.out.println("name2===>" + name2);
            return "SpringBoot is running";
        }
    }
    
    
  • 读取yaml引用类型属性数据

    只读取部分属性,就需要封装部分属性,如下列例子,只封装datasource属性中的数据

    datasource:
      driver: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost/springboot_db
      username: root
      password: 123
    
    • 创建一个类,用于封装datasource数据

    • 由Spring加载数据到对象中,一定要告诉Spring加载这组信息

      //1.定义用于模型封装yaml文件中对应的数据
      
      @Data
      //2.定义为Spring管理的bean
      @Component
      //3.指定加载的属性
      @ConfigurationProperties(prefix = "datasource")
      public class MyDataSource {
          private String driver;
          private String url;
          private String username;
          private String password;
      }
      
    • 使用时直接从Spring获取信息使用

      @RestController
      @RequestMapping("/books")
      public class BookController {
          @Autowired
          private MyDataSource myDataSource;
      
          @GetMapping
          public String getById() {
              System.out.println(myDataSource);
              return "SpringBoot is running";
          }
      }
      
      

3. 整合第三方技术

3.1 整合JUnit

  • 导入测试对应的starter
  • 测试类使用@SpringBootTest修饰
  • 使用自动装配的形式添加要测试的对象
@SpringBootTest
class Springboot04JunitApplicationTests {
    @Autowired
    private BookDao bookDao;

    @Test
    void contextLoads() {
        bookDao.save();
    }
}

当SpringBoot测试类和引导类所在目录不同时,需要为@SpringBootTest添加classes属性

@SpringBootTest(classes = Springboot04JunitApplication.class)

3.2 整合MyBatis

  • 步骤

    • 创建全新boot工程

    • 选择当前模块需要使用的技术集(MyBatis、MySQL)

    • 设置数据源参数

      #配置相关信息
      spring:
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ssm_db
          username: root
          password: 123
      
      
    • 定义数据层接口与映射配置

      //数据库SQL映射需要添加@Mapper被容器识别到
      @Mapper
      public interface BookDao {
          @Select("select * from tbl_book where id = #{id};")
          public Book getById(Integer id);
      }
      
    • 测试

  • SpringBoot整合MyBatis常见问题

    • 当SpringBoot版本降低以后,会出现一些意外:

      • 需要设置数据库时区,可以在数据库服务器配,也可以在路径配置:jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC

      • Tomcat8和Tomcat5的驱动略有不同:

        Tomcat8:com.mysql.cj.jdbc.Driver

        Tomcat5:com.mysql.jdbc.Driver

3.3 整合MyBatis-Plus

SpringBoot官方并没有涵盖MyBatisPlus,因此不能在创建工程时直接导入

  • 两种解决办法:

    • 方式一:使用阿里云创建SpringBoot工程
    • 方式二:创建工程时只勾选MySQL,创建好工程后手工导入MP的坐标(正统)
  • 步骤

    • 创建工程

    • 设置数据源参数

      #配置相关信息
      spring:
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ssm_db
          username: root
          password: 123
      
      
    • 定义数据层接口,继承BaseMapper

      @Mapper
      public interface BookDao extends BaseMapper<Book> {
      }
      
    • 测试功能

  • 可能会遇到的问题:

    • domain的类名与表名不一致,无法查询:配置MP

      #设置MP相关的配置
      mybatis-plus:
        global-config:
          db-config:
            #在类名前增加字段tbl_使得类名与表名一致
            table-prefix: tbl_
      

3.4 整合Druid

  • 步骤:

    • 创建工程

    • 导入坐标

    • 修改配置

      • 方式一:
      #配置相关信息
      spring:
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
          username: root
          password: 123
          type: com.alibaba.druid.pool.DruidDataSource
      
      • 方式二:
      spring:
        datasource:
          druid:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
            username: root
            password: 123
      
    • 测试基于MyBatis的工程

3.5 整合SSMP案例

3.5.1 案例实现方案分析

  • 实体类开发——使用Lombok快速制作实体类
  • Dao开发——整合MyBatisPlus,制作数据层测试类
  • Service开发——基于MyBatisPlus进行增量开发,制作业务层测试类
  • Controller开发——基于Restful开发,使用PostMani测试接口功能
  • Controller开发——前后端开发协议制作
  • 页面开发——基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
    • 列表、新增、修改、删除、分页、查询
  • 项目异常处理
  • 按条件查询——页面功能调整、Controller修正功能、Service修正功能

3.5.2 模块创建

  • 创建工程,勾选SpringMVC与MySQL坐标,添加MP和druid

  • 修改配置文件为yml格式文件

  • 设置端口为80方便访问

  • 实体类开发

    @Data
    public class Book {
        private Integer id;
        private String type;
        private String name;
        private String description;
    }
    

3.5.3 数据层开发(基础CRUD)

  • 创建接口并继承BeanMapper<Book>

    @Mapper
    public interface BookDao extends BaseMapper<Book> {
    }
    
  • 配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)

    spring:
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
          username: root
          password: 123
    
    mybatis-plus:
      global-config:
        db-config:
          table-prefix: tbl_
          #数据库id自增
          id-type: auto
    
  • 测试实现增删改查

    @SpringBootTest
    class BookDaoTest {
        @Autowired
        private BookDao bookDao;
    
        @Test
        void testSave() {
            Book book = new Book();
            book.setType("测试数据123");
            book.setName("测试数据456");
            book.setDescription("测试数据789");
            //由于id自增没有设置,需要在破欸之文件中设置id自增
            bookDao.insert(book);
        }
    
        @Test
        void testUpdate() {
            Book book = new Book();
            book.setType("测试数据abc");
            book.setName("测试数据def");
            book.setDescription("测试数据789");
            book.setId(13);
            bookDao.updateById(book);
        }
    
        @Test
        void testDelete() {
            bookDao.deleteById(14);
        }
    
        @Test
        void testGetById() {
            System.out.println(bookDao.selectById(1));
        }
    
        @Test
        void testGetAll() {
            System.out.println(bookDao.selectList(null));
        }
    }
    

3.5.4 开启MP运行日志

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      #数据库id自增
      id-type: auto
  configuration:
  	#设置使用日志StdOutImpl
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.5.5 分页

  • 分页操作需要设定分页对象Ipage

    @Test
    void testGetPage() {
        //获取第一页数据,一页获取5条
        IPage page = new Page(2,5);
        //添加MP提供的拦截器实现limit
        bookDao.selectPage(page,null);
    }
    
  • 分页操作是在MyBatisP1us的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用myBatisPlus拦截器实现

    @Configuration
    public class MPConfig {
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            //添加拦截器
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            //添加分页拦截器
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            return interceptor;
        }
    }
    
  • Ipage对象中封装了分页操作中的所有数据

    • 数据:page.getRecords()
    • 当前页码值:page.getCurrent()
    • 每页数据总量:page.getSize()
    • 最大页码值:page.getPages()
    • 数据总量:page.getTotal()

3.5.6 按条件查询

@Test
void testGetBy() {
    //需要创建queryWrapper对象
    //QueryWrapper<Book> qw = new QueryWrapper<>();
    //最终写出的语句为:select * from tbl_book where name like %Spring%
    //qw.like("name","Spring");

    //为了防止name字符串拼错,使用lambdaQueryWrapper
    LambdaQueryWrapper<Book> lwq = new LambdaQueryWrapper<>();
    //lwq.like(Book::getName, "Spring");

    //String name = "Spring";
    //当表单传来的值为null时,会与预期效果有差别
    String name = null;
    //可以在两字段之前添加条件:当name!=null为true时查询有效
    lwq.like(name != null, Book::getName, name);

    //bookDao.selectList(qw);
    bookDao.selectList(lwq);
}

3.5.7 业务层开发

  • Service层接口定义与数据层接口定义具有较大区别,不要混用。业务层接口关注的是业务名称,数据层接口关注的是数据库相关的操作。一个面对数据库命名,一个面对服务命名

    • 数据层:selectByUserNameAndPassword(String username,String password);
    • 业务层:login(String username,String password);
  • 步骤

    • 定义接口

      public interface BookService {
          /**
           * 新增
           *
           * @param book
           * @return
           */
          Boolean save(Book book);
      
          /**
           * 修改
           *
           * @param book
           * @return
           */
          Boolean update(Book book);
      
          /**
           * 删除
           *
           * @param id
           * @return
           */
          Boolean delete(Integer id);
      
          /**
           * 通过id查询
           *
           * @return
           */
          Book getById(Integer id);
      
          /**
           * 查询所有
           *
           * @return
           */
          List<Book> getAll();
      
          /**
           * 分页查询
           *
           * @param currentPage
           * @param pageSize
           * @return
           */
          IPage<Book> getPage(int currentPage, int pageSize);
      
      }
      
      
    • 完成实现类

      @Service
      public class BookServiceImpl implements BookService {
          @Autowired
          private BookDao bookDao;
      
          @Override
          public Boolean save(Book book) {
              return bookDao.insert(book) > 0;
          }
      
          @Override
          public Boolean update(Book book) {
              return bookDao.updateById(book) > 0;
          }
      
          @Override
          public Boolean delete(Integer id) {
              return bookDao.deleteById(id) > 0;
          }
      
          @Override
          public Book getById(Integer id) {
              return bookDao.selectById(id);
          }
      
          @Override
          public List<Book> getAll() {
              return bookDao.selectList(null);
          }
      
          @Override
          public IPage<Book> getPage(int currentPage, int pageSize) {
              IPage page = new Page(currentPage, pageSize);
              bookDao.selectPage(page, null);
              return page;
          }
      }
      
      
    • 测试:

      @SpringBootTest
      public class BookServiceTest {
          @Autowired
          private BookService bookService;
      
          @Test
          void testGetById() {
              System.out.println(bookService.getById(2));
          }
      
          @Test
          void testSave() {
              Book book = new Book();
              book.setType("测试数据123");
              book.setName("测试数据456");
              book.setDescription("测试数据789");
              //由于id自增没有设置,需要在破欸之文件中设置id自增
              bookService.save(book);
          }
      
          @Test
          void testUpdate() {
              Book book = new Book();
              book.setType("测试数据abc");
              book.setName("测试数据def");
              book.setDescription("测试数据789");
              book.setId(13);
              bookService.update(book);
          }
      
          @Test
          void testDelete() {
              bookService.delete(13);
          }
      
          @Test
          void testGetAll() {
              System.out.println(bookService.getAll());
          }
      
          @Test
          void testGetPage() {
              IPage<Book> page = bookService.getPage(2, 5);
              System.out.println(page.getPages());
              System.out.println(page.getTotal());
              System.out.println(page.getRecords());
              System.out.println(page.getSize());
              System.out.println(page.getCurrent());
          }
      }
      
      

3.5.8 业务层快速开发

  • 快速开发方案

    • 使用MyBatisPlus提供有业务层通用接口(ISerivce\<T>)与业务层通用实现类(ServiceImpl\<M,T>)
    • 在通用类基础上做功能重载或功能追加
    • 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
  • 步骤

    • 定义业务层通用接口,继承IService类

      public interface IBookService extends IService<Book> {
      }
      
    • 完成业务层实现类

      //实现接口IBookService,并且继承ServiceImpl,提供两个泛型:BookDao和Book
      public class bookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
      
      }
      
      
    • 测试:

      @SpringBootTest
      public class BookServiceTestCase {
          @Autowired
          private IBookService bookService;
      
          @Test
          void testGetById() {
              System.out.println(bookService.getById(2));
          }
      
          @Test
          void testSave() {
              Book book = new Book();
              book.setType("测试数据123");
              book.setName("测试数据456");
              book.setDescription("测试数据789");
              //由于id自增没有设置,需要在破欸之文件中设置id自增
              bookService.save(book);
          }
      
          @Test
          void testUpdate() {
              Book book = new Book();
              book.setType("测试数据abc");
              book.setName("测试数据def");
              book.setDescription("测试数据789");
              book.setId(13);
              bookService.updateById(book);
          }
      
          @Test
          void testDelete() {
              bookService.removeById(13);
          }
      
          @Test
          void testGetAll() {
              System.out.println(bookService.list());
          }
      
          @Test
          void testGetPage() {
              IPage<Book> page = new Page<Book>(2, 5);
              bookService.page(page);
              System.out.println(page.getPages());
              System.out.println(page.getTotal());
              System.out.println(page.getRecords());
              System.out.println(page.getSize());
              System.out.println(page.getCurrent());
          }
      }
      
      

3.5.9 表现层开发

@RestController
@RequestMapping("/books/")
public class BookController {
    @Autowired
    private IBookService bookService;

    @GetMapping
    public List<Book> getAll() {
        return bookService.list();
    }

    @PostMapping
    public Boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    @PutMapping
    public Boolean update(@RequestBody Book book) {
        return bookService.modify(book);
    }

    @DeleteMapping("{id}")
    public Boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }

    @GetMapping("{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    @GetMapping("{cuuentPage}/{pageSize}")
    public IPage<Book> getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
        return bookService.getPage(currentPage, pageSize)
    }
}

3.5.10 表现层数据一致性处理

设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议

统一格式,前端接收数据格式:封装数据到data属性中,封装操作到code属性中,封装特殊消息到message(msg)属性中

  • 设置统一数据返回结果类

    @Data
    public class Result {
        private boolean flag;
        private Object data;
    
        public Result() {
    
        }
    
        public Result(boolean flag) {
            this.flag = flag;
        }
    
        public Result(boolean flag, Object data) {
            this.flag = flag;
            this.data = data;
        }
    }
    
  • 修改Controller的return数据

    @RestController
    @RequestMapping("/books/")
    public class BookController {
        @Autowired
        private IBookService bookService;
    
        @PostMapping
        public Result save(@RequestBody Book book) {
    //        Result result = new Result();
    //        boolean flag = bookService.save(book);
    //        result.setFlag(flag);
            return new Result(bookService.save(book));
        }
    
        @PutMapping
        public Result update(@RequestBody Book book) {
            return new Result(bookService.modify(book));
        }
    
        @DeleteMapping("{id}")
        public Result delete(@PathVariable Integer id) {
            return new Result(bookService.delete(id));
        }
    
        @GetMapping("{id}")
        public Result getById(@PathVariable Integer id) {
            return new Result(true, bookService.getById(id));
        }
    
        @GetMapping
        public Result getAll() {
            return new Result(true, bookService.list());
        }
    
        @GetMapping("{currentPage}/{pageSize}")
        public Result getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
            return new Result(true, bookService.getPage(currentPage, pageSize));
        }
    }
    
    

3.5.11 前后端调用(axios发送异步请求)

前后端分离结构设计中页面归属前端服务器

单体工程中页面放置在resources目录下的static目录中(建议执行clean)

前端发送异步请求,调用后端接口

//列表
getAll() {
    //发送异步请求
    axios.get("/books").then((res)=>{
        console.log(res.data);
    });
},

3.5.12 数据处理

  • 列表功能

    getAll() {
        //发送异步请求
        axios.get("/books").then((res)=>{
            //console.log(res.data);
            this.dataList = res.data.data;
        });
    }
    
  • 添加功能

    //弹出添加窗口
    handleCreate() {
        this.dialogFormVisible = true;
        this.resetForm();
    },
    
    //添加
    handleAdd() {
        axios.post("/books", this.formData).then((res) => {
            //判断当前操作是否成功
            if (res.data.flag) {
                //关闭弹层
                this.dialogFormVisible = false;
                this.$message.success("添加成功!");
            } else {
                this.$message.error("添加失败");
            }
        }).finally(() => {
            //重新加载数据
            this.getAll();
        });
    },
    
    //重置表单
    resetForm() {
        this.formData = {};
    },
    
    //取消
    cancel() {
        this.dialogFormVisible = false;
        this.$message.info("当前操作取消");
    },
    
  • 删除功能

    //将当前行数据传入
    handleDelete(row) {
        //console.log(row);
        this.$confirm("此操作不可逆,是否继续?", "警告", {type: "info"}).then(()=>{
            //console.log("ok");
            axios.delete("/books/"+row.id).then((res) => {
                if (res.data.flag){
                    this.$message.success("删除成功");
                }else{
                    this.$message.error("删除失败");
                }
            }).finally(()=>{
                this.getAll();
            });
        }).catch(()=>{
            //console.log("cancel");
            this.$message.info("取消操作");
        });
    },
    
  • 修改功能(加载数据)

    //弹出编辑窗口
    handleUpdate(row) {
        axios.get("/books/" + row.id).then((res) => {
          	//判断是否查询到该条数据且该条数据是否为空
            if (res.data.flag && res.data.data != null) {
              	//弹出窗口
                this.dialogFormVisible4Edit = true;
              	//设置formData数据为查询到的数据
                this.formData = res.data.data;
            } else {
                this.$message.error("数据同步失败,自动刷新");
            }
        }).finally(() => {
            //重新加载数据
            this.getAll();
        });
    },
    
  • 修改功能

    //编辑提交
    handleEdit() {
        axios.put("/books", this.formData).then((res) => {
            //判断当前操作是否成功
            if (res.data.flag) {
                //关闭弹层
                this.dialogFormVisible4Edit = false;
                //弹出消息
                this.$message.success("修改成功!");
            } else {
                this.$message.error("修改失败");
            }
        }).finally(() => {
            //重新加载数据
            this.getAll();
        });
    },
    
  • 异常消息处理

    image

    • SpringMVC提供了专门的异常处理器,对异常进行统一处理,出现异常后,返回指定信息
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
        //拦截所有的异常信息
        @ExceptionHandler
        public Result doException(Exception ex) {
            //记录日志
            //通知运维
            //通知开发
            ex.printStackTrace();
            return new Result("服务器故障,请联系管理员");
        }
    }
    
    
    • 修改表现层返回结果的模型类,封装出现异常后对应的信息

      • flag:false
      • Data:null
      • 消息(msg):要显示信息
      @Data
      public class Result {
          private boolean flag;
          private Object data;
          private String msg;
      
          public Result(boolean flag, String msg) {
              this.flag = flag;
              this.msg = msg;
          }
      
          public Result(String msg) {
              this.flag = false;
              this.msg = msg;
          }
      }
      
    • 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息

      //编辑提交
      handleEdit() {
          axios.put("/books", this.formData).then((res) => {
              //判断当前操作是否成功
              if (res.data.flag) {
                  //关闭弹层
                  this.dialogFormVisible4Edit = false;
                  //弹出消息
                  this.$message.success(res.data.msg);
              } else {
                  this.$message.error(res.data.msg);
              }
          }).finally(() => {
              //重新加载数据
              this.getAll();
          });
      },
      
  • 分页

    • 页面使用el分页组件添加分页功能

      <!--分页组件-->
      <div class="pagination-container">
          <el-pagination
                  class="pagiantion"
                  @current-change="handleCurrentChange"
                  :current-page="pagination.currentPage"
                  :page-size="pagination.pageSize"
                  layout="total, prev, pager, next, jumper"
                  :total="pagination.total">
          </el-pagination>
      </div>
      
    • 定义分页组件需要使用的数据并将数据绑定到分页组件

      data: {
          pagination: {
              currentPage: 1,
              pageSize: 10,
              total: 0
          },//分页模型数据,暂时弃用
      },
      
    • 替换查询功能为分页功能

      getAll() {
          //发送异步请求
          axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
              //console.log(res.data);
            	//取当前页的条数
              this.pagination.pageSize = res.data.data.size;
            	//取当前页码值
              this.pagination.currentPage = res.data.data.current;
            	//取数据总数
              this.pagination.total = res.data.data.total;
            	//取数据
              this.dataList = res.data.data.records;
          });
      },
      
    • 分页切换页码值

      //切换页码:
      handleCurrentChange(currentPage) {
          //修改页码值为当前的页码值
          this.pagination.currentPage = currentPage;
          //执行查询
          this.getAll();
      },
      
  • 分页功能维护(删除BUG)

    当页面只有一条数据,删除后显示出现问题:无数据显示,显示的还是刚刚最后一页的数据。使得当前页比页面总数还大。

    后台补救性方案:修改Controller分页查询

    @GetMapping("{currentPage}/{pageSize}")
    public Result getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
        IPage<Book> page = bookService.getPage(currentPage, pageSize);
        //对当前查询结果进行校验,如果当前页码值大于总页码值,重新执行查询操作,使用最大页码值作为查询页码值
        if (currentPage > page.getPages()){
            page = bookService.getPage((int) page.getPages(), pageSize);
        }
        return new Result(true,page);
    }
    
  • 条件查询

    • 查询条件数据封装

      • 单独封装

      • 与分页操作混合封装

        pagination: {
            currentPage: 1,
            pageSize: 10,
            total: 0,
            type: "",
            name: "",
            description: ""
        }
        
  • 页面数据模型绑定

    <!--条件查询-->
    <div class="filter-container">
        <el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;"
                  class="filter-item"></el-input>
        <el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;"
                  class="filter-item"></el-input>
        <el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;"
                  class="filter-item"></el-input>
        <el-button @click="getAll" class="dalfBut">查询</el-button>
        <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
    </div>
    
  • 组织数据成为get请求发送的数据

    CgetAll() {
        //组织参数,拼接URL请求参数
        // console.log(this.pagination.name);
        param = "?type=" + this.pagination.type;
        param += "&name=" + this.pagination.name;
        param += "&description" + this.pagination.description;
        //console.log(param);
        //发送异步请求
        axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
            //console.log(res.data);
            this.pagination.pageSize = res.data.data.size;
            this.pagination.currentPage = res.data.data.current;
            this.pagination.total = res.data.data.total;
            this.dataList = res.data.data.records;
        });
    },
    
    • 条件参数组织可以通过条件判定书写的更简洁
  • Controller接收参数

    @GetMapping("{currentPage}/{pageSize}")
    public Result getPage(@PathVariable int currentPage, @PathVariable int pageSize,Book book) {
        IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
        //如果当前页码值大于总页码值,重新执行查询操作,使用最大页码值作为查询页码值
        if (currentPage > page.getPages()){
            page = bookService.getPage((int) page.getPages(), pageSize,book);
        }
        return new Result(true,page);
    }
    
  • 业务层接口功能开发

    public interface IBookService extends IService<Book> {
        IPage<Book> getPage(int currentPage, int pageSize, Book book);
    }
    
    @Override
    public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType());
        lqw.like(Strings.isNotEmpty(book.getName()),Book::getType,book.getName());
        lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getType,book.getDescription());
        Page page = new Page(currentPage, pageSize);
        bookDao.selectPage(page,lqw);
        return page;
    }
    

3.5.13 整合SSMP案例总结

  1. pom.xml

    配置起步依赖

  2. application.yml

    设置数据源、端口、框架技术相关配置等

  3. dao

    继承BaseMapper、设置@Mapper

  4. dao测试类

  5. service

    调用数据层接口或MyBatis-Plus提供的接口快速开发

  6. service测试类

  7. controller

    基于Restful开发,使用Postman测试跑通功能

  8. 页面

    放置在resources目录下的static目录中

4. 打包与运行

4.1 程序打包与运行(Win版)

IDEA中找到Maven该项目的Lifecycle,双击package打包,即可在工程目录中看到target目录。在资源管理器中打开该目录,在路径框输入cmd+回车打开命令提示符。输入命令运行即可。

java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
  • 注意:

    • jar支持命令行启动需要依赖maven插件支持,要确认打包时是否具有SpringBooti对应的maven插件

      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
              </plugin>
          </plugins>
      </build>
      

    使用使用SpringBoot提供的maven插件可以将工程打包成可执行jar包

由于在测试中执行了增删改查的操作,因此需要跳过测试

点击Maven工具栏上方的闪电按钮,再次点击package即可跳过测试打包

  • Windonws端口被占用问题:

    • 查询端口

      netstat -ano

    • 查询指定端口

      netstat-ano | findstr "端口号"

    • 根据进程PID查询进程名称

      tasklist | findstr "进程PID号"

    • 根据PID杀死任务

      taskkill /F /PID "进程PID号"

    • 根据进程名称杀死任务

      taskkill -f -t -im"进程名称"

4.2 程序运行(Linux版)

将打包号的jar包上传到linux中,一般将项目放在/usr/app中。进入到此目录输入命令java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar运行即可。要注意在运行前将数据库创建好。

由于这种运行方式会将屏幕占用,因此要使用后台运行

nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar >server.log 2>&1 &

想要终止运行可以使用ps -ef | "java -jar"查询进程号,并使用kill -9 进程号 命令杀死该进程

可以查看server.log查看运行日志

5. 高级配置

5.1 临时属性设置

  • 带属性启动SpringBoot

    java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar --server.port=80

  • 带多个属性启动SpringBoot属性间使用空格分隔

    java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar --server.port=80 --spring.datasource.druid.password=666

  • 在idea下测试临时属性

    image

    image

    在Program arguments处填入临时属性即可

    在main函数参数中的args即临时属性,若去掉读取外部参数的形参args则不能使用外部参数,即断开了读取外部临时配置对应的入口

5.2 配置文件分类

  • SpringBoot中4级配置文件
    • 1级:file: 工作空间/config/application.yml ----最高(运维经理)
    • 2级:file: 工作空间/application.yml
    • 3级:classpath: resources/config/application.yml(项目经理使用)
    • 4级:classpath: resources/application.yml ----最低(程序员使用)
  • 作用:
    • 1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控
    • 3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控

5.3 自定义配置文件

  • 在resrouces下创建配置文件ebank.properties
  • 添加临时属性:--spring.config.name=ebank或者--spring.config.location=classpath:/ebank.properties

6. 多环境开发

  • yaml版

    #应用环境
    #公共配置
    spring:
      profiles:
        active: dev
    
    ---
    #设置环境
    #生产环境
    spring:
      profiles: pro
    server:
      port: 80
    
    ---
    #开发环境
    spring:
      profiles: dev
    server:
      port: 81
    
    ---
    #测试环境
    spring:
      profiles: test
    server:
      port: 82
      
      
    

    ​ yaml不过时设置写法:

    ​```yaml
    yaml

    生产环境

    spring:
    config:
    activate:
    on-profile: pro
    server:
    port: 80
    ​```

  • 多文件yaml版

    • 每个环境创建独立的yml文件:application-pro、application-dev、application-test

    • 主配置文件application.yml中配置应用环境:

      spring:
        profiles:
          active: dev
      
  • 多文件properties版

    • 每个环境创建独立的.properties文件:application-dev.properties,application-pro.properties、application-test.properties,添加环境配置

      server.port=80 server.port=81 server.port=82

    • 在主启动配置文件application.properties中添加

      设置启用的环境spring.profiles.active=dev

  • 根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下

    • application-devDB.yml
    • application-devRedis.yml
    • application-devMVC.yml
  • 使用incalude属性在激活指定环境的情况下,同时对多个环境加载使其生效,多个环节间使用逗号分隔

    spring:
      profiles:
        active: dev
        include: devDB,devRedis,devMVC
    
    
  • 多环境开发控制

    • 当Maven与SpringBoot环境发生冲突,应当让SpringBoot使用Maven的配置环境

7. 日志

  • 日志(log)作用
    • 编程期调试代码
    • 运营期记录信息
      • 记录日常运营重要信息(峰值流量、平均响应时长…)
      • 记录应用报错信息(错误堆栈)
      • 记录运维过程数据(扩容、宕机、报警…)
  • 日志级别
    • TRACE:运行堆栈信息,使用率低
    • DEBUG:程序员调试代码使用
    • NFO:记录运维过程数据
    • WARN:记录运维过程报警数据
    • ERROR:记录错误堆栈信息
    • FATAL:灾难信息,合并计入ERROR

7.1 日志基础

  • 代码中使用日志工具记录日志

    • 添加日志记录操作

      @RestController
      @RequestMapping("/books")
      public class BookController {
          //创建记录日志的对象
          private static final Logger log = LoggerFactory.getLogger(BookController.class);
      
          @GetMapping
          public String getById() {
              System.out.println("SpringBoot is running");
      
              log.debug("debug...");
      
              log.info("info...");
              log.warn("warn...");
              log.error("error...");
      
              return "SpringBoot is running";
          }
      
    • 设置日志输出级别

      #方式一:开启debug模式,输出调试信息,常用于检查系统运行情况
      #debug: true
      
      #方式二:设置日志级别,root表示根节点,即整体应用日志级别
      logging:
        level:
          root: debug
      
      • 设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别

        #设置日志级别,root表示根节点,即整体应用日志级别
        logging:
          #设置分组
          group:
          	#自定义组名,设置当前组中所包含的包
            ebank: com.mark.controller,com.mark.service,com.mark.dao
            iservice: com.alibaba
          level:
            root: warn
            #设置某个包的日志级别
            #com.mark.controller: debug
            #对某个组设置日志级别
            ebank: warn
        

7.2 快速创建日志对象

优化日志对象创建代码:

  • 方式一:创建类继承

    • 创建日志类BaseClass

      public class BaseClass {
          private Class clazz;
      
          public final Logger log;
      
          public BaseClass() {
              clazz = this.getClass();
              log = LoggerFactory.getLogger(clazz);
          }
      }
      
    • Controller继承日志类

      public class BookController extends BaseClass {
          @GetMapping
          public String getById() {
              System.out.println("SpringBoot is running");
      
              log.debug("debug...");
      
              log.info("info...");
              log.warn("warn...");
              log.error("error...");
      
              return "SpringBoot is running";
          }
      }
      
  • 方式二:注解

    • 添加@Slf4j注解即可

      @Slf4j
      @RestController
      @RequestMapping("/books")
      public class BookController{
      
          @GetMapping
          public String getById() {
              System.out.println("SpringBoot is running");
      
              log.debug("debug...");
      
              log.info("info...");
              log.warn("warn...");
              log.error("error...");
      
              return "SpringBoot is running";
          }
      }
      

7.3 日志输出格式控制

image

  • 设置日志输出格式

    #设置日志的模板格式
    pattern:
      console: "%d - %m %n"
    
    • %d:时间日期
    • %m:消息
    • %n:换行
    • %clr(){}:添加颜色,默认绿色,添加大括号可以修改颜色
    • %p:级别
    • %t:所属线程
    • %c:所属类/接口
    pattern:
      console: "%d %clr(%5p) ---[%16t] %clr(%-40.40c){cyan} : %m %n"
      #%5p表示级别信息占用5个字符
      #%-40.40c表示左对齐占用40个字符,满40字符截断
      #%cyan为藏青色
    

7.4 日志文件

  • 设置日志文件

    logging:
      group:
        ebank: com.mark.controller
    
      level:
        ebank: warn
    
      pattern:
        console: "%d %clr(%5p) ---[%16t] %clr(%-40.40c){cyan} : %m %n"
    
      file:
        name: server.log
      logback:
        rollingpolicy:
          max-file-size: 4KB
          file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
    
  • 日志文件详细配置

    logging:
      file:
        name: server.log
      logback:
      	#设置滚动日志文件
        rollingpolicy:
          #单个文件大小最大4KB
          max-file-size: 4KB
          #文件名格式为server.2000-04-18.0.log
          #%d为日期,%i为序号,从0开始
          file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
    

8. 热部署

8.1 手工启动热部署

由于开发时要进行多出改动,且要经常查看结果,因此需要配置热部署,使代码修改后可以立马生效,实现步骤如下:

  • 开启开发者工具

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    
  • 激活热部署:Ctrl+F9/工具栏build选项->build project

关于热部署过程:

  • 重启(Restart)︰自定义开发代码,包含类、页面、配置文件等,加载位置restart类加载器

  • 重载( ReLoad) : jar包,加载位置base类加载器

    热部署仅仅代表Restart的过程,而启动应用两个过程都包含

8.2 自动启动热部署

  • 设置自动构建项目

    image

    image

    • 注:旧版idea第二步按ctrl+alt+shift+/

      image

      image

    完成以上步骤即设置成功

    激活方式:当代码发生改变,idea开发工具失去焦点5s后会进行自动构建

8.3 热部署范围配置

  • 默认不触发重启的目录列表

    • /META-INF/maven
    • /META-INF/resources
    • /resources
    • /static
    • /public
    • /templates
  • 自定义修改热部署配置范围:

    spring:
      devtools:
        restart:
          #设置不参与热部署的文件或文件夹
          additional-exclude: static/**,public/**,application.yml
    

8.4 关闭热部署功能

设置高优先级属性禁用热部署

@SpringBootApplication
public class SSMPApplication {

    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SSMPApplication.class, args);
    }
}

9. 配置高级

9.1 @ConfigurationProperties

  • 使用@ConfigurationProperties为第三方bean绑定属性

    @ConfigurationProperties(prefix = "datasource")
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        //ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return ds;
    }
    
    datasource:
      driverClassName: com.mysql.cj.jdbc.Driver456
    
  • @EnableConfigurationProperties注解可以将使用@ConfigurationProperties注解对应的类加入Spring容器

    @SpringBootApplication
    @EnableConfigurationProperties(ServerConfig.class)
    public class Springboot12ConfigurationApplication {
        public static void main(String[] args) {
        }
    }
    
    @Data
    //@Component
    @ConfigurationProperties(prefix = "servers")
    public class ServerConfig {
        private String ipAddress;
        private int port;
        private Long timeOut;
    }
    
    • 注意:@EnableConfigurationProperties与@Component不能同时使用

9.2 宽松绑定/松散绑定

  • @ConfigurationProperties绑定属性支持属性名宽松绑定

    public class ServerConfig {
        private String ipAddress;
        private int port;
        private Long timeOut;
    }
    

    image

    注意事项:

    宽松绑定不支持注解@Value引用单个属性的方式

    @ConfigurationProperties(prefix = "datasource")
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        //ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return ds;
    }
    

    注意事项:

    绑定前缀名命名规范:仅能使用纯小写字母、数字、下划线作为合法的字符

9.3 常用计量单位绑定

SpringBoot支持JDK1.8提供的时间与空间计量单位

@Data
@Component
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    //3ms
    //private Duration serverTimeOut;

    //3h
    @DurationUnit(ChronoUnit.HOURS)
    private Duration serverTimeOut;

    //10byte
    //private DataSize dataSize;

    //10MB
    //@DataSizeUnit(DataUnit.MEGABYTES)
  	//还可以在yml中修改值为10MB
    private DataSize dataSize;
}
servers:
  serverTimeOut: 3
  #dataSize: 10
  dataSize: 10MB

9.4 数据校验

  • 开启数据校验有助于系统安全性,J2EE规范中]SR303规范定义了一组有关数据校验相关的API

    • 添加]SR303规范坐标与Hibernate校验框架对应坐标

      <!--导入JSR303规范(接口)-->
      <dependency>
          <groupId>javax.validation</groupId>
          <artifactId>validation-api</artifactId>
      </dependency>
      
      <!--使用hibernate框架提供的校验器做实现类-->
      <dependency>
          <groupId>org.hibernate.validator</groupId>
          <artifactId>hibernate-validator</artifactId>
      </dependency>
      
    • 对Bean开启校验功能

      @Data
      @Component
      //2. 开启对当前bean的属性注入校验
      @Validated
      @ConfigurationProperties(prefix = "servers")
      public class ServerConfig {
        
      }
      
    • 设置校验规则

      @Data
      @Component
      @Validated
      @ConfigurationProperties(prefix = "servers")
      public class ServerConfig {
          private String ipAddress;
        	//设置校验规则
          @Max(value = 8888,message = "最大值不超过8888")
          @Min(value = 202,message = "最小值不超过202")
          private int port;
          private Long timeOut;
      }
      

10. 测试

10.1 加载测试专用属性

test:
  prop: testValue

  • 在启动测试环境时可以通过properties参数设置测试环境专用的属性

    //properties属性可以为当前测试用例添加临时的属性配置
    @SpringBootTest(properties = {"test.prop=testValue1"})
    public class PropertiesArgsTest {
    
        @Value("${test.prop}")
        private String msg;
    
        @Test
        void testProperties(){
            System.out.println(msg);
        }
    }
    
    • 优势:比多环境开发中的测试环境影响范围更小,仅对当前测试类有效
  • 在启动测试环境时可以通过args参数设置测试环境专用的传入参数

    //args属性可以为当前测试用例添加临时的命令行参数
    @SpringBootTest(args = {"--test.prop=testValue2"})
    public class PropertiesArgsTest {
    
        @Value("${test.prop}")
        private String msg;
    
        @Test
        void testProperties(){
            System.out.println(msg);
        }
    }
    

10.2 加载测试专用配置

  • 使用@Import注解加载当前测试类专用的配置

    @SpringBootTest
    @Import(MsgConfig.class)
    public class ConfigurationTest {
        @Autowired
        private String msg;
    
        @Test
        void testConfiguration(){
            System.out.println(msg);
        }
    }
    

10.3 Web环境模拟测试

测试类中测表现层

  • 模拟端口

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class WebTest {
        @Test
        void test(){
    
        }
    }
    

    image

  • 虚拟请求测试

    @SpringBootTest
    //1.开启虚拟MVC调用
    @AutoConfigureMockMvc
    public class PropertiesArgsTest {
        @Test
        void testWeb(@Autowired MockMvc mvc) throws Exception {
            //2.创建虚拟请求,当前访问/books
            MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("http://localhost:8080/books");
            //3.执行对应的请求
            mvc.perform(builder);
        }
    }
    
  • 虚拟请求状态匹配

    @SpringBootTest()
    @AutoConfigureMockMvc
    public class PropertiesArgsTest {
    
        @Test
        void testWeb(@Autowired MockMvc mvc) throws Exception {
            //创建错误虚拟请求,当前访问/books1
            MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("http://localhost:8080/books1");
            //执行对应的请求,请求结果actions
            ResultActions actions = mvc.perform(builder);
    
            //1.定义本次调用的预期值
            //1.1.当前模拟运行的状态
            StatusResultMatchers status = MockMvcResultMatchers.status();
            //1.2.预计本次调用使成功的:状态200
            ResultMatcher ok = status.isOk();
            //3.添加预计值到本次调用过程中进行匹配
            actions.andExpect(ok);
        }
    }
    
  • 虚拟请求体匹配

    @Test
    public void testBody(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder =MockMvcRequestBuilders.get("http://localhost:8080/books");
        ResultActions actions = mvc.perform(builder);
      	//1.定义本次调用的预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.string("SpringBoot");
      	//2.使用本次真实执行结果的预期结果进行对比
        actions.andExpect(result);
    }
    
  • 虚拟请求体(json)匹配

    @Test
    public void testJson(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("http://localhost:8080/books");
        ResultActions actions = mvc.perform(builder);
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\"id\":1,\"name\":\"SpringBoot开发\",\"type\":\"SpringBoot\",\"description\":\"SpringBoot\"}");
        actions.andExpect(result);
    }
    
  • 虚拟响应体匹配

    @Test
    public void testContentType (@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("http://localhost:8080/books");
        ResultActions actions = mvc.perform(builder);
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type", "application/json");
        actions.andExpect(contentType);
    }
    
  • 实际开发中的一个模块完整测试:

    @Test
    public void testGetById(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("http://localhost:8080/books");
        ResultActions actions = mvc.perform(builder);
    
        //测试是否请求成功
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        actions.andExpect(ok);
    
        //测试结果是否匹配
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\"id\":1,\"name\":\"SpringBoot开发\",\"type\":\"SpringBoot\",\"description\":\"SpringBoot\"}");
        actions.andExpect(result);
    
        //测试响应头是否匹配
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type", "application/json");
        actions.andExpect(contentType);
    }
    

10.4 数据层测试回滚

在执行项目某个生命周期时,如果执行了测试,将会产生脏数据/测试数据,为了解决这个问题,SpringBoot添加了测试回滚

  • 为测试用例添加事务,SpringBoot会对测试用例对应的事务提交进行回滚

    @SpringBootTest
    @Transactional
    public class DaoTest {
    
        @Autowired
        private BookService bookService;
    
        @Test
        void testSave() {
            Book book = new Book();
            book.setType("测试数据123");
            book.setName("测试数据456");
            book.setDescription("测试数据789");
            bookService.save(book);
        }
    
    }
    
  • 如果想在测试用例中提交事务,可以通过@Rollback注解设置

    @SpringBootTest
    @Transactional
    //@Rollback默认为true 设置为false不回滚
    @Rollback(value = false)
    public class DaoTest {
    
        @Autowired
        private BookService bookService;
    
        @Test
        void testSave() {
            Book book = new Book();
            book.setType("测试数据123");
            book.setName("测试数据456");
            book.setDescription("测试数据789");
            bookService.save(book);
        }
    
    }
    

10.5 测试用例数据设定

  • 测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其复制

    testcase:
      book:
        id: ${random.int} #随机整数
        id2: ${random.int(10)} #10以内随机数
        id3: ${random.int(10,20)} #10到20随机数
        name: Mark${random.value} #随机字符串,MSD字符串,32位
        uuid: ${random.uuid} #随机uuid
        publicTime: ${random.long} #随机整数(Long范围)
    

    添加实体类

    @ConfigurationProperties(prefix = "testcase.book")
    @Component
    @Data
    public class BookCase {
        private int id;
        private String name;
        private String uuid;
        private Long publicTime;
    }
    

    使用测试数据

    @Test
    public void testProperties(){
        System.out.println(bookCase);
    }
    

11. 数据层解决方案

现有数据层解决方案选型:Druid+MyBatis-Plus+MySQL

  • 数据源:DruidDataSource
  • 持久化技术:MyBatis-Plus/MyBatis
  • 数据库:MySQL

数据源配置格式

  • 格式一

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
        username: root
        password: 123
        #
    
  • 格式二

    spring:
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
          username: root
          password: 123
    

11.1 SQL

当有引入Druid坐标时,SpringBoot会自动配置数据源使用Druid。那如果没有引入Druid坐标呢?

  • SpringBoot提供了3种内嵌的数据源对象供开发者选择

    • HikariCP:当没有数据源时,SpringBoot默认使用HikariCP数据源对象

      com.zaxxer.hikari.HikariDataSource

      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/ssm_db?serverTimeZone=UTC
          hikari:
            username: root
            password: 123
            driver-class-name: com.mysql.cj.jdbc.Driver
      

      也可以直接使用原始的DataSource配置,默认使用的就是HikariCP

    • Tomcat:提供DataSource:HikariCP不可用的情况下,且在web环境中,将使用tomcat)服务器配置的数据源对象

    • Commons DBCP:Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源

  • 内置持久化解决方案——JdbcTemplate

    • 导入坐标

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>
      
    • 使用

      @Test
          void testJdbcTemplate(){
              String sql = "select * from tbl_book where id = 1";
      //        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
      //        System.out.println(maps);
              RowMapper<Book> rm = new RowMapper<Book>(){
                  @Override
                  public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                      Book temp = new Book();
                      temp.setId(rs.getInt("id"));
                      temp.setDescription(rs.getString("description"));
                      temp.setType(rs.getString("type"));
                      temp.setName( rs.getString("name"));
                      return temp;
                  }
              };
              List<Book> query = jdbcTemplate.query(sql, rm);
      
              System.out.println(query);
          }
      
  • SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率

    • H2
    • HSQL
    • Derby
  • H2数据库的使用:

    • 添加坐标

      <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
      </dependency>
      
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>
      
    • 设置服务器端口

      server:
        port: 80
      
    • 设置当前项目为web工程,并配置H2管理控制台参数

      spring:
        h2:
          console:
            path: /h2
            enabled: true
      
    • 在浏览器输入localhost/h2进入可以看到Login设置,修改配置

      image

      在yml文件中添加配置,访问用户名sa,密码123456

      datasource:
          url: jdbc:h2:~/test
          hikari:
            username: sa
            password: 123456
            driver-class-name: org.h2.Driver
      

      再次进入控制台,登录

    H2数据库控制台仅仅用于开发阶段,上线项目务必关闭控制台

    SpringBoot可以根据url地址自动识别数据库种类,在保证驱动类存在的情况下,可以省略配置

    spring:
      h2:
        console:
          path: /h2
          enabled: true
    
      datasource:
          url: jdbc:h2:~/test
          hikari:
            username: sa
            password: 123456
            #driver-class-name: org.h2.Driver
    

11.2 NoSQL

  • 市面上常见的NoSQL解决方案

    • Redis
    • Mongo
    • ES

    上述技术通常在Linux中安装部署

11.2.1 Redis

  • Redis是一款key-value存储结构的内存及NoSQL数据库

    • 支持多种数据存储格式
    • 支持持久化
    • 支持集群
  • 整合步骤:

    • 创建包含Redis坐标的SprinBoot坐标

    • 使用操作接口RedisTemplate自动装配对象来操作Redis

    • RedisTemplate提供了多种数据存储类型的接口API

      @SpringBootTest
      class Springboot15RedisApplicationTests {
          @Autowired
          private RedisTemplate redisTemplate;
      
          @Test
          void set() {
              ValueOperations opsForValue = redisTemplate.opsForValue();
              opsForValue.set("age",14);
          }
      
          @Test
          void get(){
              ValueOperations opsForValue = redisTemplate.opsForValue();
              Object age = opsForValue.get("age");
              System.out.println(age);
          }
      }
      
  • RedisTemplate以对象作为key和value,内部对数据进行序列化

    StringRedisTemplate以字符串作为key和value,与redis客户端操作等效(常用)

  • 配置客户端:

    spring:
      redis:
        host: localhost
        port: 6379
        client-type: jedis
    
    • lettcus和jedis区别
      • jedis连接Redis,服务器是直连模式,当多线程模式下使用jedis会存在线程安全问题,解决方案可以通过配置连接池使每个连接专用,这样整体性能就大受影响。
      • lettcus基于Netty框架进行与Redis服务器连接,底层设计中采用StatefulRedisConnection。StatefulRedisConnection自身是线程安全的,可以保障并发访问安全问题,所以一个连接可以被多线程复用。当然lettcus也支持多连接实例一起工作。

11.2.2 MongoDB

  • MongoDB是一个开源高性能无模式的文档型数据库。NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库

  • 使用场景

    • 淘宝用户数据
      • 存储位置:数据库
      • 特征:永久性存储,修改频度极低
    • 游戏装备数据、游戏道具数据
      • 存储位置:数据库、Mongodb
      • 特征:永久性存储与临时存储相结合、修改频度较高
    • 直播数据、打赏数据、粉丝数据
      • 存储位置:数据库、Mongodb
      • 特征:永久性存储与临时存储相结合,修改频度极高
    • 物联网数据
      • 存储位置:Mongodb
      • 特征:临时存储,修改频度飞速
  • MongoDB(windos版)下载安装

    • 下载:https://www.mongodb.com/try/download

    • 安装:解压缩后设置数据目录

    • 启动:

      • 服务端cmd启动:

        mongod --dbpath=..\data\db

      • 客户端cmd启动:

        mongo --host=127.0.0.1 --port=27017

  • Mongo基础操作

    • 新增

      db.集合名称.insert/save/insertOne(文档)

    • 修改

      db.集合名称.remove(条件)

    • 删除

      db.集合名称.update(条件,{操作种类:{文档}})

  • SpringBoot整合

    • 导入坐标驱动

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-mongodb</artifactId>
      </dependency>
      
    • 配置客户端

      spring:
        data:
          mongodb:
            uri: mongodb://localhost/mark
      
    • 使用MongoTemplate接口创建对象调用API

12. 整合第三方技术

12.1 缓存

12.1.1 缓存的作用

  • 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
  • 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
  • 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间

12.1.2 Spring缓存的使用方式

  • SpringBoot提供了缓存技术,方便缓存使用

  • 步骤:

    • 启用缓存

      • 加入cache坐标

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
      • 在启动类中开启缓存功能

        @SpringBootApplication
        //开启缓存操作
        @EnableCaching
        public class SSMPApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(SSMPApplication.class, args);
            }
        
        }
        
    • 设置当前操作的结果数据进入缓存

      @Override
      @Cacheable(value = "cacheSpace",key = "#id")
      public Book getById(Integer id) {
          return bookDao.selectById(id);
      }
      
  • SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理

    • Generic
    • JCache
    • Ehcache
    • Hazelcast
    • Infinispan
    • Couchbase
    • Redis
    • Caffenine
    • Simple(默认)
    • memcached

12.1.3 手机验证码案例

  • 验证码domain

    @Data
    public class SIMCode {
        private String tel;
        private String Code;
    }
    
  • 验证码工具类

    @Component
    public class CodeUtils {
    
        private String [] patch= {"000000","00000","0000","000","00","0",""};
    
        public String generator(String tel) {
            //获取电话的hashcode
            int hash = tel.hashCode();
            //设置加密码
            int encryption = 20226666;
            long result = hash ^ encryption;
            long nowTime = System.currentTimeMillis();
            result = result ^ nowTime;
            //取后六位
            long code = result % 1000000;
            //防止出现负数
            code = code < 0 ? -code : code;
            String codeStr = code + "";
            int len = codeStr.length();
    
            return patch[len] + codeStr;
        }
    
        @Cacheable(value = "simCode",key = "#tel")
        public String get(String tel){
            return null;
        }
    }
    
  • 表现层

    @RestController
    @RequestMapping("/sim")
    public class SIMCodeController {
        @Autowired
        private SIMCodeService simCodeService;
    
        @GetMapping
        public String getCode(String tel){
            String code = simCodeService.sendCodeToSIM(tel);
            return code;
        }
    
        @PostMapping
        public boolean checkCode(SIMCode simCode){
            return simCodeService.checkCode(simCode);
        }
    }
    
  • 业务层

    @Service
    public class SIMCodeServiceImpl implements SIMCodeService {
        @Autowired
        private CodeUtils codeUtils;
    
      	//获取验证码,将验证码放入指定位置
        @Override
        //@Cacheable(value = "simCode",key = "#tel")
        @CachePut(value = "simCode", key = "#tel")
        public String sendCodeToSIM(String tel) {
            String code = codeUtils.generator(tel);
            return code;
        }
      
      	//验证码校验
        @Override
        public boolean checkCode(SIMCode simCode) {
            String code = simCode.getCode();
          	//取出内存中验证码
            String cacheCode = codeUtils.get(simCode.getTel());
            return code.equals(cacheCode);
        }
    }
    
    

12.1.4 变更缓存供应商Ehcache

  • 导入坐标

    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    
  • 缓存设定启用ehcache

    spring:
      cache:
        type: ehcache
        #ehcache:
          #config: ehcache.xml
    
  • 提供配置文件ehcache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        <diskStore path="D:\ehcache" />
    
        <!--默认缓存策略 -->
        <!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
        <!-- diskPersistent:是否启用磁盘持久化-->
        <!-- maxElementsInMemory:最大缓存数量-->
        <!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
        <!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
        <!-- timeToLiveSeconds:最大存活时间-->
        <!-- memoryStoreEvictionPolicy:缓存清除策略-->
        <defaultCache
            eternal="false"
            diskPersistent="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            timeToIdleSeconds="60"
            timeToLiveSeconds="60"
            memoryStoreEvictionPolicy="LRU" />
    
        <!--名叫simCode的Cache空间-->
        <cache
                name="simCode"
                eternal="false"
                diskPersistent="false"
                maxElementsInMemory="1000"
                overflowToDisk="false"
                timeToIdleSeconds="60"
                timeToLiveSeconds="60"
                memoryStoreEvictionPolicy="LRU" />
    </ehcache>
    

12.1.5 变更缓存供应商Redis

  • 添加坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 配置Redis服务器,缓存设定为Redis

    spring:
      cache:
        type: redis
        redis:
          #设置存活时间60s
          time-to-live: 60s
          #使用前缀(默认为true)
          use-key-prefix: true
          #设置前缀为sim_
          key-prefix: sim_
      redis:
        host: localhost
        prot: 6379
    

12.1.6 jetcache远程缓存方案

  • jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能

  • jetCache设定了本地缓存与远程缓存的多级缓存解决方案

    • 本地缓存(local)
      • LinkedHashMap
      • Caffeine
    • 远程缓存(remote)
      • Redis
      • Tair
  • 使用步骤

    • 添加依赖

      <dependency>
          <groupId>com.alicp.jetcache</groupId>
          <artifactId>jetcache-starter-redis</artifactId>
          <version>2.6.7</version>
      </dependency>
      
    • 添加配置

      jetcache:
        #远程缓存
        remote:
          default:
            type: redis
            host: localhost
            port: 6379
            poolConfig:
              maxTotal: 50
      #    sim:
      #      type: redis
      #      host: localhost
      #      port: 6379
      #      poolConfig:
      #        maxTotal: 50
      
    • 使用jetcache

      @SpringBootApplication
      //jetcache启用缓存的主开关
      @EnableCreateCacheAnnotation
      public class SSMPApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(SSMPApplication.class, args);
          }
      
      }
      
      @Service
      public class SIMCodeServiceImpl implements SIMCodeService {
          @Autowired
          private CodeUtils codeUtils;
      
          @CreateCache(name = "jetCache",expire = 60,timeUnit = TimeUnit.SECONDS)
          private Cache<String,String> jetCache;
      
      //    @CreateCache(area = "sim",name = "simCache",expire = 30,timeUnit = TimeUnit.SECONDS)
      //    private Cache<String,String> jetCache2;
      
          @Override
          public String sendCodeToSIM(String tel) {
              String code = codeUtils.generator(tel);
              jetCache.put(tel,code);
              return code;
          }
      
          @Override
          public boolean checkCode(SIMCode simCode) {
              String code = jetCache.get(simCode.getTel());
              return simCode.getCode().equals(code);
          }
      }
      

12.1.7 jetcache本地缓存方案

  • 修改配置

    jetcache:
      local:
        default:
          type: linkedhashmap
          keyConvertor: fastjson
    
  • 启用本地缓存方案

    //默认使用远程缓存方案
    @CreateCache(name = "jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
    private Cache<String,String> jetCache;
    

12.1.8 jetcache方法缓存

  • 启用方法注解

    @SpringBootApplication
    @EnableCreateCacheAnnotation
    //开启方法注解缓存
    @EnableMethodCache(basePackages = "com.mark")
    public class SSMPApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SSMPApplication.class, args);
        }
    
    }
    
  • 使用方法注解操作缓存

    @Service
    public class bookServiceImpl implements IBookService{
        @Autowired
        private BookDao bookDao;
    
        @Override
        @Cached(name = "book_",key = "#id",expire = 1000)
        //@CacheRefresh(refresh = 5)
        public Book getById(Integer id) {
            return bookDao.selectById(id);
        }
    }
    
  • 想要操作缓存,缓存对象必须保证可序列化

    @Data
    public class Book implements Serializable {
        private Integer id;
        private String type;
        private String name;
        private String description;
    }
    
    jetcache:
      local:
        default:
          type: linkedhashmap
          keyConvertor: fastjson
          valueEncode: java
          valueDecode: java
    
    
  • 查看缓存报告

    jetcache:
      statIntervalMinutes: 1
    

12.1.9 j2cache基本操作

  • j2cache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能

  • 基于ehcache+redis进行整合

    • pom中导入坐标

      <dependency>
          <groupId>net.oschina.j2cache</groupId>
          <artifactId>j2cache-core</artifactId>
          <version>2.8.5-release</version>
      </dependency>
      
      <dependency>
          <groupId>net.oschina.j2cache</groupId>
          <artifactId>j2cache-spring-boot2-starter</artifactId>
          <version>2.8.0-release</version>
      </dependency>
      
      <dependency>
          <groupId>net.sf.ehcache</groupId>
          <artifactId>ehcache</artifactId>
      </dependency>
      
    • 定义将cache的配置文件

      server:
        port: 80
      
      j2cache:
        config-location: j2cache.properties
      
    • 定义相关配置

      #一级缓存
      j2cache.L1.provider_class = ehcache
      ehcache.configXml = ehcache.xml
      
      #二级缓存
      j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
      j2cache.L2.config_section = redis 
      redis.hosts = local:6379
      
      #一级缓存中的数据如何到达二级缓存
      j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
      

12.1.10 SpringBoot整合quartz

  • Quartz是市面上流行的定时任务技术
  • 相关概念
    • 工作(Job):用于定义具体执行的工作
    • 工作明细(JobDetail):用于描述定时工作相关的信息
    • 触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
    • 调度器(Scheduler):描述了工作明细与触发器的对应关系

12.1.11 SpringBoot整合task

12.2 邮件

  • SMTP:简单邮件传输协议,用于发送电子邮件的传输协议
  • POP3:用于接收电子邮件的标准协议
  • IMAP:互联网消息协议,是POP3的替代协议

12.2.1 发送简单邮件

  • 导入坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
  • 配置JavaMail

    spring:
      mail:
        host: smtp.163.com
        username: ********@qq.com
        password: **********
    
  • 开启定时任务功能

    @Service
    public class SendMailServiceImpl implements SendMailService {
        @Autowired
        private JavaMailSender javaMailSender;
    
        //发送人
        private String from = "**********@qq.com";
        //接收人
        private String to = "********@163.com";
        //标题
        private String subject = "测试邮件";
        //正文
        private String context = "测试邮件正文内容";
    
        @Override
        public void sendMail() {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);
            message.setTo(to);
            message.setSubject(subject);
            message.setText(context);
            javaMailSender.send(message);
        }
    }
    

12.2.2 发送多部件邮件

@Service
public class SendMailServiceImpl implements SendMailService {
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "********@qq.com";
    //接收人
    private String to = "********@163.com";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "<a href='https://www.baidu.com'>点开有惊喜</a>";

    @Override
    public void sendMail() {
        try {
            MimeMessage message = javaMailSender.createMimeMessage();
          	//true:可发送附件
            MimeMessageHelper helper = new MimeMessageHelper(message,true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
          	//true:可发送html
            helper.setText(context,true);

            //添加附件
            File f1= new File("");
            helper.addAttachment(f1.getName(),f1);

            javaMailSender.send(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

12.3 消息

12.3.1 消息简介

  • 消息的相关概念
    • 消息发送方
      • 生产者
    • 消息接收方
      • 消费者
    • 同步消息
    • 异步消息
  • 当一个业务系统收到了浏览器无数个请求,业务系统的压力就会很大,业务系统服务器拆分成了若干个子业务系统,当业务系统接收到了请求之后,不直接处理,交给后面的子业务系统来处理,这时就需要一个东西,用来保存业务系统发给子业务系统的消息,子业务系统到这个东西中获取要执行的工作,整个过程中所有要执行的工作都转换成消息的格式存在。通常我们将这个东西称为队列,又因为是保存消息的,所以叫做消息队列
  • 企业级应用中广泛使用的三种异步消息传递技术
    • JMS:一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
      • JMS消息模型
        • peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息,队列的消息只能被一个消费者消费,或超时
        • publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
      • JMS消息种类
        • TextMessage
        • MapMessage
        • BytesMessage
        • StreamMessage
        • ObjectMessage
        • Message(只有消息头和属性)
      • JSM实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范)
    • AMQP:一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
      • 优点:具有跨平台性,服务器供应商、生产者、消费者可以使用不同的语言来实现
      • AMQP消息模型:
        • direct exchange
        • fanout exchange
        • topic exchange
        • headers exchange
        • system exchange
      • AMQP消息种类:byte[]
      • AMQP实现:RabbitMQ、StormMQ、RocketMQ
    • MQTT:消息队列遥测传输技术、专为小设备设计,是物联网(IOT)生态系统中主要成分之一
  • Kafka:一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。

12.3.2 购物订单案例:发送短信

  • 购物订单业务

    • 登录状态检测
    • 生成主单
    • 生成子单
    • 库存检测与变更
    • 积分变更
    • 支付
    • 短信通知
    • 购物车维护
    • 运单信息初始化
    • 商品库存维护
    • 会员维护
    • ...
  • 短信通知

    • 业务层

      @Service
      public class OrderServiceImpl implements OrderService {
          @Autowired
          private MessageService messageService;
      
          @Override
          public void order(String id) {
              //一系列操作,包含各种服务调用,处理各种业务
              System.out.println("订单处理开始");
              //短信消息处理
              messageService.sendMessage(id);
              System.out.println("订单处理结束");
              System.out.println();
          }
      }
      
      @Service
      public class MessageServiceImpl implements MessageService {
      
          private ArrayList<String> msgList = new ArrayList<>();
      
          @Override
          public void sendMessage(String id) {
              System.out.println("待发送短信的订单已纳入消息队列,id:"+id);
              msgList.add(id);
          }
      
          @Override
          public String doMessage() {
              String id = msgList.remove(0);
              System.out.println("已完成短信发送业务,id:"+id);
              return id;
          }
      }
      
    • 表现层

      @RestController
      @RequestMapping("/orders")
      public class OrderController {
          @Autowired
          private OrderService orderService;
      
          @PostMapping("{id}")
          public void order(@PathVariable String id){
              orderService.order(id);
          }
      }
      
      @RestController
      @RequestMapping("/msg")
      public class MessageController {
      
          @Autowired
          private MessageService messageService;
      
          @GetMapping
          public String doMessage() {
              String id = messageService.doMessage();
              return id;
          }
      }
      

12.3.3 ActiveMQ

  • ActiveMQ下载安装

  • 使用:

    • 启动服务

      activemq.bat

    • 访问服务器

      http://127.0.0.1:8161/

      • 服务端口:61616,管理后台端口:8161
      • 用户名&密码:admin
  • SpringBoot整合ActiveMQ

    • 导入坐标

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-activemq</artifactId>
      </dependency>
      
    • 配置

      spring:
        #配置服务地址
        activemq:
          broker-url: tcp://localhost:61616
        #配置默认保存位置
        jms:
          template:
            default-destination: mark
      
    • 使用

      @Service
      public class MessageServiceActivemqImpl implements MessageService {
        	//使用JmsMessagingTemplate接口实现消息队列对象
          @Autowired
          private JmsMessagingTemplate接口实现消息队列对象 messagingTemplate;
      
          @Override
          public void sendMessage(String id) {
              System.out.println("待发送短信的订单已纳入消息队列,id:"+id);
              messagingTemplate.convertAndSend(id);
            	//messagingTemplate.convertAndSend("order.queue.id",id);
          }
      
          @Override
          public String doMessage() {
            	///String id = messagingTemplate.receiveAndConvert("order.queue.id",String.class); -----
              String id = messagingTemplate.receiveAndConvert(String.class);
              System.out.println("已完成短信发送业务,id:"+id);
              return id;
          }
      }
      

      监听:自动获取消息

      @Component
      public class MessageListener {
          @JmsListener(destination = "mark")
          public void receive(String id){
              System.out.println("已完成短信发送业务,id:"+id);
          }
      }
      

      监听转发,将队列中的消息转发给另一个队列处理

      @Component
      public class MessageListener {
          @JmsListener(destination = "mark")
          @SendTo("order.queue.id")
          public String receive(String id) {
              System.out.println("已完成短信发送业务,id:" + id);
              return "new" + id;
          }
      }
      
    • 使用发布订阅模型:

      spring:
        activemq:
          broker-url: tcp://localhost:61616
        jms:
          template:
            default-destination: mark
          pub-sub-domain: true
      

12.3.4 RabbitMQ

  • RabbitMQ下载安装
    • RabbitMQ基于Erlang语言编写,需要安装Erlang
    • Erlang
      • 下载地址:https://www.erlang.org/downloads
      • 安装:一键傻瓜式安装,安装完毕需要重启,需要依赖Windows组件
      • 环境变量配置
        • ERLANG HOME
        • PATH
    • RabbitMQ
      • 下载地址:https:/rabbitmg.com/install-windows.html
      • 安装:一键傻瓜式安装

12.3.5 RocketMQ

  • 下载安装
    • 下载地址:https://rocketmq.apache.org
    • 安装:解压缩
      • 默认服务端口:9876
    • 环境变量配置
      • ROCKETMO HOME
      • PATH
      • NAMESRV ADDR(建议):127.0.0.1:9876
      • NAMESRV ADDR:当生产者要连接多台业务服务器(broker)时,过程会很繁琐,因此需要一个命名服务器(NameServer),业务服务器在命名服务器上注册信息,生产者和消费者连接NameServer,获取具体的业务服务器。

12.3.5 kafka

  • 下载地址:https://kafka.apache.org/downloads

  • 安装:解压缩

  • 启动:

    • 启动zookeeper注册中心

      zookeeper-server-start.bat ../../config/zookeeper.properties

      • 默认端口:2181
    • 启动kafka

      kafka-server-start.bat ../../config/server.properties

      • 默认端口:9092

13. 监控

13.1 监控的意义

13.2 可视化监控平台

13.3 监控原理

13.4 自定义监控指标

标签:String,class,private,public,学习,book,深入,id,SpringBoot
From: https://www.cnblogs.com/hackertyper/p/16737785.html

相关文章

  • 一文详解深度学习中的Normalization:BN/LN/WN
    参考资料:https://mp.weixin.qq.com/s?__biz=MzU3NDgxMzI0Mw==&mid=2247504259&idx=3&sn=bcd5af6172a5dd77d29e9d5f15362078&chksm=fd2e34d7ca59bdc11ed2d8663916452293ab4......
  • IM跨平台技术学习(三):vivo的Electron技术栈选型、全方位实践总结
    本文由vivo技术团队YangKun分享,原题“electron应用开发优秀实践”,本文有修订。1、引言在上篇《Electron初体验(快速开始、跨进程通信、打包、踩坑等)》的分享中,我们已......
  • 计算机学习心得
    先看个小故事。有一天我问我媳妇:你觉得操作系统复杂么?我媳妇想都没想就来了一句:复杂!我又问:为什么觉得操作系统复杂?或者操作系统复杂在哪里?我媳妇眼睛转了两圈之后说:不......
  • 【学习笔记】数据库的列类型和字段属性
    数据库的列类型和字段属性 列类型数值tinyint十分小的数据1个字节smallint较小的数据2个字节mediumint中等大小的数据3个字节int标准的整数4......
  • flask框架学习
    以前只整过js的后端,这次网安平台实践大作业打算用python写后端,于是赶紧滚过来学学flask简介与安装Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更......
  • 9、FFmpeg使用clion+mingw32编译学习y420p,yuv,rgb编码
    基本思想:继续学习ffmpeg基础知识第一步:进行y420p解码,然后将数据转rgb24格式,显示,重点学习了sws_getContext函数,可以通过他进行各种转码cmakelists.txt文件内容cmake_minimum_......
  • SpringBoot热部署
    一、引言(devtools)在开发过程中,由于每次修改完项目中的类都需要重启服务才能看到运行的结果,对于开发调试很不友好,浪费时间,引入devtools工具可以快速启动项目,无需再次重启......
  • Flask 学习-80.Flask-RESTX使用reqparse 解析器trim=True去掉字符两边空格
    前言reqparse.RequestParser()解析器可以帮助我们经验请求参数。trim=True可以去掉字符两边空格trim=True使用fromflask_restximportNamespace,Resource,reqpars......
  • 【学习笔记】Mysql基本命令
    Mysql基本命令连接mysqlmysql-uroot-p123456 --连接数据库所有sql语句都必须以";"结尾updatemysql.usersetauthentication_string=password('123456')where......
  • 如何学习ReactJS:初学者完整指南
    英文| https://www.geeksforgeeks.org/how-to-learn-reactjs-a-complete-guide-for-beginners/?ref=rp翻译 |web前端开发(ID:web_qdkf)每个前端开发人员和Web开发人员都知......