springboot整合SSMP+Vue+Element-ui+Axios项目 学习笔记2

目录

准备工作

实体类的开发

数据层开发

测试数据层增删改查

分页查询:

条件查询:

业务层:

标准开发(基础CRUD):

快速开发(基于Mybatis-plus): 

表现层

使用postman测试请求:

表现层数据一致性处理

 前后端数据协议

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

显示全部数据 

增添数据

删除数据

错误分析 

 修改数据

 异常处理

springMVC异常处理器

分页查询

 Bug

条件查询

结尾 


准备工作

 

为了强化对于springboot基础的学习,这里做一个图书管理系统,用springboot整合SSMP框架来实现这个系统,这篇文章会记录在这个小系统的开发中的思路、过程、以及一些知识点。

前端用到的知识:Vue axios Element-ui

后端用到的知识:Springboot SSMP(Spring SpringMVC Mybatis-plus)

首先,给数据库的表做一下初始化

实体类的开发

在写pojo类时,我们通常的方法是定义一个类,类的成员变量跟表的字段相对应,但在这里我们提供一个更加简便的方法:Lombot

我们先在pom配置文件中添加一个Lombot的坐标

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

springboot中已经为我们收录了lombok的版本,因此这里不需要写版本号

在pojo类加一个注解@Data,就自动帮我们生成了get set toString hashCode equals等方法,因此我们不用再去手动创建这些方法

package com.yit.domain;

import lombok.Data;

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

数据层开发

数据层开发用到的技术为:Mybatis-plus,Druid

如果是从官网路径创建的项目,要先导入一下依赖坐标

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

再在配置文件中,连接上数据库

#把端口修改为80
server:
  port: 80

#连接数据库
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC
      username: root
      password: 123456

配置完成后,接下来就要做数据层接口,为数据层的接口BookDao添加一个@Mapper注解,用于识别。让这个类继承BaseMapper类,这样数据层基本就完成了,接下来就可以来测试数据层的各个方法。

@Mapper
public interface BookDao extends BaseMapper<Book>{

}

测试数据层增删改查

然后在text目录中新建一个dao包,写一个测试类,测试一下这个方法

@SpringBootTest
public class BookDaoTestCase {

    @Autowired
    BookDao bookDao;

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

 运行成功,说明前面的步骤没有问题。

当我们测试插入数据的方法时,会发现一个问题

原因是没有使用数据库中的id自增策略,不知道应该赋给id什么样的值,我们将配置文件改为以下内容,便可解决这个问题 

#把端口修改为80
server:
  port: 80

#连接数据库
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC
      username: root
      password: 123456
mybatis-plus:
  global-config:
    db-config:
#使用数据库的id自增策略
      id-type: auto

为了方便观察mp的运行情况,我们可以开启mp的调试日志

还是配置文件,加入下面的两行即可

mybatis-plus:
  global-config:
    db-config:
#使用数据库的id自增策略
      id-type: auto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

 日志就已经在控制台上出现了,这个日志仅在开发时使用,在上线时,这个日志一定要关掉,不然服务器会崩溃的。

注:在测试查询所有的方法是,是要传入一个参数的,这里传null即可,这样就能查询出所有的数据

分页查询:

 测试分页查询的方法,我们可以发现这里要传入两个参数

 第一个是page,第二个是queryWrapper,我们可以给第二个参数传一个null,而第一个参数需要的就是一个page对象,因此,我们要在测试方法里new一个page对象出来

Page的构造方法中,current表示的是第几页的数据,size则是表示一页中有几条数据。但这时如果测试,会发现分页查询是没有生效的。在mp中如果要使用分页查询的功能,要添加拦截器,而添加拦截器就要在springboot项目中写一个配置类,通过配置类来完成拦截器的添加。我们在com.yit包下新建一个config包,在里面写一个MPConfig类代码如下

//定义mp拦截器
@Configuration//加载为配置类
public class MPConfig {
    @Bean//将这个拦截器bean做出来
    public MybatisPlusInterceptor mybatisPlusInterceptor(){

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        //在拦截器中添加一个具体的拦截器,也就是用来做分页查询的拦截器,以后还会有其他的拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());

        return interceptor;
    }

这样才可以实现分页查询的功能,我们可以通过测试来观察一下结果。

分页查询成功实现,那我们如何拿到分页查询的这些数据呢?

可以通过page对象中的这五个方法,拿到总页数,当前页数,返回的数据,每页条数,数据总条数。

条件查询:

其实条件查询直接使用前面提到过的查询所有的方法即可,前面调用查询所有的时候,参数我们传入了一个null,其实这个参数就是条件。

我们将这个对象创建出来,后调用这个对象的like方法,其实对应的就是sql语句中的like关键字 模糊查询。将字段名和值传入like方法,再将这个对象作为条件传入selectList方法即可

@Test
    void textSelextByCondition(){
        QueryWrapper<Book> qw=new QueryWrapper<>();
        qw.like("type","计算机基础");
        bookDao.selectList(qw);

    }

查询成功

为了方便书写并减少错误,mp还为我们提供了一个对象LambdaQuaryWrapper,效果是一样的,还不容易写错。

    @Test
    void textSelextByCondition2(){
        LambdaQueryWrapper<Book> lqw=new LambdaQueryWrapper<>();
        lqw.like(Book::getType,"计算机基础");
        bookDao.selectList(lqw);

    }
}

有时type的值可能为null,这样就没必要再连接这个条件,按照以前的思路,我们可以通过if条件进行判断,现在mp为我们提供了一种新的方式,可以在like的参数中传入一个布尔类型的值,如果为true就连接条件,如果是false就不连接条件。

观察一下运行结果,还是查询所有,并没有为我们连接条件

如果将type改为字符串,那么条件将会连接

 

业务层:

写完数据层,接下来就要写业务层了

标准开发(基础CRUD):

 我们在com.yit包下新建一个service包,里面创建一个BookService接口,来定义service层中基本的方法,再创建一个impl包,在里面写这个接口的实现类,实现类的上面要加入@Service注解,将这个实现类定义为业务层的bean。注入BookDao接口并自动装配,然后在实现类中返回调用方法即可。条件查询先不着急写

public interface BookService {
    Boolean add(Book book);
    Boolean update(Book book);
    Boolean delete(Integer id);
    Book getById(Integer id);
    List<Book> getAll();
    IPage<Book> getPage(int currentPage,int pageSize);
}
@Service//将这个类定义为业务层的bean
public class BookServiceImpl implements BookService {
    @Autowired
    BookDao bookDao;

    @Override
    public Boolean add(Book book) {
        //insert的返回值为影响行数,若影响行大于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);
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page=new Page(currentPage,pageSize);
        bookDao.selectPage(page,null);
        return page;
    }
}

 接下来就可以在text目录下com.yit包下新建一个service包来测试了

 开发中测试时尽量还是要每个方法都测试一遍

快速开发(基于Mybatis-plus): 

 因为业务层的代码逻辑大部分都相同,因此mp也为我们提供了快速开发的方式。因为mp中给出的getPage较为复杂,我们在这里重写一下

接口:

//跟数据层一样,里面已经为我们封装好了很多方法
public interface IBookService extends IService<Book> {
    IPage<Book> getPage(int currentPage, int pageSize);
}

实现类:

@Service
public class IBookServiceImpl extends ServiceImpl<BookDao,Book> implements IBookService {
    @Autowired
    BookDao bookDao;
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page=new Page(currentPage,pageSize);
        bookDao.selectPage(page,null);
        return page;
    }
}

随后测试即可。如果想再加入业务层的其他功能,在接口和实现类中像标准开发一样写即可

表现层

数据层和业务层都完成了,那么接下来就要做表现层了。表现层中基于Restful进行表现层接口开发,然后使用postman来测试表现层接口

我们在yit包下新建一个controller包,里面新建一个BookController类,这里"/books"是命名规范,一般为模块名+s

@RestController
@RequestMapping("/books")
public class BookController {
    //注入业务层
    @Autowired
    private IBookService iBookService;

    @GetMapping//请求方式,get请求
    public List<Book> getAll(){
        return iBookService.list();
    }
}

现在就可以运行这个springboot项目了,运行后可以发现

 json格式的数据已经被发送到了页面上

使用postman测试请求:

现在我们用postman进行调试,postman是一款软件,可以到官网下载Download Postman | Get Started for Free

完成注册后输入http://localhost/books然后点击send就可以看到发送过来的数据了

 接下来可以再来写一个post请求(注意看注释)

    @PostMapping
    public Boolean save(@RequestBody Book book){//请求体参数为book,接收json数据
        return iBookService.save(book);
    }
    @PutMapping//这里是put方式提交 属于Restful开发方式
    public Boolean update(@RequestBody Book book){
        return iBookService.updateById(book);
    }
    //路径:http://localhost/books/1
    @DeleteMapping("{id}")//这里是delete方式提交 属于Restful开发方式
    public Boolean delete(@PathVariable Integer id){//从路径中获取id
        return iBookService.removeById(id);
    }
    @GetMapping("{id}")
    public Book getById(@PathVariable Integer id){
        return iBookService.getById(id);
    }

再来重启一下,再postman中测试

 在测试post请求时选择Body-raw-JSON就可以在下面的文本框中来写JSON数据了

点击send后,可以看到响应中给出了true,说明请求成功

数据库中也成功添加了测试数据,是不是很好用呢

接下来就可以利用postman,对剩下的请求进行测试了,postman还是很容易上手的。

但是这个地方有一个弊端,就是有时候返回的时json数组,有时候返回一个json数据,有时候返回一个true,这不利于前端的开发,那我们怎么解决这个问题呢?

表现层数据一致性处理

现在我们返回给前端的数据的格式是很乱的 

我们可以将这些数据都放到统一的data里面,这时问题又来了,那如果我查到了一个null,那么此时到底是没有找到数据呢,还是过程中抛异常了呢,这个是没有办法确定的

 我们在数据里面再增添一条flag就可以判断,到底是没查到还是过程中抛异常了

 前后端数据协议

设计表现层的返回结果模型类,用于统一数据。在controller包下新建一个utils包,里面创建一个R类,类里面写两个属性flag和data,并加上@Data注解,就不用写get set等方法了,最后写一个flag的构造放法

@Data
public class R {
    private Boolean flag;
    public Object data;
    public R(){

    }
    public R(boolean flag){
        this.flag=flag;
    }
    public R(boolean flag,Object data){
        this.flag=flag;
        this.data=data;
    }
}

接下来对BookController进行修改,把所有的返回值类型改为R

@RestController//加载为bean
@RequestMapping("/books")
public class BookController {
    //注入业务层
    @Autowired
    private IBookService iBookService;

    @GetMapping//请求方式,get请求
    public R getAll(){
        return new R(true,iBookService.list());
    }
    @PostMapping
    public R save(@RequestBody Book book){//请求体参数为book,接收json数据
        return new R(iBookService.save(book));
    }
    @PutMapping//这里是put方式提交 属于Restful开发方式
    public R update(@RequestBody Book book){
        return new R(iBookService.updateById(book));
    }
    //路径:http://localhost/books/1
    @DeleteMapping("{id}")//这里是delete方式提交 属于Restful开发方式
    public R delete(@PathVariable Integer id){//从路径中获取id
        return new R(iBookService.removeById(id));
    }
    @GetMapping("{id}")
    public R getById(@PathVariable Integer id){
        return new R(true,iBookService.getById(id));
    }
    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize){
        return new R(true,iBookService.getPage(currentPage,pageSize));
    }
}

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

在springboot项目中,前端的相关文件是放在resources目录下的static目录中的,里面放了这些东西,前端页面的编写在这里就不过多赘述。

显示全部数据 

我们先在钩子函数中(Vue初始化完成后自动执行)来调用getAll()函数,拿到全部的数据

 //钩子函数,VUE对象初始化完成后自动执行
        created() {
            //初始化完成后 自动加载所有数据
            this.getAll();
        },

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

axios用来发送请求.get代表发送的是get请求,然后通过then(res=>{})拿到后端发送过来的数据,在花括号内就可以对这个拿到的数据进行操作了。这里在控制台输出res.data,就可以看到拿过来的数据显示在了浏览器的控制台上。在前端开发时,其实每次在拿到数据后最好在控制台上输出一下,这样就能看到这个数据的结构,然后通过 . 来获取不同层级的数据

 

可以发现,flag和data都被拿了过来,我们是不是通过赋值,让Vue data中的dataList=res.data.data就可以将数据填到表格上了呢,我们来试一下。 

重新运行一下:

 

填入数据成功。 

增添数据

首先,找到新建的标签,发现按钮中绑定了一个单击事件,也就是说,当点击按钮时,系统就会去执行按钮绑定的事件,也就是函数通过单击新建按钮,要让新建的窗口弹出来

 

我们只需要将这个值改为true,窗口就显现出来了,观察窗口的标签结构,确定按钮绑定了handleAdd的单击事件,因此我们只需要在handleAdd的方法中,通过请求将模型数据发送到服务器,就可以完成对数据的新建.

post指定了发送的路径和要发送的数据,然后在回调函数中,先判断操作是否成功,若成功,将窗口关掉,并给出用户提示,添加成功。如果没有成功呢,提示用户添加失败,而且数据也是要重新加载一下的,因此这里我们可以写一个finally,重新加载数据 。

 

数据新建成功 

这里还有一个小细节,每次打开窗口的时候,为了避免显示上一次操作留下的数据,在打开窗口的时候要做一下表单的重置。

再将取消的功能完善一下 

 这样我们的增添数据的功能就算是完成了,其他的功能也几乎是一个思路,后面只写每个思路不同的地方,其他相同的思路这里不再赘述。

删除数据

首先,在表单中,将本行的数据封装成了一个scope,然后将scope.row作为参数传到函数中,scope.row其实就是获取到了本行的数据

 为了防止用户误操作,在点击删除时先弹出一个对话框,点击确认后执行then,点击取消时执行catch。还有一点注意的就是,这里采用delete的方式进行提交,因为用到的是Restful进行表现层接口开发,我们就要用delete的方式进行提交。而+则是用来拼接字符串的,用row.id就可以拿到当前行的id值。

错误分析 

在我们的系统中,为什么会存在删除失败的情况呢?如果我们的页面是有多人在使用,另一个人在操作时,在其他页面已经将这个数据删除掉了,但我们这里并没有刷新,在我们点击到删除后,数据库中已经不存在这个数据了所以才会删除失败。在修改时也会遇到这个问题,因此,后面程序的编写中也要注意到这些问题。

 修改数据

修改数据的基本思路就是,点击按钮时先发送get请求,回显数据,供用户修改,然后再将修改后的数据提交给服务器,便可完成修改的操作。

回显数据

 异常处理

当后台存在异常时,前端就会返回下面格式的数据,那么我们应该怎么去处理呢

springMVC异常处理器

在utils包下新建一个类

//作为springmvc的异常处理器
//@ControllerAdvice//将其定义为异常处理器
@RestControllerAdvice//二者皆可
public class ProjectExceptionAdvice {
    //拦截所有的异常信息
    @ExceptionHandler
    public R doException(Exception exception){
        //记录日志
        //通知运维
        //通知开发

        //在控制台上输出
        exception.printStackTrace();
        return new R("服务器故障,稍后再试");
    }
}

 当有异常出现后,就会被抛到这个类中,在这里,我们希望通过R类,提供出一句话来进行提示,那么我们就要在R类中进行修改

@Data
public class R {
    private Boolean flag;
    private Object data;
    private String msg;

    public R(){

    }
    public R(boolean flag){
        this.flag=flag;
    }
    public R(boolean flag,Object data){
        this.flag=flag;
        this.data=data;
    }
    public R(String msg){
        this.flag=false;
        this.msg=msg;
    }
}

新定义一个msg用来传递字符串,在构造方法中,写一句this.flag=false。因为既然是抛出了异常才会调用这个构造方法,那么flag一定为false

我们来人为的制造一个异常,然后在postman中进行测试

 

 这次返回的数据就变了。

那么在我们的前端代码中,就也要进行修改

 改成这样即可

分页查询

我们直接将查询所有的函数,改造为分页查询

先将路径改好,先不做赋值,重启,我们来观察一下路径和数据在不在

 

 接下来我们就根据返回的数据来对页面中的变量进行赋值,通过参照page对象中提供的方法,就可以得知 . 后应该写哪个变量名,我们将这些变量一一赋值给vue中的变量,就可以完成基本的分页查询了

但这时currentPage和pageSize是定死的,我们现在不能切换页面。找到handleCurrentChange的函数,这个函数的参数就是选中的页码值,只要让参数等于当前页码值,就可以完成换页的操作

 Bug

 这时的分页查询还是有一个bug的,就是如果你删除完一页数据时,这一页还是存在的,不会减少一页。即使这一页没有任何数据,那么这个问题应该怎么解决呢?

 出现bug的原因是什么呢?

从响应的数据中可以看出,我们此时还是查询的第三页的数据,但是此时的第三页是没有数据的,我们只需要在表现层添加一个判断即可 

    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize){
        IPage<Book> page=iBookService.getPage(currentPage,pageSize);
        //如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        if (currentPage>page.getPages()){
            page=iBookService.getPage((int)page.getPages(),pageSize);
        }

        return new R(true,page);
    }

这样这个小bug就解决了。

条件查询

条件查询时,条件是跟着分页查询走的,因此我们直接在pagination中,添加条件的数据即可

 然后将数据模型绑定到组件上

接下来在getAll的函数中来写条件查询

首先就是要组织参数,并且完成url地址的拼接,这里的url拼接的方式很巧妙,是利用+=来进行每个参数段的拼接

 

接下来在get请求的路径中拼接上param,就可以完成参数的携带

我们来做一下测试,在条件查询的输入框中输入几个值,然后点击查询,看一下发送的请求中是否携带了参数 

 

 观察路径,这里是没有问题的。接下来就要对BookController类中的分页查询方法进行修改

@GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize,Book book){
        //这里传过来的参数会被自动装配到book中
        System.out.println("参数===>"+book);

        IPage<Book> page=iBookService.getPage(currentPage,pageSize);
        //如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        if (currentPage>page.getPages()){
            page=iBookService.getPage((int)page.getPages(),pageSize);
        }

        return new R(true,page);
    }

 这里传过来的参数会被自动装配到参数中的book对象中,输出在控制台上看一下

参数确实被封装到了book对象中。

接下来就要把book对象作为参数传递到业务层来完成条件查询的操作。

    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize,Book book){
        //这里传过来的参数会被自动装配到book中
        //System.out.println("参数===>"+book);

        IPage<Book> page=iBookService.getPage(currentPage,pageSize,book);
        //如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        if (currentPage>page.getPages()){
            page=iBookService.getPage((int)page.getPages(),pageSize,book);
        }

        return new R(true,page);
    }

 然后我们要回到IBookService接口中,写一个带book参数的方法重载

public interface IBookService extends IService<Book> {
    IPage<Book> getPage(int currentPage, int pageSize);
    IPage<Book> getPage(int currentPage, int pageSize,Book book);
}

再将实现类编写一下

   @Override
    public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
        LambdaQueryWrapper<Book> lambdaQueryWrapper=new LambdaQueryWrapper<Book>();
        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;
    }

这里是通过lambaQueryWrapper来写条件查询,第一个参数为布尔类型,为true是连接条件,为false时则不连接条件。这里是判断字符串是否为空,来决定是否连接条件,第二个参数是字段名,而第三个参数则是条件值。

运行成功!

结尾 

这样一来项目的工作就基本完成了

这是目录中用到的文件。跟Javaweb相比,springboot为我们大大减轻了开发时的工作量,方便了我们的开发,非常非常感谢springboot的开发团队!

猜你喜欢

转载自blog.csdn.net/qq_63708623/article/details/124281079