SpringBoot整合MP
- 勾选SpringMVC和MySql坐标
- 修改配置文件为yml格式
- 设置端口为80方便访问
- 导入相应的坐标(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,那么就会存在问题,因此我们需要在进行查询前进行判断:
// 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);
}
}
表现层消息一致性
将数据再多存储到一个data中:
但是如果data中数据是空,就需要额外处理:多写一层flag,如果为true,就不需要抛异常,就是空数据,如果为false,就抛异常
@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();
})
},
异常消息处理:
异常处理器全部规定写在controller层:所有的异常都会在这里被拦截:
// 作为springmvc的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
// 拦截所有的异常信息
@ExceptionHandler
public R doException(Exception ex){
// 记录日志
// 通知运维
// 通知开发
ex.printStackTrace();
return new R(false,"服务器故障,请稍后再试");
}
}
分页查询:
将之前写的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;
}