SpringBoot2基础篇

186 阅读20分钟

一、快速上手SpringBoot

1.SpringBoot入门程序开发步骤:

①创建新模块,选择Spring Initializr,并配置模块相关基础信息

image.png

②:选择当前模块需要使用的技术集

image.png

③:开发控制器类:controller

④:运行自动生成的Application类

2.Spring程序与SpringBoot程序对比

Spring程序缺点

  • 依赖设置繁琐
  • 配置繁琐

SpringBoot程序优点

  • 起步依赖(简化依赖配置)
  • 自动配置(简化常用工程相关配置)
  • 辅助功能(内置服务器,……)

注意事项:基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构

3.创建SpringBoot工程的四种方式

  • 基于Idea创建SpringBoot工程,上述
  • 基于官网创建SpringBoot工程
  • 基于阿里云创建SpringBoot工程
  • 手工创建Maven工程修改为SpringBoot工程(一般不用)

3.1基于官网创建SpringBoot工程

步骤:

  1. 打开SpringBoot官网(start.spring.io/),选择Quickst… Your Project
  2. 创建工程,并保存项目
  3. 解压项目,通过IDE导入项目

3.2基于阿里云创建SpringBoot工程

基于阿里云创建项目,地址:start.aliyun.com

注意事项:

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

  1. 选择start来源为自定义URL
  2. 输入阿里云start地址
  3. 创建项目

image.png

4.如何隐藏指定文件/文件夹、复制模块

  • Setting → File Types → Ignored Files and Folders
  • 输入要隐藏的文件名,支持*号通配符
  • 回车确认添加

image.png

复制模块

image.png

5.浅谈工作原理

5.1.parent(仅定义,未使用)

  • 开发SpringBoot程序要继承spring-boot-starter-parent
  • spring-boot-starter-parent中定义了若干个依赖管理
  • 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
  • 继承parent的形式也可以采用引入依赖的形式实现效果

5.2.starter(解决配置问题)

starter的作用是为了简化配置,因为如果开发中使用的某项技术需要导入的jar包依赖比较多,就需要添加多个依赖配置。starter会为某种技术提供一系列常用的依赖坐标,使得我们使用这种技术的时候,只需要导入starter就可以了,而不是多个依赖,进而简化的配置。

  • 开发SpringBoot程序需要导入坐标时通常导入对应的starter
  • 每个不同的starter根据功能不同,通常包含多个依赖坐标
  • 使用starter可以实现快速配置的效果,达到简化配置的目的

5.3.引导类

  • SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目 (未启动Web服务器)
  • SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean

5.4.辅助功能(内嵌tomcat)

  • 内嵌Tomcat服务器是SpringBoot辅助功能之一
  • 内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理
  • 变更内嵌服务器思想是去除现有服务器,添加全新的服务器
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--web起步依赖环境中,排除Tomcat起步依赖-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
 
    <!--添加Jetty起步依赖,版本由SpringBoot的starter控制-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

6.REST风格

  • REST(Representational State Transfer)表现形式状态转换

  • 传统风格资源描述形式

  • REST风格描述形式

  • 优点:

    • 隐藏资源的访问行为, 无法通过地址得知对资源是何种操作
    • 书写简化
  • 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

  • 注意:

    • 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
    • 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如: users、 books、 accounts…
  • @RequestMapping

image.png

  • @PathVariable

image.png

  • @RequestBody @RequestParam @PathVariable

image.png

6.1 RESTful快速开发

使用 @RestController 注解开发 RESTful 风格

@RequestMapping("/books") //请求路径前缀

image.png

使用 @GetMapping @PostMapping @PutMapping @DeleteMapping 简化 @RequestMapping 注解开发

image.png

二、基础配置

  • SpringBoot默认配置文件application.properties,通过键值对配置对应属性
# 服务器端口配制 server.port=80
# 日志 logging.level.root=info

SpringBoot提供了多种属性配置方式,yml是主流格式

image.png

  • SpringBoot配置文件加载顺序

    • application.properties > application.yml > application.yaml

1.yaml

基本语法

  • key: value -> value 前面一定要有空格
  • 大小写敏感
  • 属性层级关系使用多行描述,每行结尾使用冒号结束
  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
  • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)

image.png

1.1数据读取

1)读取单个数据

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

  • 使用@Value配合SpEL读取单个数据
  • 如果数据存在多层级,依次书写层级名称即可

image.png

使用属性名引用方式引用属性读取数据

  • 在配置文件中可以使用${属性名}方式引用属性值
  • 如果属性中出现特殊字符,可以使用双引号包裹起来作为字符解析

image.png

2)读取yaml全部属性数据,封装全部数据到Environment对象

  • 使用Environment对象封装全部配置信息
  • 使用@Autowired自动装配数据到Environment对象中

image.png

3)读取yaml引用类型属性数据

  • 自定义对象封装指定数据
  • @component表示将MyDataSaource定义为spring管控的bean 截屏2023-01-20 15.49.04.png image.png
  • 使用自动装配封装指定数据

@Autowired 
private MyDataSource myDataSource;

System.out.println(myDataSource);

三、Springboot整合第三方技术

1.SpringBoot整合JUnit

  • 添加Junit的起步依赖 Spring Initializr 创建时自带
xml
复制代码
<!--测试的起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • SpringBoot整合JUnit
typescript
复制代码
@SpringBootTest //设置JUnit加载的SpringBoot启动类
class Springboot07JunitApplicationTests {
    @Autowired //使用自动装配的形式添加要测试的对象
    private BookService bookService;
    
    @Test //需要自动测试的函数
    public void testSave(){
        bookService.save();
    }
}

整合JUnit——classes属性

  • 测试类如果不存在于引导类所在的包或子包中,需要通过 classes 属性指定引导类。
java
复制代码
@SpringBootTest(classes = Springboot04JunitApplication.class)
//@ContextConfiguration(classes = Springboot04JunitApplication.class)
class Springboot04JunitApplicationTests {
  
    @Autowired //1.注入你要测试的对象
    private BookDao bookDao;

    @Test
    void contextLoads() {
        //2.执行要测试的对象对应的方法
        bookDao.save();
        System.out.println("two...");
    }
}

2. SpringBoot整合MyBatis

  • 创建新模块,选择当前模块需要使用的技术集(MyBatisMySQLimage.png
  • 设置数据源参数
yaml
复制代码
#DB Configuration:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_db
    username: root
    password: 123456
  • 在 springboot_db 数据库中创建 user 表

  • 创建实体Bean

arduino
复制代码
public class User {
    // 主键
    private Long id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 姓名
    private String name;
  
    //此处省略getter,setter,toString方法 .. ..   
}
  • 定义数据层接口与映射配置
kotlin
复制代码
@Mapper // 如果在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类。
public interface UserDao { 
    @Select("select * from user") 
    public List<User> getAll(); 
}
  • 测试类中注入dao接口,测试功能
ruby
复制代码
@SpringBootTest
class Springboot05MybatisApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void contextLoads() {
        List<User> userList = userDao.getAll();
        System.out.println(userList);
        // [User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
    }
}

总结:

  1. 勾选MyBatis技术,也就是导入MyBatis对应的starter
  2. 数据库连接相关信息转换成配置
  3. 数据库SQL映射需要添加@Mapper被容器识别到

SpringBoot整合MyBatis常见问题处理

  • SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
bash
复制代码
jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
  • 或在MySQL数据库端配置时区解决此问题

    • MySQL 8.X驱动强制要求设置时区

      • 修改url,添加serverTimezone设定
      • 修改MySQL数据库配置(略)
    • 驱动类过时,提醒更换为com.mysql.cj.jdbc.Drive

3.Springboot整合MP

  • 手动添加SpringBoot整合MyBatis-Plus的坐标,可以通过mvnrepository获取。由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
xml
复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>
  • 数据层接口使用BaseMapper简化开发,定义数据层接口与映射配置,继承BaseMapper
java
复制代码
@Mapper 
public interface UserDao extends BaseMapper<User> { 
    // 构建时,会自动生成对应user的增删查改函数
}
  • 其他同SpringBoot整合MyBatis
  • 测试类中注入dao接口,测试功能
ruby
复制代码
@SpringBootTest
class Springboot06MybatisPlusApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void contextLoads() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
        // [User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
    }
}
  • 注意: 如果你的数据库表有前缀要在 application.yml 添加如下配制
yaml
复制代码
#设置Mp相关的配置 
mybatis-plus: 
    global-config: 
        db-config: 
            table-prefix: tbl_

4.SpringBoot整合Druid

  • 导入Druid对应的starter
xml
复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
  • 指定数据源类型 (这种方式只需导入一个 Druid 的坐标)
yaml
复制代码
#DB Configuration:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  • 或者 变更Druid的配置方式(推荐) 这种方式需要导入 Druid对应的starter
yaml
复制代码
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
      username: root
      password: 123456

5.SSMP整合案例制作分析

image.png

  • 案例实现方案分析与流程解析
markdown
复制代码
1. 案例实现方案分析
    实体类开发————使用Lombok快速制作实体类
    Dao开发————整合MyBatisPlus,制作数据层测试类
    Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
    Controller开发————基于Restful开发,使用PostMan测试接口功能
    Controller开发————前后端开发协议制作
    页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理,列表、新增、修改、删除、分页、查询
    项目异常处理
    按条件查询————页面功能调整、Controller修正功能、Service修正功能
2. SSMP案例制作流程解析
    先开发基础CRUD功能,做一层测一层
    调通页面,确认异步提交成功后,制作所有功能
    添加分页功能与查询功能

5.1模块创建

  • 创建模块和数据库
  • 勾选SpringMVC与MySQL坐标

image.png

  • pom.xml文件增加mybatis-plus和druid库
javascript
复制代码
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <scope>runtime</scope>
       </dependency>
        
       // mybatis-plus
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>3.4.3</version>
       </dependency>
       
       // druid
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid-spring-boot-starter</artifactId>
           <version>1.2.6</version>
       </dependency>

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

5.2实体类开发

  • Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
  • 为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等。
xml
复制代码
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
  • lombok版本由SpringBoot提供,无需指定版本,@Data注解。

5.3 数据层标准开发(基础CRUD)

  • 导入MyBatisPlus与Druid对应的starter
xml
复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-starter</artifactId>
     <version>1.2.6</version>
</dependency>
  • 配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
yaml
复制代码
# druid 数据源配制
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
      username: root
      password: 123456

# mybatis-plus
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键策略
  • 继承BaseMapper并指定泛型
java
复制代码
@Mapper
public interface BookDao extends BaseMapper<Book> {

    /**
     * 查询一个
     * 这是 Mybatis 开发
     * @param id
     * @return
     */
    @Select("select * from tbl_book where id = #{id}")
    Book getById(Integer id);
}
  • 制作测试类测试结果
typescript
复制代码
@SpringBootTest
public class BookDaoTestCase {

    @Autowired
    private BookDao bookDao;

    @Test
    void testGetById() {
        System.out.println(bookDao.getById(1));
        System.out.println(bookDao.selectById(1));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookDao.insert(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(13);
        book.setType("测试数据asfd");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookDao.updateById(book);
    }

    @Test
    void testDelete() {
        bookDao.deleteById(13);
    }

    @Test
    void testGetAll() {
        System.out.println(bookDao.selectList(null));
    }
}

开启MP运行日志

  • 为方便调试可以开启MyBatisPlus的日志
yaml
复制代码
# mybatis-plus
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键策略
  configuration:
    # 开启MyBatisPlus的日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 设置日志输出方式为标准输出 image.png

5.4分页

  • 分页操作需要设定分页对象IPage
java
复制代码
@Test 
void testGetPage() { 
    IPage page = new Page(1, 5);
    bookDao.selectPage(page, null); 
}
  • IPage对象中封装了分页操作中的所有数据

    • 数据
    • 当前页码值
    • 每页数据总量
    • 最大页码值
    • 数据总量
  • 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现。

java
复制代码
@Configuration //配置类,会自动扫描加载
    public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //1. 定义 Mp 拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //2. 添加具体的拦截器 分页拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

5.5数据层标准开发(条件查询)

  • 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
typescript
复制代码
@Test
void testGetBy2() {
    LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.like(Book::getName, "Spring");
    bookDao.selectList(lambdaQueryWrapper);
}

@Test
void testGetBy() {
    QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name", "Spring"); // name like %spring%
    bookDao.selectList(queryWrapper);
}
  • 支持动态拼写查询条件
ini
复制代码
@Test
void testGetBy2() {
    String name = "1";
    LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //if (name != null) lambdaQueryWrapper.like(Book::getName,name);
    
    // 条件
    lambdaQueryWrapper.like(Strings.isNotEmpty(name), Book::getName, name);
    bookDao.selectList(lambdaQueryWrapper);
}

image.png

5.6-业务层标准开发(基础CRUD)

  • Service层接口定义与数据层接口定义具有较大区别,不要混用

    • selectByUserNameAndPassword(String username,String password); 数据层接口
    • login(String username,String password); Service层接口
  • 接口定义,Service接口名称定义成业务名称,并与Dao接口名称进行区分

scss
复制代码
public interface BookService {

    Boolean save(Book book);

    Boolean update(Book book);

    Boolean delete(Integer id);

    Book getById(Integer id);

    List<Book> getAll();

    IPage<Book> getPage(int currentPage,int pageSize);
}
  • 实现类定义
typescript
复制代码
@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;
    }
}
  • 测试类定义
scss
复制代码
@SpringBootTest
public class BookServiceTestCase {

    @Autowired
    private BookService bookService;

    @Test
    void testGetById() {
        System.out.println(bookService.getById(4));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(14);
        book.setType("测试数据asfd");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.update(book);
    }

    @Test
    void testDelete() {
        bookService.delete(14);
    }

    @Test
    void testGetAll() {
        System.out.println(bookService.getAll());
    }

    @Test
    void testGetPage() {
        IPage<Book> page = bookService.getPage(2, 5);
        System.out.println(page.getCurrent());
        System.out.println(page.getSize());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
        System.out.println(page.getRecords());
    }
}

5.7(基于MyBatisPlus构建)

  • 快速开发方案

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

    • 使用通用接口(ISerivce)快速开发Service image.png
  • 实现类定义,使用通用实现类(ServiceImpl<M,T>)快速开发ServiceImpl

scala
复制代码
@Service 
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService { }
  • 实现类追加功能,可以在通用接口基础上做功能重载或功能追加,注意重载时不要覆盖原始操作,避免原始提供的功能丢失
typescript
复制代码
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

    @Autowired
    private BookDao bookDao;

    public Boolean insert(Book book) {
        return bookDao.insert(book) > 0;
    }

    public Boolean modify(Book book) {
        return bookDao.updateById(book) > 0;
    }

    public Boolean delete(Integer id) {
        return bookDao.deleteById(id) > 0;
    }

    public Book get(Integer id) {
        return bookDao.selectById(id);
    }
}
  • 测试类定义
scss
复制代码
@SpringBootTest
public class BookServiceTest {

    @Autowired
    private IBookService bookService;

    @Test
    void testGetById() {
        System.out.println(bookService.getById(4));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(14);
        book.setType("===========");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.updateById(book);
    }

    @Test
    void testDelete() {
        bookService.removeById(14);
    }

    @Test
    void testGetAll() {
        System.out.println(bookService.list());
    }

    @Test
    void testGetPage() {
        IPage<Book> page = new Page<>(2, 5);
        bookService.page(page);
        System.out.println(page.getCurrent());
        System.out.println(page.getSize());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
        System.out.println(page.getRecords());
    }
}

5.8表现层标准开发

  • 基于Restful进行表现层接口开发
  • 使用Postman测试表现层接口功能
  • 表现层开发
less
复制代码
@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("{currentPage}/{pageSize}")
    public IPage<Book> getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
        return bookService.getPage(currentPage, pageSize);
    }
}
  • 添加分页的业务层方法
  • IBookService
arduino
复制代码
IPage<Book> getPage(int currentPage,int pageSize);
  • BookServiceImpl
java
复制代码
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
    IPage page = new Page(currentPage, pageSize);
    bookDao.selectPage(page, null);

    return page;
}
  • 功能测试 image.png

39-表现层数据一致性处理(R对象)

  • 之前的格式,当数据为 null 可能出现的问题

    • 查询id不存在的数据,返回 null
    • 查询过程中抛出异常,catch 中返回 null image.png
  • 增加一个 data 属性,把数据全部封装到 data 里 image.png

  • 增加 一个状态属性 image.png

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

kotlin
复制代码
@Data
public class R {
    private Boolean flag;
    private Object data;

    public R() {
    }

    /**
     * 不返回数据的构造方法
     *
     * @param flag
     */
    public R(Boolean flag) {
        this.flag = flag;
    }

    /**
     * 返回数据的构造方法
     *
     * @param flag
     * @param data
     */
    public R(Boolean flag, Object data) {
        this.flag = flag;
        this.data = data;
    }
}
  • 表现层接口统一返回值类型结果
less
复制代码
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private IBookService bookService;

    @GetMapping
    public R getAll() {
        return new R(true, bookService.list());
    }

    @PostMapping
    public R save(@RequestBody Book book) {
        return new R(bookService.save(book));

    }

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

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

    @GetMapping("{id}")
    public R getById(@PathVariable Integer id) {
        return new R(true, bookService.getById(id));
    }

    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
        return new R(true, bookService.getPage(currentPage, pageSize));
    }
}

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

  • 使用VUE的方法时提示报错:

  • 表示:该方法定义的缺陷是不支持当前的JavaScript版本,虽然可以程序可以正常运行,但是这个方法会出现红色的波浪线,很不爽。

  • 解决办法:
    打开 File -> Settings -> Languages & Frameworks -> Javascript
    把JavaScript版本为ECMAScript 6就可以了

    • 前后端分离结构设计中页面归属前端服务器
    • 单体工程中页面放置在resources目录下的static目录中(建议执行clean)
    • 前端发送异步请求,调用后端接口
javascript
复制代码
//钩子函数,VUE对象初始化完成后自动执行 
created() { 
    //调用查询全部数据的操作 
    this.getAll(); 
},

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

image.png

  • 小结:

    1. 单体项目中页面放置在resources/static目录下
    2. created钩子函数用于初始化页面时发起调用
    3. 页面使用axios发送异步请求获取数据后确认前后端是否联通

41-列表功能

  • 列表页
javascript
复制代码
//列表
getAll() {
    //发送异步请求
    axios.get("/books").then((res) => {
        //console.log(res.data);
        this.dataList = res.data.data;
    })
},

image.png

  • 小结

    • 将查询数据返回到页面,利用前端数据双向绑定进行数据展示

42-添加功能

  • 弹出添加窗口
javascript
复制代码
// 弹出添加窗口 
handleCreate() {
    this.dialogFormVisible = true;
},
  • 清除数据
javascript
复制代码
//重置表单 
resetForm() { 
    this.formData = {}; 
},
  • 在弹出添加窗口时 清除数据
javascript
复制代码
//弹出添加窗口 
handleCreate() { 
    this.dialogFormVisible = true; 
    this.resetForm(); 
},
  • 发送添加请求
kotlin
复制代码
   //添加
   handleAdd() {
       axios.post("/books", this.formData).then((res) => {
           //判断当前操作是否成功
           if (res.data.flag) {
               //1.关闭弹层
               this.dialogFormVisible = false;
               this.$message.success("添加成功");
           } else {
               this.$message.error("添加失败");
           }
       }).finally(() => {
           //2.重新加载数据
           this.getAll();
       })
   },
  • 取消添加
javascript
复制代码
//取消 
cancel() { 
    //1.关闭弹层 
    this.dialogFormVisible = false; 
    //2.提示用户 
    this.$message.info("当前操作取消"); 
},
  • 小结:

    1. 请求方式使用POST调用后台对应操作
    2. 添加操作结束后动态刷新页面加载数据
    3. 根据操作结果不同,显示对应的提示信息
    4. 弹出添加Div时清除表单数据

43-删除功能

  • 删除
javascript
复制代码
  // 删除
  handleDelete(row) {
      axios.delete("/books/" + row.id).then((res) => {
          if (res.data.flag) {
              this.$message.success("删除成功");
          } else {
              this.$message.error("删除失败");
          }
      }).finally(() => {
          this.getAll();
      });
  }
  • 加入确认删除对话框
kotlin
复制代码
   // 删除
   handleDelete(row) {
       //1. 弹出提示框
       this.$confirm("些操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
           //2. 做删除业务
           axios.delete("/books/" + row.id).then((res) => {
               //判断当前操作是否成功
               if (res.data.flag) {
                   this.$message.success("删除成功");
               } else {
                   this.$message.error("删除失败");
               }
           }).finally(() => {
               //2.重新加载数据
               this.getAll();
           })
       }).catch(() => {
           //3. 取消删除
           this.$message.info("取消操作");
       });

   },
  • 小结:

    1. 请求方式使用Delete调用后台对应操作
    2. 删除操作需要传递当前行数据对应的id值到后台
    3. 删除操作结束后动态刷新页面加载数据
    4. 根据操作结果不同,显示对应的提示信息
    5. 删除操作前弹出提示框避免误操作

44-修改功能(加载数据)

  • 弹出修改窗口
kotlin
复制代码
  //弹出编辑窗口
  handleUpdate(row) {
      axios.get("/books/" + row.id).then((res) => {
          if (res.data.flag && res.data.data != null) {
              // 展示弹层,加载数据
              this.dialogFormVisible4Edit = true;
              this.formData = res.data.data;
          } else {
              this.$message.error("数据同步失败,自动刷新");
          }
      }).finally(() => {
          //重新加载数据
          this.getAll();
      });
  },
  • 删除消息维护
kotlin
复制代码
   // 删除
   handleDelete(row) {
       //1. 弹出提示框
       this.$confirm("些操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
           //2. 做删除业务
           axios.delete("/books/" + row.id).then((res) => {
               //判断当前操作是否成功
               if (res.data.flag) {
                   this.$message.success("删除成功");
               } else {
                   this.$message.error("数据同步失败,自动刷新");
               }
           }).finally(() => {
               //2.重新加载数据
               this.getAll();
           });
       }).catch(() => {
           //3. 取消删除
           this.$message.info("取消操作");
       });

   },
  • 小结:

    1. 加载要修改数据通过传递当前行数据对应的id值到后台查询数据
    2. 利用前端数据双向绑定将查询到的数据进行回显

45-修改功能

  • 修改
kotlin
复制代码
  //修改
  handleEdit() {
      axios.put("/books", this.formData).then((res) => {
          //判断当前操作是否成功
          if (res.data.flag) {
              //1.关闭弹层
              this.dialogFormVisible4Edit = false;
              this.$message.success("修改成功");
          } else {
              this.$message.error("修改失败");
          }
      }).finally(() => {
          //2.重新加载数据
          this.getAll();
      });
  },
  • 取消添加和修改
kotlin
复制代码
  //取消
  cancel() {
      //1.关闭弹层
      this.dialogFormVisible = false;
      this.dialogFormVisible4Edit = false;
      //2.提示用户
      this.$message.info("当前操作取消");
  },
  • 小结:

    1. 请求方式使用PUT调用后台对应操作
    2. 修改操作结束后动态刷新页面加载数据(同新增)
    3. 根据操作结果不同,显示对应的提示信息(同新增)

46-异常消息处理

  • 业务操作成功或失败返回数据格式
json
复制代码
{ "flag": true, "data": null } 
{ "flag": false, "data": null }
  • 后台代码BUG导致数据格式不统一性
json
复制代码
{ "timestamp": "2021-11-07T12:44:29.343+00:00", "status": 500, "error": "Internal Server Error", "path": "/books" }
  • 对异常进行统一处理,出现异常后,返回指定信息
java
复制代码
@RestControllerAdvice
public class ProjectExceptionAdvice {

    //拦截所有的异常信息
    @ExceptionHandler(Exception.class)
    public R doException(Exception ex) {
        // 记录日志
        // 发送消息给运维
        // 发送邮件给开发人员 ,ex 对象发送给开发人员
        ex.printStackTrace();
        return new R(false, null, "系统错误,请稍后再试!");
    }
}
  • 修改表现层返回结果的模型类,封装出现异常后对应的信息

    • flag:false
    • Data: null
    • 消息(msg): 要显示信息
kotlin
复制代码
@Data
public class R{
	private Boolean flag;
	private Object data;
	private String msg;
	public R(Boolean flag,Object data,String msg){
		this.flag = flag;
		this.data = data;
		this.msg = msg;
	}
}
  • 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
kotlin
复制代码
  //添加
  handleAdd() {
      axios.post("/books", this.formData).then((res) => {
          //判断当前操作是否成功
          if (res.data.flag) {
              //1.关闭弹层
              this.dialogFormVisible = false;
              this.$message.success("添加成功");
          } else {
              this.$message.error(res.data.msg);
          }
      }).finally(() => {
          //2.重新加载数据
          this.getAll();
      })
  },
  • 可以在表现层Controller中进行消息统一处理
less
复制代码
    @PostMapping
    public R save(@RequestBody Book book) throws IOException {
		//if (book.getName().equals("123")) throw new IOException();
        boolean flag = bookService.save(book);
        return new R(flag, flag ? "添加成功^_^" : "添加失败-_-!");
    }
  • 页面消息处理
kotlin
复制代码
  //添加
  handleAdd() {
      axios.post("/books", this.formData).then((res) => {
          //判断当前操作是否成功
          if (res.data.flag) {
              //1.关闭弹层
              this.dialogFormVisible = false;
              this.$message.success(res.data.msg);
          } else {
              this.$message.error(res.data.msg);
          }
      }).finally(() => {
          //2.重新加载数据
          this.getAll();
      })
  },
  • 小结:

    1. 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
    2. 异常处理器必须被扫描加载,否则无法生效
    3. 表现层返回结果的模型类中添加消息属性用来传递消息到页面

47-分页

  • 页面使用 el 分页组件添加分页功能
ini
复制代码
  <!--分页组件-->
  <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>
  • 定义分页组件需要使用的数据并将数据绑定到分页组件
kotlin
复制代码
data: { 
    pagination: { // 分页相关模型数据 
        currentPage: 1, // 当前页码 
        pageSize: 10, // 每页显示的记录数 
        total: 0, // 总记录数 
        } 
},
  • 替换查询全部功能为分页功能
javascript
复制代码
getAll() {
    axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {});
},
  • 分页查询

    • 使用路径参数传递分页数据或封装对象传递数据
less
复制代码
    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
        return new R(true, bookService.getPage(currentPage, pageSize));
    }
  • 加载分页数据
kotlin
复制代码
//分页查询
getAll() {
    //发送异步请求
    axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
        //console.log(res.data);
        this.pagination.currentPage = res.data.data.current;
        this.pagination.pageSize = res.data.data.size;
        this.pagination.total = res.data.data.total;

        this.dataList = res.data.data.records;
    })
},
  • 分页页码值切换
javascript
复制代码
   //切换页码
   handleCurrentChange(currentPage) {
       //修改页码值为当前选中的页码值
       this.pagination.currentPage = currentPage;
       //执行查询
       this.getAll();
   },
  • 小结:

    1. 使用el分页组件
    2. 定义分页组件绑定的数据模型
    3. 异步调用获取分页数据
    4. 分页数据页面回显

48-分页功能维护(删除BUG)

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

49-条件查询

  • 查询条件数据封装

    • 单独封装
    • 与分页操作混合封装
go
复制代码
   pagination: {//分页相关模型数据
       currentPage: 1,//当前页码
       pageSize: 10,//每页显示的记录数
       total: 0,//总记录数
       type: "",
       name: "",
       description: ""
   }
  • 页面数据模型绑定
ini
复制代码
<div class="filter-container">
    <el-input placeholder="图书类别" v-model="pagination.type" class="filter-item" />
    <el-input placeholder="图书名称" v-model="pagination.name" class="filter-item" />
    <el-input placeholder="图书描述" v-model="pagination.description" class="filter-item" />
    <el-button @click="getAll()" class="dalfBut">查询</el-button>
    <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>
  • 组织数据成为get请求发送的数据
kotlin
复制代码
   //分页查询
   getAll() {
       console.log(this.pagination.type);

       //  /books/1/10?type=???&name=???&decription=?? ;
       //1. 获取查询条件 , 拼接查询条件
       param = "?name=" + this.pagination.name;
       param += "&type=" + this.pagination.type;
       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.currentPage = res.data.data.current;
           this.pagination.pageSize = res.data.data.size;
           this.pagination.total = res.data.data.total;

           this.dataList = res.data.data.records;
       })
   },
  • 条件参数组织可以通过条件判定书写的更简洁
  • Controller接收参数
less
复制代码
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
	System.out.println("参数=====>"+book);
	IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);
	return new R(null != pageBook ,pageBook);
}
  • 业务层接口功能开发
java
复制代码
     /**
   * 分页的条件查询
   *
   * @param currentPage
   * @param pageSize
   * @param book
   * @return
   */
  IPage<Book> getPage(Integer currentPage, int pageSize, Book book);
  • 业务层接口实现类功能开发
less
复制代码
    @Override
    public IPage<Book> getPage(Integer currentPage, int pageSize, Book book) {

        LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();

        lambdaQueryWrapper.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
        lambdaQueryWrapper.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
        lambdaQueryWrapper.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());

        IPage page = new Page(currentPage, pageSize);
        bookDao.selectPage(page, lambdaQueryWrapper);

        return page;
    }
  • Controller调用业务层分页条件查询接口
less
复制代码
    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize, Book book) {

        // System.out.println("book=>" + book);

        IPage<Book> page = bookService.getPage(currentPage, pageSize, book);
        // 如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        if (currentPage > page.getPages()) {
            page = bookService.getPage((int) page.getPages(), pageSize, book);
        }
        return new R(true, page);
    }
  • 页面回显数据
kotlin
复制代码
   //分页查询
   getAll() {
       console.log(this.pagination.type);

       //  /books/1/10?type=???&name=???&decription=?? ;
       //1. 获取查询条件 , 拼接查询条件
       param = "?name=" + this.pagination.name;
       param += "&type=" + this.pagination.type;
       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.currentPage = res.data.data.current;
           this.pagination.pageSize = res.data.data.size;
           this.pagination.total = res.data.data.total;

           this.dataList = res.data.data.records;
       })
   },
  • 小结:

    • 定义查询条件数据模型(当前封装到分页数据模型中)
    • 异步调用分页功能并通过请求参数传递数据到后台

50-基础篇完结

  • 基于SpringBoot的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目录中