SpringBoot

70 阅读5分钟

SpringBoot整合MP

  1. 勾选SpringMVC和MySql坐标
  2. 修改配置文件为yml格式
  3. 设置端口为80方便访问
  4. 导入相应的坐标(druid、MP)

回顾MP

@SpringBootTest
class SpringBootMpItheimaApplicationTests {
   @Autowired
   private BookMapper bookMapper;

   // 查询
   @Test
   void testGetById() {
      System.out.println(bookMapper.selectById(1));
   }

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

   // 更新
   @Test
   void testUpdate(){
      Book book = new Book();
      book.setId(51);
      book.setName("测试数据abc");
      book.setType("测试数据abc");
      book.setDescription("测试数据abc");
      bookMapper.updateById(book);
   }

   // 删除
   @Test
   void testDelete(){
      bookMapper.deleteById(51);
   }

   // 查询全部
   @Test
   void testSelectAll(){
      List<Book> books = bookMapper.selectList(null);
      books.forEach(System.out::println);
   }
}

开启MP调试日志

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

开启分页插件:首先依赖拦截器

@Configuration
public class MPConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}
// 分页
@Test
void testPage(){
   // 第1页4条数据
   IPage page = new Page(1,4);

   // 他的返回值就是一个page
   bookMapper.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(page.getRecords());		//详细数据
}

回顾按条件查询

// 查询条件
@Test
void testQueryWrapper(){
   QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
   queryWrapper.like("name","Spring");
   bookMapper.selectList(queryWrapper);
}

Lambda查询

// lambda查询:防止写错
@Test
void testQueryWrapper2(){
   LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
   lqw.like(Book::getName,"Spring");
   bookMapper.selectList(lqw);
}

但是我们根据查询的参数一般也是动态的,也就是需要外界来传递,但是如果没有传递,也就是null,那么就会存在问题,因此我们需要在进行查询前进行判断:

image.png

// lambda查询:防止写错
@Test
void testQueryWrapper2(){
   String name = null;
   LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();

   lqw.like(name!=null,Book::getName,name);
   bookMapper.selectList(lqw);
}

业务层操作

继承IService简化开发,是MP为我们提供的。

BookService

public interface BookService extends IService<Book> {
    // 自定义
    boolean saveBook(Book book);
}

BookServiceImpl

@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book>
    implements BookService{

    @Override
    public boolean saveBook(Book book) {
        return false;
    }
}

测试:这里使用了IService提供的默认方法

@SpringBootTest
public class BookServiceTest {
    @Autowired
    private BookService bookService;

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

表现层

表现层也就是Controller,表现层调用服务层Service,服务层和数据打交道。

IPage<Book> getPage(int currentPage,int pageSize);
@Autowired
BookMapper bookMapper;

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

前端和后端接收到的数据格式不同,所以要用@RequestBody来修改,@PathVariable用来从路径中获取变量

@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private BookService 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.removeById(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);
    }
}

表现层消息一致性

image.png

将数据再多存储到一个data中:

image.png

但是如果data中数据是空,就需要额外处理:多写一层flag,如果为true,就不需要抛异常,就是空数据,如果为false,就抛异常

image.png

image.png

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

    public R(){}
    public R(Boolean flag){
        this.flag = flag;
    }

    public R(Boolean flag,Object data){
        this.flag = flag;
        this.data = data;
    }
}
@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private BookService 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.removeById(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));
    }
}

初始项目:

从分层角度来解释,BaseMapper是DAO层的CRUD封装,而IService是业务业务逻辑层的CRUD封装,所以多了批量增、删、改的操作封装,这也比较符合官方指南中的阐述;

  • Mapper首先继承BaseMapper,可以使用一些DAO层基本的增删改查;

  • 服务层继承IService中的方法,在Mapper的基础上,使用Mapper中提供的方法同时可以使用服务层(逻辑层)的增删改查;

  • 控制层在服务层的基础上,可以使用对应的方法并实现自己的逻辑代码。他主要用来实现路径的不同请求操作;这里统一封装返回结果类型也是在控制层实现的。

    • 而前端向对应的路径发送请求,并得到结果。
getAll() {
    // 发生异步请求
    axios.get("/books").then(res=>{
        // response中本身就返回data,而data中又存在我们定义的data
        this.dataList = res.data.data;
    })
},

新增:

//弹出添加窗口
handleCreate() {
    this.dialogFormVisible = true;
    this.resetForm();
},

//重置表单
resetForm() {
    this.formData = {};
},

//添加
handleAdd () {
    axios.post("/books",this.formData).then(res=>{
        // 前提是判断当前操作是否成功
        if (res.data.flag==true){
            // 1.关闭弹窗:
            this.dialogFormVisible = false;
            this.$message.success("添加成功!");
        } else {
            this.$message.error("添加失败!");
        }
        // 2.重新加载数据
        this.getAll();
    })
},


//取消
cancel(){
    this.dialogFormVisible = false;
    this.$message.error("当前操作取消!");
},

删除:

// 删除
handleDelete(row) {
    this.$confirm("此操作永久删除信息,是否继续?","提示",{type:"info"}).then(()=>{
        axios.delete("/books/"+row.id).then((res)=>{
            if (res.data.flag){
                this.$message.success("删除成功");
            }else{
                
                this.$message.error("数据同步失败,自动刷新!");
            }
        }).finally(()=>{
            // 重新加载数据
            this.getAll();
        });
    }).catch(()=>{
        this.$message.info("取消操作");
    })
},

为什么这里删除是:数据同步失败?

  • 因为可能有多个人同时对一条数据进行了删除,在数据库中这个数据可能被前1s的人删掉了,但是在别人那里并不会立马显示被删除的画面,而后来的人再次点击删除,实际上数据已经不存在了,因此再次点击空数据删除时,弹出数据同步失败更为客观!!

修改:

//弹出编辑窗口
handleUpdate(row) {
    axios.get("/books").then(res=>{
        this.dataList = res.data.data
    });

    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();
    })
},


//修改
handleEdit() {
    axios.put("/books",this.formData).then(res=>{
        if (res.data.flag){
            this.dialogFormVisible4Edit = false;
            this.$message.success("修改成功");
            
        }else{
            this.$message.error("修改失败");
            this.cancel();
        }
    }).finally(()=>{
        this.getAll();
    })
},

异常消息处理:

image.png

异常处理器全部规定写在controller层:所有的异常都会在这里被拦截:

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

image.png

分页查询:

将之前写的getAll()重写:

//分页查询
getAll(){
    // pagination是elementUi提供的,传递给后端,后端根据页码和页码数量展示
    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;

        // 加载数据:由于请求的是分页,而分页中的每一条详细信息保存在records中:
        // console.log(res.data.data);
        this.dataList = res.data.data.records;
    })
},

//切换页码
handleCurrentChange(currentPage) {
    // 修改页码值为当前选中的页码值
    this.pagination.currentPage = currentPage

    // 执行查询
    this.getAll();
},

但是如果我某一页中只存在一条数据, 把这条数据删除后,页码会变成前一页的,但是数据是空的,这是一个bug:如果当前页码比总页码还大,那就切换成总页码数;重写控制层:

@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize){
    // return new R(true,bookService.getPage(currentPage,pageSize));
    IPage<Book> page = bookService.getPage(currentPage,pageSize);

    // 如果当前页码值大于了总页码值,那么就重新执行查询操作,使用最大页码值作为当前页码值
    if(currentPage>page.getPages()){
        page = bookService.getPage((int)page.getPages(),pageSize);
    }
    
    return new R(true,page);
}

条件查询

先对条件查询的3个框内数据,进行双向绑定,由于条件查询依然是查询操作,所以用的仍然是getAll()方法:根据书写的数据,作为参数,传递给对应的分页方法

getAll(){
    // 组织参数,拼接url请求地址(查询)
    param = "?type="+this.pagination.type;
    param += "&name="+this.pagination.name;
    param += "&description="+this.pagination.description;

    // pagination是elementUi提供的,传递给后端,后端根据页码和页码数量展示
    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;

        // 加载数据:由于请求的是分页,而分页中的每一条详细信息保存在records中:
        // console.log(res.data.data);
        this.dataList = res.data.data.records;
    })
},

对多一个参数的分页方法进行重载,查询。这里使用数据模型类进行接收参数,参数名和对象的属性名一致,SpringMVC会自动进行映射。

@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);
    bookMapper.selectPage(page,lqw);
    return page;
}

image.png