一、实现方案
案例实现方案分析:
- 实体类开发 —— 使用Lombok快速制作实体类
- Dao开发 —— 整合MyBatisPlus,制作数据层测试类
- Service开发 —— 整合MyBatisPlus进行增量开发,制作业务层测试类
- Controller开发 —— 基于Restful开发,使用PostMan测试接口功能
- Controller开发 —— 前后端开发协议制作
- 页面开发 —— 基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
- 列表、新增、修改、删除、分页、查询
- 项目异常处理
- 按条件查询 —— 页面功能调整、Controller修正功能、Service修正功能
二、案例实现
1.快速搭建springboot工程
1.快速搭建
:
2.勾选需要的起步依赖
:
2. 实体类快速开发(Lombok)
Lombok
:一个Java类库,提供了一组注解,简化了POJO实体类开发
pom.xml导入lombok依赖
:
- lombok版本由SpringBoot提供,故无需额外指定
<!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
快速利用lombok开发实体类
:
@Data注解
:自动创建实体类属性的Getter、Setter方法
import lombok.Data; /** * @author .29. * @create 2023-03-27 20:58 */ @Data public class Book { private int id; private String type; private String name; private String description; }
3.数据层开发
手动导入springboot未提供勾选的起步依赖
:
<!--手动添加MyBatisPlus的起步依赖--> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!--手动添加Druid数据库连接池的起步依赖--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> </dependencies>
springboot配置文件
:
将配置文件修改为yml
格式后,配置相关数据源信息
# 配置端口号 server: port: 8080 # 配置dataSource(数据源) spring: datasource: druid: username: root password: abc123 url: jdbc:mysql://localhost:3306/springboot?useSSL=false&useUnicode=true&CharacterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver # 配置MyBatisPlus # table-prefix:配置表格前缀,避免无法找到表格的问题 # id-type: 配置id类型,设置为auto以配合数据库表格的自增列,若不设置,在新增时不指定自增列会报错 mybatis-plus: global-config: db-config: table-prefix: tbl_ id-type: auto # 开启MyBatisPlus日志功能(注意:配置在mybatis-plus:的下一级) configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Mapper接口
import com.haojin.springboot.springbootssmp.domain.Book; import org.apache.ibatis.annotations.Mapper; /** * @author .29. * @create 2023-03-27 21:11 */ @Mapper public interface BookMapper extends BaseMapper<Book> { //只需要让Mapper接口继承BaseMapper<>,以实体类类型作为泛型,就能直接使用MyBatisPlus提供好的CRUD方法 }
MyBatisPlus提供的相关方法参考:
MyBatisPlus——分页功能
:
- 分页操作需要设置分页对象
IPage
IPage
对象中封装了分页操作的所有数据:
- 数据(Records)
- 当前页码值(Current)
- 每页数据总量(Size)
- 最大页码值(Pages)
- 数据总量(Total)
@Test public void testSelectPage(){ //实现分页功能的测试 IPage page = new Page(1, 5); bookMapper.selectPage(page,null); //测试 获取IPage对象中封装的数据: System.out.println(page.getCurrent());//当前页码 System.out.println(page.getSize()); //每页数据总量 System.out.println(page.getPages()); //最大页码值 System.out.println(page.getTotal()); //数据总量 System.out.println(page.getRecords());//数据 }
只是上述调用MyBatisPlus中提供的分页功能相关的方法还无法真正实现分页功能,MyBatisPlu是通过拦截器来实现分页的,所以需要配置拦截器。
- 分页操作时在MyBatisPlus的常规操作基础上增强得到的,内部时动态地拼写SQL语句,因此需要增强对应地功能,使用MyBatisPlus拦截器实现:
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author .29. * @create 2023-03-27 22:36 */ //拦截器配置类 @Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //创建MP拦截器的容器 MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //往容器中添加需要的拦截器,这里是实现分页功能的拦截器 mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } }
配置了拦截器,上文测试的分页相关功能就能顺利实现啦~
MyBatisPlus——条件查询功能
:
- 可以使用
QueryWrapper
对象封装查询条件实现条件查询功能,这里推荐使用LambdaQueryWrapper
对象——所有查询操作封装成方法调用。
@Test public void testGetByCondition(){ //设置分页规则(第一页,每页五条) IPage page = new Page(1, 5); //将需要查询的类型作为泛型,LambdaQueryWrapper封装了所有查询操作,可直接调用相关方法 LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>(); //like方法封装条件进行查询 lqw.like(Book::getName,"Spring"); //查询结果分页 bookMapper.selectPage(page,lqw); }
4.业务层开发
注意
:
- Service接口名称定义为业务名称,与数据层接口名称进行区分
- 制作测试类测试Service功能是否有效
- 可使用通用接口
IService<T>
快速开发Service - 可使用通用实现类
ServiceImpl<M,T>
快速开发ServiceImpl - 可以在通用接口也基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
①普通方式开发
业务层接口
:
import com.haojin.springboot.springbootssmp.domain.Book; import java.util.List; /** * @author .29. * @create 2023-03-28 19:41 */ //业务层接口 public interface BookService { //增 Boolean save(Book book); //改 Boolean update(Book book); //删 Boolean delete(Integer id); //查 Book getById(Integer id); List<Book> getAll(); //分页 IPage getPage(int currPage,int pageSize); }
接口实现类
:
import com.haojin.springboot.springbootssmp.domain.Book; import com.haojin.springboot.springbootssmp.mapper.BookMapper; import com.haojin.springboot.springbootssmp.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author .29. * @create 2023-03-28 19:46 */ //业务层接口实现类 @Service public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; //业务层方法 与 数据层方法名 尽量不相同 //新增 @Override public Boolean save(Book book) { return bookMapper.insert(book) > 0; } //修改 @Override public Boolean update(Book book) { return bookMapper.updateById(book) > 0; } //删除 @Override public Boolean delete(Integer id) { return bookMapper.deleteById(id) > 0; } //根据id查询 @Override public Book getById(Integer id) { return bookMapper.selectById(id); } //查询所有 @Override public List<Book> getAll() { return bookMapper.selectList(null); } //分页 @Override public IPage getPage(int currPage, int pageSize) { IPage page = new Page(currPage, pageSize); bookMapper.selectPage(page,null); return page; } }
创建测试类,测试业务层功能是否能正常运行
:
import com.haojin.springboot.springbootssmp.domain.Book; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; /** * @author .29. * @create 2023-03-28 19:56 */ @SpringBootTest public class testServiceImpl { @Autowired private BookService bookService; @Test public void testSave(){ Book book = new Book(); book.setType("计算机理论"); book.setName("数据结构与算法"); book.setDescription("小白的算法入门推荐书籍"); System.out.println(bookService.save(book)); } @Test public void testUpdate(){ Book book = new Book(); book.setId(8); book.setType("测试更新 类型"); book.setName("测试更新 书名"); book.setDescription("测试更新 简介"); System.out.println(bookService.update(book)); } @Test public void testDelete(){ System.out.println(bookService.delete(8)); } @Test public void testGetById(){ System.out.println(bookService.getById(1)); } @Test public void testGetAll(){ System.out.println(bookService.getAll()); } @Test public void testGetPage(){ IPage page = bookService.getPage(1, 5); System.out.println(page.getCurrent());//当前页码 System.out.println(page.getSize()); //每页数据总量 System.out.println(page.getPages()); //最大页码值 System.out.println(page.getTotal()); //数据总量 System.out.println(page.getRecords());//数据 } }
②快速方式开发
基于MyBatisPlus快速构建业务层接口
:
- 这里一个接口,继承IService,操作类型作为泛型,就完成了上文繁琐的业务层实现操作
import com.baomidou.mybatisplus.extension.service.IService; import com.haojin.springboot.springbootssmp.domain.Book; /** * @author .29. * @create 2023-03-28 20:25 */ //业务层快速开发(基于MyBatisPlus构建) public interface IBookService extends IService<Book> { //已经提供了大量通用方法 //不通用的业务层方法继续在这里定义开发即可 //分页 IPage<Book> getPage(int currPage,int pageSize); //条件查询分页 IPage<Book> getPage(int currPage,int pageSize,Book book); }
MyBatisPlus提供的通用方法
:
基于MyBatisPlus快速构建接口实现类
:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.haojin.springboot.springbootssmp.domain.Book; import com.haojin.springboot.springbootssmp.mapper.BookMapper; import com.haojin.springboot.springbootssmp.service.IBookService; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author .29. * @create 2023-03-28 20:34 */ @Service //基于MyBatisPlus快速构建业务层接口实现类 public class MPBookServiceImpl extends ServiceImpl<BookMapper, Book> implements IBookService { @Autowired BookMapper bookMapper; //自己定义的 //分页 @Override public IPage<Book> getPage(int currPage, int pageSize) { IPage page = new Page(currPage, pageSize); bookMapper.selectPage(page,null); return page; } //条件查询分页 @Override public IPage<Book> getPage(int currPage, 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(currPage, pageSize); bookMapper.selectPage(page,lqw); return page; } }
5. 表现层开发
- 基于Restful进行表现层接口开发:
- 新增:POST
- 删除:DELETE
- 修改:PUT
- 查询:GET
- 接收参数:
- 实体数据:@RequestBody
- 路径变量:@PathVariable
表现层接口功能实现
:
import com.baomidou.mybatisplus.core.metadata.IPage; import com.haojin.springboot.springbootssmp.domain.Book; import com.haojin.springboot.springbootssmp.service.IBookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @author .29. * @create 2023-03-28 21:00 */ @RestController @RequestMapping(value = "/books") public class BookController { @Autowired private IBookService bookService; //Get方式,表示查询 @GetMapping public List<Book> getAll(){ return bookService.list(); } //Post方式,表示新增 @PostMapping public Boolean save(@RequestBody Book book){ return bookService.save(book); } //Put方式,表示修改 @PutMapping public Boolean update(@RequestBody Book book){ return bookService.updateById(book); } //Delete方式,表示删除 @DeleteMapping("/{id}") public Boolean delete(@PathVariable Integer id){ return bookService.removeById(id); } //Get方式,"/{}"表示传入的参数,根据参数查询 @GetMapping("/{id}") public Book getById(@PathVariable Integer id){ return bookService.getById(id); } //分页 @GetMapping("/{currPage}/{pageSize}") public IPage<Book> getPage(@PathVariable int currPage,@PathVariable int pageSize){ return bookService.getPage(currPage,pageSize); } }
表现层消息一致性处理
:
- 现在情况(格式不统一,前端交互不友好):
- 增删改操作:返回true / false
- 根据参数查询:返回 JSON 格式 / null (空数据 或 出现异常)
- 查询所有数据:返回 JSON数组 格式 / null (空数据 或 出现异常)
解决方案
:
- 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也可以称为
前后端数据协议
模型类:
import lombok.Data; /** * @author .29. * @create 2023-03-28 22:11 * * 控制层返回结果的模型类,用于后端与前端进行数据格式统一,也可以称为`前后端数据协议` */ //使用lombok快速开发 @Data public class Result { private boolean flag; private Object data; private String msg; public Result(boolean flag){ this.flag = flag; } public Result(boolean flag, Object data) { this.flag = flag; this.data = data; } public Result() { } public Result(String msg){ this.flag = false; this.msg = msg; } public Result(boolean flag,String msg){ this.flag = flag; this.msg = msg; } }
定义SpringMVC异常处理类,让异常信息也以统一的格式获得:
- 使用注解@RestControllerAdvice定义SpringMVC异常处理器来处理异常
- 处理器必须被扫描加载,否则无法生效
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @author .29. * @create 2023-03-29 19:14 */ //作为SpringMVC的异常处理器 @RestControllerAdvice public class ProjectExceptionAdvice { //拦截异常信息 @ExceptionHandler public Result doException(Exception ex){ //记录日志 //通知运维 //通知开发 //... //打印异常内容 ex.printStackTrace(); //返回消息 return new Result("服务器故障,请稍后再试!"); } }
消息一致性处理后的表现层接口:
import com.baomidou.mybatisplus.core.metadata.IPage; import com.haojin.springboot.springbootssmp.domain.Book; import com.haojin.springboot.springbootssmp.service.IBookService; import com.haojin.springboot.springbootssmp.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @author .29. * @create 2023-03-28 21:00 */ @RestController @RequestMapping(value = "/books") public class BookController { @Autowired private IBookService bookService; //Get方式,表示查询 @GetMapping public Result getAll(){ return new Result(true, bookService.list()); } //Post方式,表示新增 @PostMapping public Result save(@RequestBody Book book){ boolean flag = bookService.save(book); return new Result(flag,flag?"添加成功":"添加失败"); } //Put方式,表示修改 @PutMapping public Result update(@RequestBody Book book){ return new Result(bookService.updateById(book)); } //Delete方式,表示删除 @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id){ return new Result(bookService.removeById(id)); } //Get方式,"/{}"表示传入的参数,根据参数查询 @GetMapping("/{id}") public Result getById(@PathVariable Integer id){ return new Result(true,bookService.getById(id)) ; } //分页 @GetMapping("/{currPage}/{pageSize}") public Result getPage(@PathVariable int currPage,@PathVariable int pageSize){ IPage<Book> page = bookService.getPage(currPage, pageSize); //如果当前页码值大于总页码值,重写执行查询操作,使用最大页码值作为当前页码值 if(currPage > page.getPages()){ page = bookService.getPage((int)page.getPages(), pageSize); } return new Result(true,page); } }
小结
:
- 设计统一的返回值结果类型,便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也叫 前后端数据协议
6.前端页面
前后端协议联调
:
- 前后端分离结构设计中,前端页面归属前端服务器
- 单体工程中,页面方式在resources目录下的static目录中(若出现问题,建议执行maven的clean命令)
books.html
<!DOCTYPE html> <html> <head> <!-- 页面meta --> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>基于SpringBoot整合SSM案例</title> <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport"> <!-- 引入样式 --> <link rel="stylesheet" href="../plugins/elementui/index.css"> <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="../css/style.css"> </head> <body class="hold-transition"> <div id="app"> <div class="content-header"> <h1>图书管理</h1> </div> <div class="app-container"> <div class="box"> <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> <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row> <el-table-column type="index" align="center" label="序号"></el-table-column> <el-table-column prop="type" label="图书类别" align="center"></el-table-column> <el-table-column prop="name" label="图书名称" align="center"></el-table-column> <el-table-column prop="description" label="描述" align="center"></el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <!--分页组件--> <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> <!-- 新增标签弹层 --> <div class="add-form"> <el-dialog title="新增图书" :visible.sync="dialogFormVisible"> <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="图书类别" prop="type"> <el-input v-model="formData.type"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="图书名称" prop="name"> <el-input v-model="formData.name"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="描述"> <el-input v-model="formData.description" type="textarea"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleAdd()">确定</el-button> </div> </el-dialog> </div> <!-- 编辑标签弹层 --> <div class="add-form"> <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit"> <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="图书类别" prop="type"> <el-input v-model="formData.type"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="图书名称" prop="name"> <el-input v-model="formData.name"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="描述"> <el-input v-model="formData.description" type="textarea"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleEdit()">确定</el-button> </div> </el-dialog> </div> </div> </div> </div> </body> <!-- 引入组件库 --> <script src="../js/vue.js"></script> <script src="../plugins/elementui/index.js"></script> <script type="text/javascript" src="../js/jquery.min.js"></script> <script src="../js/axios-0.18.0.js"></script> <script> var vue = new Vue({ el: '#app', data:{ dataList: [],//当前页要展示的列表数据 dialogFormVisible: false,//添加表单是否可见 dialogFormVisible4Edit:false,//编辑表单是否可见 formData: {},//表单数据 rules: {//校验规则 type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }], name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }] }, pagination: {//分页相关模型数据 currentPage: 1,//当前页码 pageSize:6,//每页显示的记录数 total:0,//总记录数 type: "", name: "", description: "" } }, //钩子函数,VUE对象初始化完成后自动执行 created() { //调用查询全部数据的操作 this.getAll(); }, methods: { //列表 // getAll() { // //发送异步请求 // axios.get("/books").then((res)=>{ // // console.log(res.data); // this.dataList = res.data.data; // }); // }, //分页查询 getAll() { //组织参数,拼接url请求地址 // console.log(this.pagination.type); 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; }); }, //切换页码 handleCurrentChange(currentPage) { //修改页码值为当前选中的页码值 this.pagination.currentPage = currentPage; //执行查询 this.getAll(); }, //弹出添加窗口 handleCreate() { this.dialogFormVisible = true; this.resetForm(); }, //重置表单 resetForm() { this.formData = {}; }, //添加 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(); }); }, //取消 cancel(){ this.dialogFormVisible = false; this.dialogFormVisible4Edit = false; this.$message.info("当前操作取消"); }, // 删除 handleDelete(row) { // console.log(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(()=>{ //2.重新加载数据 this.getAll(); }); }).catch(()=>{ this.$message.info("取消操作"); }); }, //弹出编辑窗口 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(()=>{ //2.重新加载数据 this.getAll(); }); }, //修改 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(); }); }, //条件查询 } }) </script> </html>
7. 源码免费下载
数据库表格结构
:
- 前端页面代码中引用的外部样式文件,文章中没有提供 。
需要完整代码的,可以在此下载
👇:
SpringBoot整合Spring+SpringMVC+MyBatisPlus案例:图书管理系统