基于SpringBoot的SSMP(Spring+SpringMVC+Mybatis-Plus)整合实现CURD功能
- 项目结构
- 1 环境搭建
- 2 实体类开发------Lombok
- 3 Dao开发------MyBatisPlus + Druid
- 4 Service开发------基于MyBatisPlus进行增量开发
- 5 Controller开发------Restful开发 + 前后端开发协议
- 6 页面开发------基于VUE+ElementUI,前后端联调,页面数据处理,页面消息处理(列表、新增、修改、删除、分页、查询)
- 7 项目异常处理
- 8 按条件查询
项目结构
1 环境搭建
新建项目:
修改配置文件格式为yml
2 实体类开发------Lombok
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发,需要引入相关坐标,lombok版本由SpringBoot提供,无需指定版本;常用注解:@Data,为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等
在pom.xml中导入Lombok坐标:
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
创建book实体:
package com.lifeilin.domain;
import lombok.Data;
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
3 Dao开发------MyBatisPlus + Druid
创建项目时使用阿里云网站创建,勾选了MyBatisPlus,即项目中有了MyBatisPlus坐标,只需要pom.xml中导入Druid坐标:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
# 应用名称
spring.application.name: SpringBoot_06_ssmp
# 应用服务 WEB 访问端口
server.port: 8080
# 数据库驱动:
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name: defaultDataSource
# 数据库连接地址
spring.datasource.url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username: root
spring.datasource.password: LIFEILIN
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
继承BaseMapper并指定泛型:BaseMapper中已经定义好了基本的增删查改语句,直接调用对应的方法即可使用。
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
为方便调试可以开启MyBatisPlus的日志:
3.1 分页功能
分页操作需要设定分页对象IPage:
@Test
void testGetPage(){
IPage page = new Page(2,5);
bookDao.selectPage(page,null);
}
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
在项目下建立config包,该包下建立拦截器类实现拦截器:
package com.lifeilin.config;
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;
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
3.2 条件查询功能
@Test
void testGetBy(){
QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","Spring");
bookDao.selectList(queryWrapper);
}
@Test
void testGetBy2(){
String name = null;
LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(name!=null,Book::getName,name);
bookDao.selectList(lambdaQueryWrapper);
}
4 Service开发------基于MyBatisPlus进行增量开发
4.1 标准开发
定义接口:
public interface BookService {
Boolean save(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
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public Boolean save(Book book) {
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) {
Page page = new Page(currentPage, pageSize);
return bookDao.selectPage(page, null);
}
}
4.2 基于MyBatisPlus快速开发(避免重复代码)
快速开发方案
使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl<M,T>)
在通用类基础上做功能重载或功能追加
注意重载时不要覆盖原始操作,避免原始提供的功能丢失
定义接口:
package com.lifeilin.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lifeilin.domain.Book;
public interface IBookService extends IService<Book> {
//自增的分页查询
IPage<Book> getPage(int currentPage,int pageSize);
}
定义实现类:
package com.lifeilin.service.impl;
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.lifeilin.dao.BookDao;
import com.lifeilin.domain.Book;
import com.lifeilin.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao, Book> implements IBookService {
@Autowired
private BookDao bookDao;
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
Page page = new Page(currentPage, pageSize);
bookDao.selectPage(page,null);
return page;
}
}
5 Controller开发------Restful开发 + 前后端开发协议
表现层消息一致性处理:在controller包下新建utils包,新建一个类处理消息数据一致性
package com.lifeilin.controller.utils;
import lombok.Data;
@Data
public class Result {
private Boolean flag;
private Object data;
public Result() {
}
public Result(Boolean flag) {
this.flag = flag;
}
public Result(Boolean flag, Object data) {
this.flag = flag;
this.data = data;
}
}
controller层类返回上面创建类的类型以统一数据格式:
package com.lifeilin.controller;
import com.lifeilin.controller.utils.Result;
import com.lifeilin.domain.Book;
import com.lifeilin.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/books")
public class BookController2 {
@Autowired
private IBookService iBookService;
//查询所有
@GetMapping
public Result getAll() {
return new Result(true, iBookService.list());
}
//保存 json请求体数据
@PostMapping
public Result save(@RequestBody Book book) {
return new Result(iBookService.save(book));
}
//更新 json请求体数据
@PutMapping
public Result update(@RequestBody Book book) {
return new Result(iBookService.updateById(book));
}
//删除 使用路径参数
@DeleteMapping("{id}")
public Result delete(@PathVariable Integer id) {
return new Result(iBookService.removeById(id));
}
//查询单个 使用路径参数
@GetMapping("{id}")
public Result getById(@PathVariable Integer id) {
return new Result(true, iBookService.getById(id));
}
//分页查询 使用路径变量传参数
@GetMapping("{currentPage}/{pageSize}")
public Result getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return new Result(true, iBookService.getPage(currentPage, pageSize));
}
}
6 页面开发------基于VUE+ElementUI,前后端联调,页面数据处理,页面消息处理(列表、新增、修改、删除、分页、查询)
初始页面:
前端核心业务代码:
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: 10,//每页显示的记录数
total: 0//总记录数
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
//调用查询全部数据的操作
this.getAll();
},
methods: {
//列表
getAll() {
//发送异步请求
axios.get("/books").then((res) => {
this.dataList = res.data.data;
})
},
//弹出添加窗口
handleCreate() {
//dialogFormVisible: false,//添加表单是否可见
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(res.data.msg);//从后台读提示信息
}
}).finally(() => {
// 重新加载列表数据
this.getAll();
});
},
//取消
cancel() {
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("当前操作取消");
},
// 删除
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("取消操作");
});
},
//弹出编辑窗口
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();
});
},
//修改
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();
});
}
}
})
6.1 分页查询
这里为了避免一个bug:即总共三页时,第三页只有一条数据,删除这一条数据页面仍然停留在第三页的情况,在controller层进行查询判断控制:
//分页查询 使用路径变量传参数
@GetMapping("{currentPage}/{pageSize}")
public Result 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 Result(true, page);
}
//分页查询
getAll() {
//发送异步请求
axios.get("/books/" + this.pagination.currentPage + "/" +
this.pagination.pageSize
).then((res) => {
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;
})
},
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前页码值
this.pagination.currentPage = currentPage;
//执行查询
this.getAll();
},
7 项目异常处理
业务操作成功或失败返回数据格式是正常前端想要得到的,但后台代码BUG导致数据格式不统一性,这就需要对异常进行统一处理,出现异常后,返回指定信息,使用异常处理器类处理:
修改表现层返回结果的模型类,封装出现异常后对应的信息
flag:false
Data: null
消息(msg): 要显示信息
package com.lifeilin.controller.utils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//springmvc的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有异常信息
@ExceptionHandler
public Result doException(Exception ex) {
ex.printStackTrace();
return new Result(false, "服务器故障,请稍后再试");
}
}
8 按条件查询
Service层增加接口(修改分页查询接口,在分页查询接口上增加参数):
Service层实现类:
controller层: