基于 SpringBoot 的 SSMP 整合案例

基于 SpringBoot 的 SSMP 整合案例

一个简单的图书管理demo,基于 SSMP(Spring+SpringMVC+MyBatis-Plus)的CRUD (增删改查) 模块

案例实现方案分析与流程解析

案例实现方案分析

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

SSMP案例制作流程解析

  • 先开发基础CRUD功能,做一层测一层
  • 调通页面,确认异步提交成功后,制作所有功能
  • 添加分页功能与查询功能

模块创建

Untitled

Untitled

pom.xml

		<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>

        <!--MyBatisPlus对应starter-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--druid对应starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>

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

tbl_book.sql

DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 19 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Java编程思想(第4版)', 'Java学习经典,殿堂级著作!赢得了全球程序员的广泛赞誉。');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', '计算机组成原理', '资深的计算机体系结构教育家Alan Clements博士编写,涵盖计算机系统的组成和体系结构的基本概念、指令系统以及处理器实现等涉及计算机组成原理课程的内容。');
INSERT INTO `tbl_book` VALUES (3, '程序设计', 'C++ Primer Plus 第6版 中文版', 'C++程序设计经典教程,畅销30年的C++大百科全书全新升级,经典C++入门教程十年新版再现');
INSERT INTO `tbl_book` VALUES (4, '程序设计', 'RocketMQ技术内幕:RocketMQ架构设计与实现原理(第2版)', '畅销书升级,RocketMQ创始人高度评价,深入源码分析技术架构和实现原理,打造高性能、高可用、高吞吐量、低延迟RocketMQ');
INSERT INTO `tbl_book` VALUES (5, '程序设计', ' 深入理解Java虚拟机:JVM高级特性与实践(第3版)', '周志明虚拟机新作,第3版新增内容近50%,5个维度全面剖析JVM,大厂面试知识点全覆盖。与 Java编程思想、Effective Java、Java核心技术 堪称:Java四大名著');
INSERT INTO `tbl_book` VALUES (6, '历史', '见识城邦·人类简史:从动物到上帝(新版)', '以色列新锐历史学家尤瓦尔·赫拉利代表作,第十届文津图书奖获奖作品');
INSERT INTO `tbl_book` VALUES (7, '历史', '中国通史', '吕思勉先生写给普通读者的中国通史入门书,用白话文写成的中国通史,把历史从“帝王的家谱”转变为人类的进化史');
INSERT INTO `tbl_book` VALUES (8, '哲学', '理想国(柏拉图代表作)', '奠定西方哲学史的源流之作。2021新译本,以斯灵斯校勘本为底本,遵照“字对字”的原则,从古希腊原文直译,兼顾准确性和语言通顺性,助你读懂理想国。');
INSERT INTO `tbl_book` VALUES (9, '哲学', '苏格拉底的申辩', '《柏拉图注疏集:苏格拉底的申辩》记述的是公元前399年,一个叫莫勒图斯的年轻人在雅典状告哲学家苏格拉底,说他不信城邦诸神,引进新的精灵之事,败坏青年。 于是,苏格拉底被传讯,在500人组成的陪审团面前作了著名的申辩。');
INSERT INTO `tbl_book` VALUES (10, '文学', '鲁迅全集', '大师全集,完整收录,鲁迅毕生之心血尽归于此。\r\n\r\n  鲁迅是中国20世纪的文学家、思想家、革命家,中国近代文学巨匠。他早年留学于日本,后来弃医从文,他用笔耕不辍的文字为新一代青年们指引方向,在国内外思想文化领域有着极高的声誉。');
INSERT INTO `tbl_book` VALUES (11, '文学', '人间清醒', '茅盾文学奖获得者梁晓声2021全新力作');
INSERT INTO `tbl_book` VALUES (12, '经济', '八次危机:中国的真实经验1949-2009', '著名“三农”专家温铁军,用经济的独特视角,重新审视中国的1949-2009,历史给我了我们怎样的真实经验?');

SET FOREIGN_KEY_CHECKS = 1;

application.yml

# 设置端口为80方便访问
server:
  port: 80

小结

  1. 勾选 SpringMVC 与 MySQL 坐标
  2. 修改配置文件为 yml 格式
  3. 设置端口为80方便访问

实体类快速开发(lombok)

  • lombok,一个Java类库,提供了一组注解,简化POJO实体类开发
				<!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
  • lombok 版本由 SpringBoot 提供,无需指定版本
  • 常用注解:@Data
@Data
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
}
  • 为当前实体类在编译期设置对应的 get/set方法toString方法hashCode方法equals方法

小结

  1. 实体类制作
  2. 使用lombok简化开发
  • 导入lombok无需指定版本,由SpringBoot提供版本
  • @Data 注解

数据层标准开发(基础 CRUD

  • 导入 MyBatisPlus 与 Druid 对应的 starter
				<!--MyBatisPlus对应starter-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--druid对应starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>
  • 配置 druid数据源 与 MyBatisPlus 对应的基础配置(id生成策略使用数据库自增策略)
# druid 数据源配置
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      username: root
      password: 283619

# mybatis-plus 配置
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键自增策略

数据层接口

  • 继承 BaseMapper 并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {

}

编写测试用例

  • 单个查询、全部查询、增删改查
@SpringBootTest
public class BookDaoTestCase {

    @Autowired
    private BookDao bookDao;

    // 根据id查询单个数据
    @Test
    void testGetById() {
        System.out.println(bookDao.selectById(1));
    }

    // 新增数据
    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅");
        bookDao.insert(book);
    }

    // 更新操作
    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(13);
        book.setType("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅-新文化运动");
        bookDao.updateById(book);
    }

    // 删除操作
    @Test
    void testDelete() {
        bookDao.deleteById(19);
    }

    // 查询全部数据
    @Test
    void testGetAll() {
        List<Book> list = bookDao.selectList(null);
        for (Book book : list) {
            System.out.println(book);
        }
    }

}

小结

  1. 手工导入starter坐标(2个)
  2. 配置数据源与MyBatisPlus对应的配置
  3. 开发Dao接口(继承BaseMapper)
  4. 制作测试类测试Dao功能是否有效

开启 MyBatis-Plus 运行日志

  • 为方便调试可以开启 MyBatis-Plus 的日志
  • 使用配置方式开启日志,设置日志输出方式为标准输出
# mybatis-plus 配置
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键自增策略
  configuration:
    # 开启MyBatisPlus的日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 标准输出

Untitled

分页功能实现

  • 分页操作需要设定分页对象 IPage
		// 分页操作
    @Test
    void testGetPage() {
        IPage page = new Page(1, 5);
        bookDao.selectPage(page, null);
    }
  • IPage对象中封装了分页操作中的所有数据
    • 数据
    • 当前页码值
    • 每页数据总量
    • 最大页码值
    • 数据总量
  • 分页操作是在 MyBatis-Plus 的常规操作基础上增强得到,内部是动态的拼写SQL语句
    ,因此需要增强对应的功能,使用 MyBatis-Plus 拦截器实现
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 1. 定义 Mybatis-Plus 拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 2. 添加具体的拦截器(分页拦截器)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }

}
  • 测试分页数据
		// 分页操作
    @Test
    void testGetPage() {
        IPage page = new Page(2, 5);
        bookDao.selectPage(page, null);
        System.out.println("获取当前页:" + page.getCurrent());
        System.out.println("获取每页显示个数:" + page.getSize());
        System.out.println("获取总个数:" + page.getTotal());
        System.out.println("获取总页数:" + page.getPages());
        // 获取分页查询数据
        System.out.println("获取分页查询数据:");
        List<Book> books = page.getRecords();
        for (Book book : books) {
            System.out.println(book);
        }
    }

Untitled

小结

  1. 使用IPage封装分页数据
  2. 分页操作依赖MyBatisPlus分页拦截器实现功能
  3. 借助MyBatisPlus日志查阅执行SQL语句

条件查询

写法一

		// 根据条件进行查询
    @Test
    void testGetBy() {
        QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
        // 查询包含"java"名称的书籍有哪些
        queryWrapper.like("name", "java");
        bookDao.selectList(queryWrapper);
    }

Untitled

写法二(推荐)

  • 使用 QueryWrapper 对象封装查询条件,推荐使用 LambdaQueryWrapper 对象,所有查询操作封装成方法调用
		// 根据条件进行查询
    @Test
    void testGetBy2() {
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        // 查询包含"java"名称的书籍有哪些
        queryWrapper.like(Book::getName, "java");
        bookDao.selectList(queryWrapper);
    }

Untitled

支持动态拼写查询条件(业务开发一般是这种写法

  • 当查询条件为空时,相当于查询全部数据
		// 根据条件进行查询
    @Test
    void testGetBy3() {
        String name = null;
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        // 如果name为空,就不进行查询
        queryWrapper.like(name != null, Book::getName, name);
        bookDao.selectList(queryWrapper);
    }

Untitled

  • 查询条件不为空时,进行条件查询
		// 根据条件进行查询
    @Test
    void testGetBy3() {
        String name = "中国";
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        // 查询包含"中国"名称的书籍有哪些
        // 如果name为空,就不进行查询
        queryWrapper.like(name != null, Book::getName, name);
        bookDao.selectList(queryWrapper);
    }

Untitled

小结

  1. 使用QueryWrapper对象封装查询条件
  2. 推荐使用LambdaQueryWrapper对象
  3. 所有查询操作封装成方法调用
  4. 查询条件支持动态条件拼装

业务层标准开发(基础 CRUD

  • Service层接口定义与数据层接口定义具有较大区别,不要混用
    • selectByUserNameAndPassword(String username,String password); 数据层接口
    • login(String username,String password); Service层接口

接口定义

public interface BookService {

    Boolean save(Book book);

    Boolean update(Book book);

    Boolean delete(Integer id);

    Book getById(Integer id);

    List<Book> getAll();

    // currentPage:当前页码值
    // pageSize:每页显示个数
    IPage<Book> getPage(int currentPage, int pageSize);

}

实现类定义

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public Boolean save(Book book) {
        // 当插入成功,返回的是影响行计数,大于0表示插入成功,否则插入失败
        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);
    }

    // currentPage:当前页码值
    // pageSize:每页显示个数
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage, pageSize);
        bookDao.selectPage(page, null);
        return page;
    }

}

测试类定义

@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("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(20);
        book.setType("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅-新文化运动");
        bookService.update(book);
    }

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

    @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.getTotal());
        System.out.println("获取总页数:" + page.getPages());
        // 获取分页查询数据
        System.out.println("获取分页查询数据:");
        List<Book> books = page.getRecords();
        for (Book book : books) {
            System.out.println(book);
        }
    }

}

小结

  1. Service接口名称定义成业务名称,并与Dao接口名称进行区分
  2. 制作测试类测试Service功能是否有效

业务层快速开发(基于 MyBatis-Plus 构建)

快速开发方案

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

Untitled

接口定义

public interface IBookService extends IService<Book> {

}
  • 接口追加功能
/**
 * @author xiexu
 * @create 2022-04-06 13:54
 */
public interface IBookService extends IService<Book> {

    // 注意:追加的操作与原始操作通过名称区分,功能类似

    // 新增
    Boolean saveBook(Book book);

    // 删除
    Boolean delete(Integer id);

    // 修改
    Boolean modify(Book book);

    // 根据id获取数据
    Book get(Integer id);

    // 分页查询
    IPage<Book> getPage(int currentPage, int pageSize);

}

Untitled

实现类定义

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

}
  • 实现类追加功能
/**
 * @author xiexu
 * @create 2022-04-06 13:58
 */
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

    @Autowired
    private BookDao bookDao;

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

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

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

    @Override
    public Book get(Integer id) {
        return bookDao.selectById(id);
    }

    @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 IBookService bookService;

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

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(21);
        book.setType("测试用例-小说");
        book.setName("测试用例-狂人日记");
        book.setDescription("鲁迅-新文化运动");
        bookService.updateById(book);
    }

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

    @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.getTotal());
        System.out.println("获取总页数:" + page.getPages());
        // 获取分页查询数据
        System.out.println("获取分页查询数据:");
        List<Book> books = page.getRecords();
        for (Book book : books) {
            System.out.println(book);
        }
    }

}

小结

  1. 使用通用接口(ISerivce)快速开发Service
  2. 使用通用实现类(ServiceImpl<M,T>)快速开发ServiceImpl
  3. 可以在通用接口基础上做功能重载或功能追加
  4. 注意重载时不要覆盖原始操作避免原始提供的功能丢失

表现层标准开发

  • 基于Restful进行表现层接口开发
  • 使用Postman测试表现层接口功能

表现层开发

/**
 * @author xiexu
 * @create 2022-04-07 10:09
 */
@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.updateById(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 int currentPage, @PathVariable int pageSize) {
        return bookService.getPage(currentPage, pageSize);
    }

}
  • 使用 Postman 进行功能测试

Untitled

Untitled

小结

  1. 基于Restful制作表现层接口
  • 新增:POST
  • 删除:DELETE
  • 修改:PUT
  • 查询:GET
  1. 接收参数
  • 实体数据:@RequestBody
  • 路径变量:@PathVariable

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

之前的格式

Untitled

增加一个 data 属性,把数据全部封装到 data 里

Untitled

  • 当数据为 null 可能出现的问题
    • 查询id不存在的数据,返回 null
    • 查询过程中抛出异常,catch 中返回 null

增加 一个状态属性

Untitled

  • 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
/**
 * @author xiexu
 * @create 2022-04-07 11:22
 */
@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;
    }

}
  • 表现层接口统一返回值类型结果
/**
 * @author xiexu
 * @create 2022-04-07 10:09
 */
@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.updateById(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 int currentPage, @PathVariable int pageSize) {
        return new R(true, bookService.getPage(currentPage, pageSize));
    }

}

Untitled

  • 使用 Postman 进行功能测试

Untitled

Untitled

Untitled

小结

  1. 设计统一的返回值结果类型便于前端开发读取数据
  2. 返回值结果类型可以根据需求自行设定,没有固定格式
  3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议

前后端调用

将提前编辑好的静态资源文件放入 resource/static 目录中,执行maven插件中的clean脚本清除 target 中的项目缓存。

Untitled

Untitled

打开浏览器访问 localhost/pages/books.html

Untitled

小结

  • 前后端分离结构设计中页面归属前端服务器
  • 单体工程中页面放置在resources目录下的static目录中(建议执行clean)
  • 前端发送异步请求,调用后端接口

axios 发送异步请求

  • books.html 中修改对应位置的代码,执行完毕后可以看到浏览器控制台和IDEA控制台有数据信息显示。
				//钩子函数,VUE对象初始化完成后自动执行
        created() {
            // 调用查询全部数据的操作
            this.getAll();
        },

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

Untitled

小结

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

列表功能

列表页

						//列表
            getAll() {
                // 发送异步请求
                axios.get("/books").then((res) => {
                    // console.log(res.data);
                    this.dataList = res.data.data; // res.data是前端返回给后端的数据名
                });
            },

Untitled

小结

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

添加功能

  • 弹出添加窗口
						// 弹出添加窗口
            handleCreate() {
                this.dialogFormVisible = true;
                this.resetForm();
            },
  • 清除数据
						// 重置表单
            resetForm() {
                this.formData = {};
            },
  • 在弹出添加窗口时 清除数据
						// 弹出添加窗口
            handleCreate() {
                this.dialogFormVisible = true;
                this.resetForm();
            },
  • 发送添加请求
						// 添加
            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();
                });
            },
  • 取消添加
						// 取消
            cancel() {
                // 1.关闭弹层
                this.dialogFormVisible = false;
                // 2.提示用户
                this.$message.info("当前操作取消");
            },

小结

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

删除功能

  • 删除基本操作
										// 发送异步请求
                    axios.delete("/books/" + row.id).then((res) => {
                        // 判断当前操作是否成功
                        if (res.data.flag) { // res.data是前端返回给后端的数据名
                            this.$message.success("删除成功");
                        } else {
                            this.$message.error("删除失败");
                        }
                    }).finally(() => {
                        // 删除后需要重新加载数据
                        this.getAll();
                    });
  • 加入确认删除对话框
						// 删除
            handleDelete(row) {
                // 1.删除弹出框
                this.$confirm("此操作将永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
                    // 2.发送异步请求
                    axios.delete("/books/" + row.id).then((res) => {
                        // 判断当前操作是否成功
                        if (res.data.flag) { // res.data是前端返回给后端的数据名
                            this.$message.success("删除成功");
                        } else {
                            this.$message.error("删除失败");
                        }
                    }).finally(() => {
                        // 删除后需要重新加载数据
                        this.getAll();
                    });
                }).catch(() => {
                    // 3.取消删除操作
                    this.$message.info("取消操作");
                });
            },

小结

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

修改功能

加载数据

  • 弹出修改窗口
						//弹出编辑窗口
            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();
                });
            },
  • 删除消息维护
						// 删除
            handleDelete(row) {
                // 1.删除弹出框
                this.$confirm("此操作将永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
                    // 2.发送异步请求
                    axios.delete("/books/" + row.id).then((res) => {
                        // 判断当前操作是否成功
                        if (res.data.flag) { // res.data是前端返回给后端的数据名
                            this.$message.success("删除成功");
                        } else {
                            this.$message.error("数据同步失败,自动刷新");
                        }
                    }).finally(() => {
                        // 删除后需要重新加载数据
                        this.getAll();
                    });
                }).catch(() => {
                    // 3.取消删除操作
                    this.$message.info("取消操作");
                });
            },

小结

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

修改功能

  • 修改
						//修改
            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();
                });
            },
  • 取消添加和修改
						//取消
            cancel() {
                // 1.关闭弹层
                this.dialogFormVisible = false;
                this.dialogFormVisible4Edit = false;
                // 2.提示用户
                this.$message.info("当前操作取消");
            },

小结

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

异常消息处理

Untitled

  • 对异常进行统一处理,出现异常后,返回指定信息
// 作为springmvc的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {

    // 拦截所有的异常信息
    @ExceptionHandler
    public R doException(Exception ex) {
        // 记录日志
        // 通知运维
        // 通知开发
        ex.printStackTrace();
        return new R(false, "服务器故障,请稍后再试!");
    }

}
  • 修改表现层返回结果的模型类,封装出现异常后对应的信息
  • flag:false
  • 消息(msg): 要显示信息
@Data
public class R {

    private Boolean flag;
    private Object data;
    private String msg;

    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;
    }

    /**
     * 返回异常信息的构造方法
     *
     * @param flag
     * @param msg
     */
    public R(Boolean flag, String msg) {
        this.flag = flag;
        this.msg = msg;
    }

}
  • 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
						//添加
            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中进行消息统一处理
		@PostMapping
    public R save(@RequestBody Book book) throws Exception {
        if (book.getName().equals("123")) {
            throw new Exception();
        }
        boolean flag = bookService.save(book);
        return new R(flag, flag ? "添加成功^_^" : "添加失败-_-!");
    }
  • 页面消息处理
						//添加
            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. 表现层返回结果的模型类中添加消息属性用来传递消息到页面

分页功能

  • 页面使用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) => {

	});
},
  • 分页查询
    • 使用路径参数传递分页数据或封装对象传递数据
		@GetMapping("/{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
        return new R(true, bookService.getPage(currentPage, pageSize));
    }
  • 加载分页数据
						//分页查询
            getAll() {
                // 发送异步请求
                axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
                    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();
            },

小结

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

分页功能维护(删除BUG)

  • 对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询
		@GetMapping("/{currentPage}/{pageSize}")
    public R 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 R(true, page);
    }

小结

  1. 基于业务需求维护删除功能

条件查询

  • 查询条件数据封装
    • 单独封装
    • 与分页操作混合封装
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请求发送的数据
						//分页查询
            getAll() {
                // 组织参数,拼接url请求地址
                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) => {
                    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 R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {
				System.out.println("参数====>" + book);
        IPage<Book> page = bookService.getPage(currentPage, pageSize);
        // 如果当前页码值大于总页码值,那么重新执行查询操作时,使用最大页码值作为当前页码值
        if (currentPage > page.getPages()) {
            page = bookService.getPage((int) page.getPages(), pageSize);
        }
        return new R(true, page);
    }
  • 业务层接口功能开发
		// 分页的条件查询
    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();
        lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
        lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
        lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
        IPage page = new Page(currentPage, pageSize);
        bookDao.selectPage(page, lqw);
        return page;
    }
  • Controller调用业务层分页条件查询接口
		@GetMapping("/{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {
//        System.out.println("参数====>" + 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);
    }

小结

  1. 定义查询条件数据模型(当前封装到分页数据模型中)
  2. 异步调用分页功能并通过请求参数传递数据到后台
本项目示例基于spring boot 最新版本(2.1.9)实现Spring Boot、Spring Cloud 学习示例,将持续更新…… 在基于Spring Boot、Spring Cloud 分布微服务开发过程中,根据实际项目环境,需要选择、集成符合项目需求的各种组件和积累各种解决方案。基于这样的背景下,我开源了本示例项目,方便大家快速上手Spring Boot、Spring Cloud 。 每个示例都带有详细的介绍文档、作者在使用过程中踩过的坑、解决方案及参考资料,方便快速上手为你提供学习捷径,少绕弯路,提高开发效率。 有需要写关于spring boot、spring cloud示例,可以给我提issue哦 ## 项目介绍 spring boot demo 是一个Spring Boot、Spring Cloud的项目示例,根据市场主流的后端技术,共集成了30+个demo,未来将持续更新。该项目包含helloworld(快速入门)、web(ssh项目快速搭建)、aop(切面编程)、data-redis(redis缓存)、quartz(集群任务实现)、shiro(权限管理)、oauth2(四种认证模式)、shign(接口参数防篡改重放)、encoder(用户密码设计)、actuator(服务监控)、cloud-config(配置中心)、cloud-gateway(服务网关)、email(邮件发送)、cloud-alibaba(微服务全家桶)等模块 ### 开发环境 - JDK1.8 + - Maven 3.5 + - IntelliJ IDEA ULTIMATE 2019.1 - MySql 5.7 + ### Spring Boot 模块 模块名称|主要内容 ---|--- helloworld|[spring mvc,Spring Boot项目创建,单元测试](https://github.com/smltq/spring-boot-demo/blob/master/helloworld/HELP.md) web|[ssh项目,spring mvc,过滤器,拦截器,监视器,thymeleaf,lombok,jquery,bootstrap,mysql](https://github.com/smltq/spring-boot-demo/blob/master/web/HELP.md) aop|[aop,正则,前置通知,后置通知,环绕通知](https://github.com/smltq/spring-boot-demo/blob/master/aop/HELP.md) data-redis|[lettuce,redis,session redis,YAML配置,连接池,对象存储](https://github.com/smltq/spring-boot-demo/blob/master/data-redis/HELP.md) quartz|[Spring Scheduler,Quartz,分布式调度,集群,mysql持久化等](https://github.com/smltq/spring-boot-demo/blob/master/quartz/HELP.md) shiro|[授权、认证、加解密、统一异常处理](https://github.com/smltq/spring-boot-demo/blob/master/shiro/HELP.md) sign|[防篡改、防重放、文档自动生成](https://github.com/smltq/spring-boot-demo/blob/master/sign/HELP.md) security|[授权、认证、加解密、mybatis plus使用](https://github.com/smltq/spring-boot-demo/blob/master/security/HELP.md) mybatis-plus-generator|[基于mybatisplus代码自动生成](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-generator) mybatis-plus-crud|[基于mybatisplus实现数据库增、册、改、查](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-crud) encoder|[主流加密算法介绍、用户加密算法推荐](https://github.com/smltq/spring-boot-demo/blob/master/encoder/HELP.md) actuator|[autuator介绍](https://github.com/smltq/spring-boot-demo/blob/master/actuator/README.md) admin|[可视化服务监控、使用](https://github.com/smltq/spring-boot-demo/blob/master/admin/README.md) security-oauth2-credentials|[oauth2实现密码模式、客户端模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-credentials/README.md) security-oauth2-auth-code|[基于spring boot实现oauth2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-auth-code/README.md) mybatis-multi-datasource|[mybatis、数据库集群、读写分离、读库负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-multi-datasource) template-thymeleaf|[thymeleaf实现应用国际化示例](https://github.com/smltq/spring-boot-demo/blob/master/template-thymeleaf) mq-redis|[redis之mq实现,发布订阅模式](https://github.com/smltq/spring-boot-demo/blob/master/mq-redis) email|[email实现邮件发送](https://github.com/smltq/spring-boot-demo/blob/master/email) jGit|[java调用git命令、jgit使用等](https://github.com/smltq/spring-boot-demo/blob/master/jGit) webmagic|[webmagic实现某电影网站爬虫示例](https://github.com/smltq/spring-boot-demo/blob/master/webmagic) netty|[基于BIO、NIO等tcp服务器搭建介绍](https://github.com/smltq/spring-boot-demo/blob/master/netty) ### Spring Cloud 模块 模块名称|主要内容 ---|--- cloud-oauth2-auth-code|[基于spring cloud实现oath2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/cloud-oauth2-auth-code) cloud-gateway|[API主流网关、gateway快速上手](https://github.com/smltq/spring-boot-demo/blob/master/cloud-gateway) cloud-config|[配置中心(服务端、客户端)示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-config) cloud-feign|[Eureka服务注册中心、负载均衡、声明式服务调用](https://github.com/smltq/spring-boot-demo/blob/master/cloud-feign) cloud-hystrix|[Hystrix服务容错、异常处理、注册中心示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-hystrix) cloud-zuul|[zuul服务网关、过滤器、路由转发、服务降级、负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/cloud-zuul) cloud-alibaba|[nacos服务中心、配置中心、限流等使用(系列示例整理中...)](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba) #### Spring Cloud Alibaba 模块 模块名称|主要内容 ---|--- nacos|[Spring Cloud Alibaba(一)如何使用nacos服务注册和发现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README1.md) config|[Spring Cloud Alibaba(二)配置中心多项目、多配置文件、分目录实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README2.md) Sentinel|[Spring Cloud Alibaba(三)Sentinel之熔断降级](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README3.md) Dubbo|[Spring Cloud Alibaba(四)Spring Cloud与Dubbo的融合](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README4.md) RocketMQ|[Spring Cloud Alibaba(五)RocketMQ 异步通信实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README5.md) ### 其它 模块名称|主要内容 ---|--- leetcode|[力扣题解目录](https://github.com/smltq/spring-boot-demo/blob/master/leetcode) ## Spring Boot 概述 Spring Boot简化了基于Spring的应用开发,通过少量的代码就能创建一个独立的、产品级别的Spring应用。 Spring Boot为Spring平台及第三方库提供开箱即用的设置,这样你就可以有条不紊地开始。多数Spring Boot应用只需要很少的Spring配置。 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Sprin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值