• SpringBoot学习_day3


    Spring Boot整合druid

    整合druid的步骤主要为:

    • 导入依赖druid-spring-boot-starter,mysql-connector-java依赖
    • 在在配置文件中配置数据库驱动
    • 就可以进行测试了

    Spring Boot整合Mybatis-plus

    Spring Boot整合Mybatis-Plus的基本步骤为:

    • 导入依赖Mybatis-Plus-boot-starter,因为需要利用数据库,所以还需要导入druid数据库源以及connector-java依赖,如下所示:

      <dependency>
          <groupId>com.baomidougroupId>
          <artifactId>mybatis-plus-boot-starterartifactId>
          <version>3.4.2version>
      dependency>
      
      <dependency>
          <groupId>com.alibabagroupId>
          <artifactId>druid-spring-boot-starterartifactId>
          <version>1.1.10version>
      dependency>
      
      <dependency>
          <groupId>mysqlgroupId>
          <artifactId>mysql-connector-javaartifactId>
          <scope>runtimescope>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 因为利用到了数据库,所以在配置文件中需要配置数据库驱动

      spring:
        datasource:
          druid:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://localhost:3306/ssmp?serverTimezone=Asia/Shanghai
            username: root
            password: root
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 对应的mapper接口继承了BaseMapper,这样这个mapper接口可以不需要再写任何的代码,然后可以执行对应的操作了,而如果利用的是mybatis的时候,需要再方法上面利用对应的注解来执行响应的操作,或者需要通过映射文件,创建代理对象来操作。如下所示:

      /*
      通过mybatis-plus来实现的时候,它是通过定义mapper,然后
      让这个mapper接口来继承BaseMapper,然后什么代码都不需要
      写,因为BaseMapper中已经定义了各种操作数据库的方法了,然后
      查询到的数据返回的就是T。
       */
      @Mapper
      public interface BookDao extends BaseMapper<Book> {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      BaseMapper的部分源码如下所示:

      public interface BaseMapper<T> extends Mapper<T> {
          int insert(T entity);//插入操作,如果没有配置,那么id并不支持自增操作,所以需要配置文件中进行配置
          int deleteById(Serializable id);//根据id进行删除操作
          int updateById(@Param("et") T entity);//根据id进行编辑
          int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
          T selectById(Serializable id);//根据id来查询
          T selectOne(@Param("ew") Wrapper<T> queryWrapper);//查询单个数据
          Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
          List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);//查询多条数据,如果queryWrraper不为null,那么就是根据这个参数进行条件查询
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 进行测试

    但是进行测试查询等操作的时候,发现数据库表找不到,这是因为mybatis-plus中默认是根据实体类的名字作为数据库表名,而我们的数据库表的名字则是在实体名的前面添加了tb_前缀,从而发生数据库表找不到。为了解决这个问题,需要在配置文件中设置数据库表的前缀。

    mybatis-plus:
      global-config:
        db-config:
          table-prefix: tb_ # 配置数据库表的前缀
    
    • 1
    • 2
    • 3
    • 4

    但是如果执行插入操作的时候,发现了报错,提示Could not set property 'id' of 'class com.example.domain.Book' with value '1557236544425811970',Cause: java.lang.IllegalArgumentException: argument type mismatch,因为mybatis-plus中id默认并不是自增操作的,所以我们同样需要在配置文件中配置id-Type,使其支持自增。

    mybatis-plus:
      global-config:
        db-config:
          table-prefix: tb_ # 配置数据库表的前缀 
          id-type: auto # 配置id是支持自增的
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但是这时候如果我们需要通过日志来调试,那么需要看到执行的sql语句,这时候我们同样需要在配置文件中进行配置,使得它是标准输出,如下所示:

    mybatis-plus:
      global-config:
        db-config:
          table-prefix: tb_ # 配置数据库表的前缀
          id-type: auto # 配置id是支持自增的
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置日志输出为标准输出,方便看到执行的sql语句
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    mybatis-plus实现分页操作时,那么这时候需要通过调用selectPage(IPage,QueryWrapper),这时候第一个参数设置查询的是第几页的记录,QueryWrapper封装的是条件,如果不为null,那么就是在条件查询的基础上,进行分页操作。

    当调用selctPage操作之后,返回值是一个IPage对象,也可以用传递的参数IPage来存放数据。这时候通过这个对象调用以下方法来获取对应的信息:

    • getPages() : 获取总页数
    • getTotal(): 获取总记录数
    • getCurrent(): 获取当前时第几页
    • getSize(): 获取每一页有多少行
    • getRecords(): 获取当前页的所有记录

    所以对应的代码为:

    测试类:

    package com.example.domain;
    
    public class Book {
        private Integer id;
        private String name;
        private String description;
    
        public Book() {
        }
    
        public Book(Integer id, String name, String description) {
            this.id = id;
            this.name = name;
            this.description = description;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        @Override
        public String toString() {
            return "Book{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", description='" + description + '\'' +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    @SpringBootTest
    public class BookDaoTest {
        @Autowired
        private BookDao bookDao;
        /*
        利用mybatis-plus来是实现分页操作,首先需要
        创建MybatisPlusInterceptor,在这个拦截器
        中添加一个内部拦截器PageInnerInterceptor,用于分页操作的。
        如果没有这一步,那么执行下面代码的的时候,sql语句不会出现limit子句。
        完成上面的操作之后,通过bookDao调用selectPage方法来执行分页操作,但是
        我们需要知道要查询的是第几页,并且每一页由多少条记录。所以需要传递参数
        IPage(他是一个接口),它已经封装了查询了第几页,每一页由多少条记录。
        selectPage(IPage,QueryWrapper),其中第二个参数用于条件查询的。
         */
        @Test
        public void testPage(){
            IPage page = new Page(2,5);//获取第1页的数据,每一页有5行
            bookDao.selectPage(page,null);//将执行selectPage操作,将查询到的数据存放到page中,第二个参数表示的是条件
            System.out.println("当前是第 " + page.getCurrent() + " 页");
            System.out.println("每一页有 " + page.getSize() + " 行");
            System.out.println("总共有 " + page.getTotal() + " 行,总页数有 " + page.getPages() + " 页");
            System.out.println("当前页的记录分别为: ");
            List<Book> records = page.getRecords();
            for(Book record : records){
                System.out.println(record);
            }
        }
     
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    但是运行的时候,就会通过日志看到,并没有看到LIMIT子句,所以查询的就是所有的数据:
    在这里插入图片描述

    所以在进行分页操作之前,首先我们需要添加一个MyBatisPlus拦截器,然后再这个拦截器的内部添加一个PaginationInnerInterceptor进行拦截操作,从而实现分页操作,对应的代码为:

    @Configuration //Mybatis-Plus的配置类
    public class MPConfig {
        @Bean //将方法返回值添加到IOC容器中
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            //mybatis-plus添加拦截器
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            //拦截器内部再次添加拦截器,用于分页操作,所以需要添加分页操作的拦截器
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            return interceptor;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    再次执行测试类的方法时,日志输出的sql语句就含有limit字句了,如下所示:
    在这里插入图片描述

    通过上面可以知道,如果需要实现条件查询,那么这时候我们再调用对应的方法的时候,传递参数QueryWrapper或者LambdaQueryWrapper对象即可,通过这个对象调用对应的方法来设置条件,代码如下所示:

        /*
        测试Mybatis-plus的条件查询,只需要添加QueryWrapper参数即可,也可以是
        LambdaWrapper参数,然后通过这个对象调用对应的方法来设置对应的条件。
         */
        @Test
        public void testGetBy(){
            //查询名字为book888,并且description为玄幻小说的书
            QueryWrapper<Book> qw = new QueryWrapper<>();
            qw.eq("name","book888");//第一个参数表示数据库表中的字段名
            qw.eq("description","玄幻小说");
            Book book = bookDao.selectOne(qw);
            System.out.println(book);
        }
        /*
        在条件查询中,如果利用的是参数QueryWrapper来设置条件,那么需要
        注意第一个参数数据库的字段名没有写错。所以这时候为了防止写错
        的情况,所以就利用LambdaQueryWrapper,通过lambda表达式来获取字段名即可
         */
        @Test
        public void testGetBy2(){
            LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper();
            /*
            lqw.eq(Book::getDescription,"玄幻小说");//获取description字段值为玄幻小说的Book
            但是这样写有弊端,这样通过设置日志之后,来看到它的sql语句,发现如果传递的参数是null
            也即lqw.eq(Book::getDescription, null)的时候,那么这时候执行的sql语句是查询
            description字段中为null的Book。也即null变成了它的值。
            所以为了避免这种情况的发生,我们需要定义一个变量来定义条件.
            第一种做法为:在执行这个操作之前,判断这个变量是否为null即可.
            第二种做法:因为queryWrapper对象调用对应的方法设置条件的时候,在字段名
            前面还有一个boolean参数,如果是true,那么添加这个条件,否则不添加。
            所以我们根据字段名是否为null即可。
            */
            String description = null;
            lqw.eq(description != null,Book::getDescription,description);//变量description不为null时添加条件
            List<Book> books = bookDao.selectList(lqw);
    
            for(Book book : books){
                System.out.println(book);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    但是建议传递的参数是LambdaQueryWrapper对象,这是因为QueryWrapper对象调用对应的方法来设置条件的时候,方法中字段名必须要和数据库表的一致,很容易写错,而在LambdaQueryWrapper对象中,通过实体来获取对应的属性,就可以获取到条件对应的值了。这是因为实体类中的属性必然和数据库表的字段名相同,所以安全性更加高。

    值得一提的是,如果第二个参数的值为null的话,也即lqw.eq(Book::getDescription,description)中的description为null的话,那么在日志中可以看到,他查询的是数据库表中description字段值为"null"的数据,但是事实上我们可能没有传递这个参数,如下所示:

    测试代码:

    @Test
    public void testGetBy(){
        //查询名字为book888,并且description为玄幻小说的书
        QueryWrapper<Book> qw = new QueryWrapper<>();
        qw.eq("name",null);//第一个参数表示数据库表中的字段名
        qw.eq("description","玄幻小说");
        Book book = bookDao.selectOne(qw);
        System.out.println(book);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    所以一种做法是在设置条件之前判断变量description是否为null,如果为null,那么不会进行条件查询,否则进行条件查询。

    但是还有第二种做法,LambdaQueryWrapper或者QueryWrapper对象中还有xxx(boolean,column,value)方法,其中第一个参数boolean的值如果为true,表示会设置条件进行条件查询,否则如果为false,表示不会设置条件。所以一般建议采用的是第二种方法。

    代码如下所示:

    @Test
        public void testGetBy(){
            //查询名字为book888,并且description为玄幻小说的书
            QueryWrapper<Book> qw = new QueryWrapper<>();
            String name = null;
            qw.eq(name != null,"name",name);//第一个参数表示数据库表中的字段名
            qw.eq("description","玄幻小说");
            List<Book> books = bookDao.selectList(qw);
            for(Book book : books) {
                System.out.println(book);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    同时也可以看到,如果有多个条件的时候,那么这时候我们只需要通过QueryWrapper或者LambdaQueryWrapper对象调用多个方法来设置条件即可。

    所以利用spring boot来整合第三方技术的时候,基本步骤为:导入xxx-starter依赖,如果这个依赖并没有在spring中配置,那么就需要自己手动来配置它的版本,负责不需要配置版本了。

    ssmp进行简单开发

    我们利用spring + spring mvc + mybatis-plus来进行开发一个简单图书管理,主要是来实现它的增删改查。

    准备工作:

    • 导入相关坐标:mybatis-plus-boot-starter,druid-spring-boot-starter,mysql-connector-java,spring-boot-starter-thymeleaf.之所以需要导入thymeleaf-spring-boot-starter,是因为我们需要进行渲染,并且在获取数据之后,我们可以通过thymeleaf来进行相应的操作,例如循环遍历等,否则,如果没有导入thymeleaf的话,那么就没有办法跳转到templates目录下面的html文件中。

      
      
      <dependency>
          <groupId>com.baomidougroupId>
          <artifactId>mybatis-plus-boot-starterartifactId>
          <version>3.4.2version>
      dependency>
      
      <dependency>
          <groupId>com.alibabagroupId>
          <artifactId>druid-spring-boot-starterartifactId>
          <version>1.1.10version>
      dependency>
      
      <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-webartifactId>
      dependency>
      
      <dependency>
          <groupId>mysqlgroupId>
          <artifactId>mysql-connector-javaartifactId>
          <scope>runtimescope>
      dependency>
      <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-testartifactId>
          <scope>testscope>
          <exclusions>
              <exclusion>
                  <groupId>org.junit.vintagegroupId>
                  <artifactId>junit-vintage-engineartifactId>
              exclusion>
          exclusions>
      dependency>
      
      
      <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-thymeleafartifactId>
          <version>2.6.6version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
    • 配置文件中配置数据库驱动,以及mybatis-plus

    • 建立数据库表tb_book,如果直接通过mybatis-plus来实现数据库的操作,很容易发生报错,例如查询操作的时候,会发生报错,提示数据库表book不存在,这是因为我们的数据库表前面还有前缀tb_,或者执行插入操作的时候,发生错误,这是因为mybatis-plus中默认id不是支持自增的,所以我们在建立数据库表之后,还需要在配置文件中通过mybatis-plus来配置数据库表的前缀,id的类型。此外还需要配置日志,从而方便调试。

    • 创建数据库表对应的实体类,以及controller,service,dao层

      public class Book {
          private Integer id;
          private String name;
          private String description;
      
          public Book() {
          }
      
          public Book(Integer id, String name, String description) {
              this.id = id;
              this.name = name;
              this.description = description;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getDescription() {
              return description;
          }
      
          public void setDescription(String description) {
              this.description = description;
          }
      
          @Override
          public String toString() {
              return "Book{" +
                      "id=" + id +
                      ", name='" + name + '\'' +
                      ", description='" + description + '\'' +
                      '}';
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      /*
      通过mybatis-plus来实现的时候,它是通过定义mapper,然后
      让这个mapper接口来继承BaseMapper,然后什么代码都不需要
      写,因为BaseMapper中已经定义了各种操作数据库的方法了,然后
      查询到的数据返回的就是T。
       */
      @Mapper
      public interface BookDao extends BaseMapper<Book> {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      /*
      在mybatis-plus中,service层的实现类也可以不写代码,
      可以通过定义一个IBookService,使得这个接口继承了IService
      然后定义这个接口的实现类IBookServiceImpl,使得在实现
      这个接口的同时,继承了ServiceImpl,其中这个M就是对应的Mapper接口,
      T就是对应的实体.
      此时的service实现类就已经有了操作的各种方法了,如果需要添加功能的
      时候,就需要手动在这个实现类中追加即可
       */
      public interface IBookService extends IService<Book> {
          IPage<Book> getPage(int currentPage, int size);
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      @Service
      public class IBookServiceImpl extends ServiceImpl<BookDao,Book> implements IBookService {
          //手动追加方法,实现分页操作
          @Autowired
          private BookDao bookDao;
          public IPage<Book> getPage(int currentPage, int size){
              IPage<Book> page = new Page(currentPage, size);
              bookDao.selectPage(page,null);
              return page;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

    在上面的操作完毕之后,可以进行开发了:

    • 查询所有的图书

      当我们查询图书的时候,将通过IBookService调用list()方法来返回一个List对象,这时候我们需要将这个数据封装到视图中,然后跳转到对应的界面。但是这时候如果数据太多,影响美观,所以需要进行分页操作,因此进入首页中查询的是第一页的数据,封装到页面中的数据是一个IPage对象。但是如果在前端中怎么显示数据呢?因为有多个数据,所以必然是要用到了循环来实现,因此需要通过thymeleaf中来进行循环操作。对应的代码为:

       @GetMapping
      //获取所有的数据,然后来到首页
      public ModelAndView index(ModelAndView modelAndView){
          //获取首页的数据,因为需要实现分页查找
          IPage<Book> page = new Page(1, 5);
          iBookService.page(page,null);
          //将r添加到前端中
          modelAndView.addObject("page",page);
          //设置跳转的页面
          modelAndView.setViewName("/pages/books.html");//html文件放到了templates/pages下面
          return modelAndView;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 编辑图书

      首先当我们点击要编辑哪一本书的时候,我们需要获取这个书的信息,然后跳转到编辑页面,而在这个编辑页面中显示的是这个图书原来的信息。当编辑完毕之后,再次提交,当编辑成功之后,直接重定向到首页中。对应的代码为:

      @GetMapping("/modifyById/{id}")
      public ModelAndView modifyById(@PathVariable("id")Integer id){
          Book book = iBookService.getById(id);
          ModelAndView modelAndView = new ModelAndView();
          //设置属性
          modelAndView.addObject("book",book);
          //设置跳转的页面
          modelAndView.setViewName("/pages/update.html");
          return modelAndView;
      }
      
      @PostMapping("/modifyById")
      /*
          通过这个注解@ResponseBody,告诉spring,这个返回值并不是用来页面跳转的,而是回写数据
          如果没有这个注解,那么下面的代码就会进行页面跳转,这时候由于方法返回值是void,那么就会
          再次来到当前的页面中.
           */
      @ResponseBody
      public void modifyById(Book book,HttpServletResponse response) throws IOException { //注意的是这里并没有在Book的前面使用注解@RequestBody,否则就会发生报错
          System.out.println("编辑后的书为: " + book);
          iBookService.updateById(book);
          response.sendRedirect("/books3");//为了避免当编辑完成之后,当来到首页的时候,url依旧没有发生变化,所以需要进行重定向
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      编辑页面:

      DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>编辑页面title>
      head>
      <body>
          <form action="/books3/modifyById" method="post">
              <input type="hidden" name="id" th:value="${book.id}">
              图书名字<input type="text" name="name" th:placeholder="${book.name}"><br>
              图书描述<input type="text" name="description" th:placeholder="${book.description}"><br>
              <input type="submit" value="提交">
          form>
      body>
      html>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      在看到上面的代码之后,可能会疑惑,为什么不可以在编辑完成之后,不可以通过注解@RequestBody来表示编辑之后的Book呢?我们尝试给这个参数前面添加这个注解,即代码如下所示:

      @ResponseBody
      public void modifyById(@RequestBody Book book,HttpServletResponse response)
      
      • 1
      • 2

      然后运行之后,结果如下所示:
      在这里插入图片描述

      编辑操作点击提交之后,变成了:
      在这里插入图片描述

      数据库中数据没有编辑成功,重新回到IDEA,发现控制台中打印这一串数据:

      2022-08-11 16:08:26.361  WARN 13940 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException:Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]
      
      • 1

      重点看Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported这句,提示application/x-www-form-urlencoed;charset=UTF-8不支持,这是因为什么呢?

      这是因为@RequestBody注解导致的。可以看一下这个文章:@RequestBody 的使用方法和注意事项,查看之后可以发现,原来是因为@RequestBody支持的是application/json格式,当发送请求之后,他会将json格式的字符串绑定到对应的bean中。而在一些表单form提交,**类型为application/x-www-form-urlencoded;charset=UTF-8**的情况下,并不能使用这个注解了。所以此时我们可以直接删除这个注解,就可以了,这就涉及到了spring中的POJO了。所以这就是为什么不可以在Book参数前面添加注解@RequestBody了.

    • 删除图书

      点击删除的超链接,当点击之后,就需要进行删除操作,删除完毕之后,需要重新重定向到首页,对应的代码如下所示:

      @GetMapping("/deleteById/{id}")
      @ResponseBody
      public void deleteById(@PathVariable("id")Integer id, HttpServletResponse response) throws IOException {
          iBookService.removeById(id);
          response.sendRedirect("/books3");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 添加图书

      这个操作和编辑类似,所以代码也是相似的:

      @GetMapping("/save")
      public String save(){
          return "/pages/add.html";
      }
      @PostMapping("/save")
      @ResponseBody
      public void save(Book book, HttpServletResponse response) throws IOException {
          System.out.println("添加的图书: " + book);
          iBookService.save(book);
          //重定向到首页
          response.sendRedirect("/books3");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 分页操作

      因为我们来到首页的时候,封装的数据是IPage对象,这个对象可以通过对应的方法来获取需要的信息,如下所示:

      1. getCurrent() : 获取当前页面是第几页
      2. getTotal(): 获取一共有多少行
      3. getPages(): 获取一共有多少页
      4. getSize(): 获取每一页有多少行
      5. getRecords(): 获取每一页的数据,返回的是List对象。

      所以这时候我们需要通过循环来遍历这个List对象,此时需要利用thymeleaf中的each用法。同时,要实现分页操作,还需要进行if判断,如果是第一页的话,那么上一页就会失效,同理,如果是最后一页,下一页就会失效。而if判断如果为true,那么修饰的标签就会显示,否则不会出现(并不是隐藏,而是真的没有这个标签)

      同时,为了点击下一页的时候,我们可以重新渲染页面,还需要再conroller中添加方法来实现分页操作,需要传递的参数是currentPage, size,表示第几页,以及每一页有多少行.

      @GetMapping("/getPage/{currentPage}/{size}")
      public ModelAndView getPage(@PathVariable("currentPage")Integer currentPage,
                                  @PathVariable("size")Integer size,
                                  ModelAndView modelAndView){
          //查询currentPage的记录,并且每页有size页
          IPage page = new Page(currentPage, size);
          iBookService.page(page,null);
          modelAndView.addObject("page",page);
          //设置跳转的视图
          modelAndView.setViewName("/pages/books.html");
          return modelAndView;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    books.html的代码为:

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>books页面title>
    head>
    <body>
        <h1>ssmp练习h1>
        <div>
            <a th:href="'/books3/save'">添加a>
        div>
        <div>
            <table>
                <thead>
                   <td>图书idtd>
                   <td>图书名字td>
                   <td>图书描述td>
                   <td>操作td>
                thead>
                <tr th:each="d:${page.records}">
                    <td th:text="${d.id}">td>
                    <td th:text="${d.name}">td>
                    <td th:text="${d.description}">td>
                    <td>
                        <a th:href="'/books3/modifyById/' + ${d.id}">编辑a>
                        <a th:href="'/books3/deleteById/' + ${d.id}">删除a>
                    td>
                tr>
            table>
            <div>
                
                <a th:href="'/books3/getPage/1/' + ${page.size}">首页a>
                <a th:href="'/books3/getPage/' + ${page.current - 1} + '/' + ${page.size}" th:if="${page.current ne 1}">上一页a>
                <a th:href="'#'" th:if="${page.current eq 1}">上一页a>
                <a th:href="'/books3/getPage/' + ${page.current + 1} + '/' + ${page.size}" th:if="${page.current ne page.pages}">下一页a>
                <a th:href="'#'" th:if="${page.current eq page.pages}">下一页a>
                <a th:href="'/books3/getPage/' + ${page.pages} + '/' + ${page.size}">尾页a>
            div>
        div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    前端页面如下所示(每一页有2行):
    在这里插入图片描述

  • 相关阅读:
    【优化模型】线性规划问题
    堆--完全二叉树
    盛最多水的容器,三数之和 ,有效的括号
    go-zero 是如何实现令牌桶限流的?
    java计算机毕业设计商店管理系统源码+系统+mysql数据库+lw文档+部署
    设计一个简单HTML爵士音乐网页(HTML+CSS)
    C++学习记录——삼십이 C++IO流
    25 - 线程池和指令系统
    GPU利用率背后的性能真相
    springboot + rabbitmq + redis实现秒杀
  • 原文地址:https://blog.csdn.net/weixin_46544385/article/details/126304700