基于SpringBoot的SSMP(Spring+SpringMVC+MybatisPlus)整合案例

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

SSMP制作流程解析

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

模块创建

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

实体类开发

● Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发

<dependency>
			<groupId>org.projectlombokgroupId>
			<artifactId>lombokartifactId>
		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方法等

数据层开发

● 技术实现方案

   ◆ MybatisPlus
   
   ◆ Durid

● 导入MybatisPlus与Druid对应的starter

<dependency>
			<groupId>com.baomidougroupId>
			<artifactId>mybatis-plus-boot-starterartifactId>
			<version>3.4.3version>
		dependency>
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>druid-spring-boot-starterartifactId>
			<version>1.2.6version>
		dependency>

● 配置数据源与MybatisPlus的基础配置(id生成策略使用数据库自增策略)

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssm_db?servierTimezone=UTC
      username: root
      password: root

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;
    @Test
    public void testGetById(){
        System.out.println(bookDao.selectById(1));
    }

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

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

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

● 为方便调试可以开启MyBatisPlus的日志
● 使用配置方式开启日志,设置日志输出方式为标准输出

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

分页功能

● 分页操作需要设定分页对象IPage
● IPage对象中封装了分页操作中的所有数据

   ◆ 数据  
   
   ◆ 当前页码值  
   
   ◆ 每页数据总量 
   
   ◆ 最大页码值  
   
   ◆ 数据总量  

● 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现

@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1.定义Mp拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //2.添加具体的拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

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

条件查询功能

● 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用

@Test
void testGetByCondition(){
    IPage page = new Page(1,10);
    LambdaQueryWrapper<Book> lw = new LambdaQueryWrapper<>();
    lw.like(Book::getName,"Spring");
    bookDao.selectPage(page,lw);
 }
       
@Test
void testGetByCondition3(){
    QueryWrapper<Book> lw = new QueryWrapper<>();
    lw.like("name","Spring");
    bookDao.selectList(lw);
}

● 支持动态拼写查询条件


@Test
void testGetByCondition3(){
    QueryWrapper<Book> lw = new QueryWrapper<>();
    lw.like("name","Spring");
    bookDao.selectList(lw);
  }

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

业务层开发

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

接口定义

public interface BookService {
    boolean save(Book book);
    boolean delete(Integer id);
    boolean update(Book book);
    Book getById(Integer id);
    List<Book> getAll();
    IPage<Book>getByPage(int currentPage,int pageSize);
}

实现类定义

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    @Override
    public boolean save(Book book) {
       return bookDao.insert(book)>0;
    }

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

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

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

    @Override
    public List<Book> getAll() {
        return bookDao.selectList(null);
    }

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

测试类定义

@SpringBootTest
public class BookServiceTest {
    @Autowired
    private BookService bookService;
    @Test
    void testGetById(){
        bookService.getById(9);
    }
    @Test
    void testGetAll(){
        bookService.getAll();
    }@Test
    void testGetByPage(){
        bookService.getByPage(1,5);
    } 
    
 ......  
 
}

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

业务层开发—快速开发

● 快速开发方案
◆ 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl
◆ 在通用类基础上做功能重载或功能追加
◆ 注意重载时不要覆盖原始操作,避免原始提供的功能丢失

基于SpringBoot的SSMP(Spring+SpringMVC+MybatisPlus)整合案例_第1张图片
● 接口定义

  public interface IBookService extends IService<Book> {
    boolean saveBook(Book book);
    boolean modify(Book book);
    boolean delete(Integer id);
}

● 实现类定义

  @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 modify(Book book) {
        return bookDao.updateById(book) >0;
    }

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

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

表现层开发

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

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

表现层消息一致性处理

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

@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 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 int currentPage,@PathVariable int pageSize){
        return new R( true,bookService.getPage(currentPage,pageSize));

    }
}

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

● 前端发送异步请求,调用后端接口

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

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

● 列表页

  //列表
    getAll() {
       axios.get("/books").then((res)=>{
           //console.log(res.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){
                //关闭弹层
                this.dialogFormVisible = false;
                this.$message.success("添加成功");
            }else{
                this.$message.error("添加失败")
            }
        }).finally(()=>{
            //重新加载数据
            this.getAll();
      });
},

● 取消

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

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

// 删除
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("取消删除操作");
  });
},

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) {
    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("取消删除操作");
    });
},

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

● 修改

//修改
handleEdit() {
    axios.put("/books",this.formData).then((res)=>{
        //如果操作成功,关闭弹层并刷新页面
        if(res.data.flag){
            this.dialogFormVisible4Edit = false;
            this.$message.success("修改成功");
        }else {
            this.$message.error("修改失败,请重试");
        }
    }).finally(()=>{
        this.getAll();
    });
},

● 取消添加和修改

cancel(){
  this.dialogFormVisible = false;
  this.dialogFormVisible4Edit = false;
  this.$message.info("操作取消");
},

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

业务消息一致性处理

● 对异常进行统一处理,出现异常后,返回指定信息

//作为springmvc的异常处理器
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
    //拦截所有的异常信息
    @ExceptionHandler(Exception.class)
    public R doException(Exception ex){
        ex.printStackTrace();
        return new R("服务器故障,请稍后再试");
    }
}

● 修改表现层返回结果的模型类,封装出现异常后对应的信息
◆ flag:false
◆ Data: null
◆ 消息(msg): 要显示信息

@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(boolean flag, String msg) {
        this.flag = flag;
        this.msg = msg;
    }
    public R(String msg) {
        this.flag = false;
        this.msg = msg;
    }
}

● 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息

//添加
handleAdd () {
    axios.post("/books",this.formData).then((res)=>{
        if (res.data.flag){
            //关闭弹层
            this.dialogFormVisible = false;
            this.$message.success(res.data.msg);
        }else{
            this.$message.error(res.data.msg);
        }
    }).finally(()=>{
        //重新加载数据
        this.getAll();
    });
},

● 可以在表现层Controller中进行消息统一处理
● 目的:国际化

@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 ? "添加成功^_^" : "添加失败-_-!");
}

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)=>{
          //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;
      });
  },

● 删除功能维护
◆ 基于业务需求维护删除功能

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

● 分页页码值切换

//切换页码
handleCurrentChange(currentPage) {
    //修改页码值为当前选中的页码值
    this.pagination.currentPage=currentPage;
    //执行查询
    this.getAll();
},

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

条件查询功能

● 查询条件数据封装
◆ 单独封装
◆ 与分页操作混合封装

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() {
      //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接收参数

     @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,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);
    }

● 业务层接口功能开发

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

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

你可能感兴趣的:(SpringBoot,Mybatis,SpringMVC,springmvc,springboot,java,后端)