黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战
使用RestController代替Controller+ResponseBody
在Dao的实现类上加@Repository,@Repository就是写在数据层上的。
@Service将servive的实现类定义为 业务层对应的bean
表现层Contrller中, 带参数的,用异步提交发送的话,参数通过请求体传json数据过来, 用 请求体参数@RequestBody;删除和传单个 使用的是路径变量来传参,@PathVariable
业务层接口关注的是 业务名称
数据层接口关注的是 与数据库相关的操作
spring程序缺点:
SpringBoot程序优点:
定义一系列坐标,属性和依赖管理
子工程继承了parent, parent又继承了dependencies, dependencies定义了几百了版本信息,以及对应的坐标引用信息。方便我们的配置
下面的依赖在使用
对应的start其实就是包含了若干个坐标定义的pom管理文件,一个start包含了若干个依赖管理信息。
项目中的pom.xml定义了使用SpringMVC技术,但是并没有写SpringMVC的坐标,而是添加了一个名字中包含starter的依赖
在spring-boot-starter-web中又定义了若干个具体依赖的坐标
总结
starter与parent的区别
朦朦胧胧中感觉starter与parent好像都是帮助我们简化配置的,但是功能又不一样,梳理一下。
starter是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的
parent是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由SpringBoot统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的
@SpringBootApplication
public class Springboot0101QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot0101QuickstartApplication.class, args);
}
}
就是启动了一个spring容器
bean就是由spirng管理的组件
SpringBoot本身是为了加速Spring程序的开发的,而Spring程序运行的基础是需要创建自己的Spring容器对象(IoC容器)并将所有的对象交给Spring的容器管理,也就是一个一个的Bean。那还了SpringBoot加速开发Spring程序,这个容器还在吗?这个疑问不用说,一定在。当前这个类运行后就会产生一个Spring容器对象,并且可以将这个对象保存起来,通过容器对象直接操作Bean。
SpringBoot程序启动还是创建了一个Spring容器对象。这个类在SpringBoot程序中是所有功能的入口,称这个类为引导类。
作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication
总结
将tomcat容器的执行过程抽取出来,变成一个对象,交给spring容器去管理
总结
REST:Representational State Transfer,表现层资源状态转移。
@RequestMapping("/save")
改为
@RequestMapping(value = "/users", method = RequestMethod.POST)
添加路径参数 @PathVariable
@RequestMapping( method = RequestMethod.POST)
改为
@PostMapping
@RequestMapping( value = "/{id}", method = RequestMethod.POST)
改为
@DeleteMapping("/{id}")
SpringBoot通过配置文件application.properties就可以修改默认的配置,通过键值对修改
修改服务器端口: 将8080端口,改为80默认端口
server.port=80
关闭运行日志图表(banner)
spring.main.banner-mode=off
设置运行日志的显示级别
debug -》error -》 info
logging.level.root=debug
三种配置文件格式
server.port=80
server:
port: 81
server:
port: 82
3个文件的加载优先顺序是什么
application.properties > application.yml > application.yaml
subject: #数组
- Java
- 前端
- 大数据
enterprise:
name: itcast
age: 16
subject:
- Java
- 前端
- 大数据
likes: [王者荣耀,刺激战场] #数组书写缩略格式
users: #对象数组格式一
- name: Tom
age: 4
- name: Jerry
age: 5
users: #对象数组格式二
-
name: Tom
age: 4
-
name: Jerry
age: 5
users2: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ] #对象数组缩略格式
数据前面要加空格与冒号隔开
yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用@Value可以读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
pringBoot提供了一个对象,能够把所有的数据都封装到这一个对象中,这个对象叫做Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中
总结
创建类用于封装下面的对象
由spring帮我们去加载数据到对象中, 一定要告诉spring加载这组信息
使用时候从spring中直接获取信息使用
对象类:
@ConfigurationProperties(prefix="enterprise")
总结
如果你在书写yaml数据时,经常出现如下现象,比如很多个文件都具有相同的目录前缀
center:
dataDir: /usr/local/fire/data
tmpDir: /usr/local/fire/tmp
logDir: /usr/local/fire/log
msgDir: /usr/local/fire/msgDir
或者
center:
dataDir: D:/usr/local/fire/data
tmpDir: D:/usr/local/fire/tmp
logDir: D:/usr/local/fire/log
msgDir: D:/usr/local/fire/msgDir
这个时候你可以使用引用格式来定义数据,其实就是搞了个变量名,然后引用变量了,格式如下:
baseDir: /usr/local/fire
center:
dataDir: ${baseDir}/data
tmpDir: ${baseDir}/tmp
logDir: ${baseDir}/log
msgDir: ${baseDir}/msgDir
还有一个注意事项,在书写字符串时,如果需要使用转义字符,需要将数据字符串使用双引号包裹起来
lesson: "Spring\tboot\nlesson"
总结
如果当前测试类在引导类所在的包,或者子包下的话,可以成功测试,否则就需要加 引导类的类名@SpringBootTest(classes = Springboot04JunitApplication.class)
原因是因为测试类需要拿到容器里面的bean,容器由配置类里面的run方法创建,所以需要去找配置类。
@SpringBootTest
class Springboot04JunitApplicationTests {
//注入你要测试的对象
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
//执行要测试的对象对应的方法
bookDao.save();
System.out.println("two...");
}
}
总结
MyBatis是干什么的?
SpringMVC框架负责处理浏览器发送的请求,来调用业务逻辑层来处理业务逻辑,根据需求调用持久层进行持久化操作,而这个任务就需要MyBatis来完成。
从连接数据库,到访问并且操作数据库中的数据,最终将结构返回给业务层,再由SpringMVC将结果 响应到浏览器渲染页面。 这个过程中,各个框架相互配合,共同实现一个完整的需求。
MyBatis工作时,需要哪些东西?
步骤①:创建模块时勾选要使用的技术,MyBatis,由于要操作数据库,还要勾选对应数据库或者手工导入对应技术的starter,和对应数据库的坐标
<dependencies>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
步骤②:配置数据源相关信息,没有这个信息你连接哪个数据库都不知道
#2.配置相关信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db
username: root
password: root
完了,就这么多,没了。有人就很纳闷,这就结束了?对,这就结束了,SpringBoot把配置中所有可能出现的通用配置都简化了。下面就可以写一下MyBatis程序运行需要的Dao(或者Mapper)就可以运行了
实体类
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
映射接口(Dao)
@Mapper
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
}
测试类
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
System.out.println(bookDao.getById(1));
}
}
总结
整合操作需要勾选MyBatis技术,也就是导入MyBatis对应的starter
数据库连接相关信息转换成配置
数据库SQL映射需要添加@Mapper被容器识别到
MySQL 8.X驱动强制要求设置时区
驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
pom.xml
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
application.yml
server:
port: 80
import lombok.Data;
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
步骤①:导入MyBatisPlus与Druid对应的starter,当然mysql的驱动不能少
<dependencies>
<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>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
步骤②:配置数据库连接相关的数据源配置
server:
port: 80
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
步骤③:使用MP的标准通用接口BaseMapper加速开发,别忘了@Mapper和泛型的指定
@Mapper
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
Book getById(Integer id);
}
可以升级为:
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
步骤④:制作测试类测试结果,这个测试类制作是个好习惯,不过在企业开发中往往都为加速开发跳过此步,且行且珍惜吧
package com.itheima.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookDao;
@Test
void testGetById(){
System.out.println(bookDao.selectById(1));
}
@Test
void testSave(){
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.insert(book);
}
@Test
void testUpdate(){
Book book = new Book();
book.setId(17);
book.setType("测试数据abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.updateById(book);
}
@Test
void testDelete(){
bookDao.deleteById(16);
}
@Test
void testGetAll(){
bookDao.selectList(null);
}
}
查看MP运行日志
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
定义MP拦截器并将其设置为Spring管控的bean
// 交给spring管理 一个 bean
// 而这个bean是MybatisPlus的拦截器壳子
// 拦截器壳子 里有 new PaginationInnerInterceptor() 具体的拦截器
package com.itheima.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
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
@Test
void testGetPage(){
IPage page = new Page(2,5);
IPage iPage = bookDao.selectPage(page, null);
System.out.println(iPage.getPages());
System.out.println(iPage.getCurrent());
System.out.println(iPage.getRecords());
System.out.println(iPage.getSize());
System.out.println(iPage.getTotal());
}
其中selectPage方法需要传入一个封装分页数据的对象,可以通过new的形式创建这个对象,当然这个对象也是MP提供的,别选错包了。创建此对象时就需要指定分页的两个基本数据
@Test
void testGetBy1(){
QueryWrapper<Book> wq = new QueryWrapper<>();
wq.like("name","Spring");
bookDao.selectList(wq);
}
@Test
void testGetBy2(){
String name = "1";
LambdaQueryWrapper<Book> wq = new LambdaQueryWrapper<Book>();
// if(name != null) wq.like(Book::getName,name);
wq.like(name != null,Book::getName,name);
bookDao.selectList(wq);
}
总结
使用QueryWrapper对象封装查询条件
推荐使用LambdaQueryWrapper对象
所有查询操作封装成方法调用
查询条件支持动态条件拼装
业务层接口关注的是 业务名称
数据层接口关注的是 与数据库相关的操作
一个常识性的知识普及一下,业务层的方法名定义一定要与业务有关,例如登录操作
login(String username,String password);
而数据层的方法名定义一定与业务无关,是一定,不是可能,也不是有可能,例如根据用户名密码查询
selectByUserNameAndPassword(String username,String password);
业务层接口定义如下:
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) {
IPage page = new Page(currentPage,pageSize);
bookDao.selectPage(page,null);
return page;
}
}
别忘了对业务层接口进行测试,测试类如下
@SpringBootTest
public class BookServiceTest {
@Autowired
private IBookService bookService;
@Test
void testGetById(){
System.out.println(bookService.getById(4));
}
@Test
void testSave(){
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.save(book);
}
@Test
void testUpdate(){
Book book = new Book();
book.setId(17);
book.setType("-----------------");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.updateById(book);
}
@Test
void testDelete(){
bookService.removeById(18);
}
@Test
void testGetAll(){
bookService.list();
}
@Test
void testGetPage(){
IPage<Book> page = new Page<Book>(2,5);
bookService.page(page);
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());
}
}
总结
其实MP技术不仅提供了数据层快速开发方案,业务层MP也给了一个通用接口,个人观点不推荐使用,凑合能用吧,其实就是一个封装+继承的思想,代码给出,实际开发慎用
业务层接口快速开发
public interface IBookService extends IService<Book> {
//添加非通用操作API接口
}
业务层接口实现类快速开发,关注继承的类需要传入两个泛型,一个是数据层接口,另一个是实体类
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
@Autowired
private BookDao bookDao;
//添加非通用操作API
}
如果感觉MP提供的功能不足以支撑你的使用需要,其实是一定不能支撑的,因为需求不可能是通用的,在原始接口基础上接着定义新的API接口就行了,此处不再说太多了,就是自定义自己的操作了,但是不要和已有的API接口名冲突即可。
总结
基于Restful进行表现层开发
使用Postman测试表现层接口功能
表现层接口如下:
package com.itheima.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.itheima.damain.Book;
import com.itheima.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService 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.modify(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id){
return bookService.delete(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);
}
}
总结
增删改操作结果
true
查询单个数据操作结果
{
"id": 1,
"type": "计算机理论",
"name": "Spring实战 第5版",
"description": "Spring入门经典教程"
}
查询全部数据操作结果
[
{
"id": 1,
"type": "计算机理论",
"name": "Spring实战 第5版",
"description": "Spring入门经典教程"
},
{
"id": 2,
"type": "计算机理论",
"name": "Spring 5核心原理与30个类手写实战",
"description": "十年沉淀之作"
}
]
表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
@Data
public class R {
private Boolean flag;
private Object data;
}
其中flag用于标识操作是否成功,data用于封装操作数据,现在的数据格式就变了
{
"flag": true,
"data":{
"id": 1,
"type": "计算机理论",
"name": "Spring实战 第5版",
"description": "Spring入门经典教程"
}
}
表现层开发格式也需要转换一下
package com.itheima.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.itheima.controller.utils.R;
import com.itheima.damain.Book;
import com.itheima.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController2 {
@Autowired
private IBookService bookService;
@GetMapping
public R getAll(){
return new R(true, bookService.list());
}
@PostMapping
public R save(@RequestBody Book book){
// R r = new R();
// boolean flag = bookService.save(book);
// r.setFlag(flag);
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));
}
}
总结
设计统一的返回值结果类型便于前端开发读取数据
返回值结果类型可以根据需求自行设定,没有固定格式
返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议
在进行具体的功能开发之前,先做联通性的测试,通过页面发送异步提交(axios),这一步调试通过后再进行进一步的功能开发
created() {
// 调用查询全部数据的操作
this.getAll();
},
methods: {
//列表
getAll() {
// 发送异步请求
axios.get("/books").then((res) => {
console.log(res.data)
})
},
只要后台代码能够正常工作,前端能够在日志中接收到数据,就证明前后端是通的,也就可以进行下一步的功能开发了
总结
列表功能主要操作就是加载完数据,将数据展示到页面上,此处要利用VUE的数据模型绑定,发送请求得到数据,然后页面上读取指定数据即可
页面数据模型定义
data:{
dataList: [],//当前页要展示的列表数据
...
},
异步请求获取数据
//列表
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data.data;
});
},
这样在页面加载时就可以获取到数据,并且由VUE将数据展示到页面上了
总结:
添加功能用于收集数据的表单是通过一个弹窗展示的,因此在添加操作前首先要进行弹窗的展示,添加后隐藏弹窗即可。因为这个弹窗一直存在,因此当页面加载时首先设置这个弹窗为不可显示状态,需要展示,切换状态即可
methods: {
//列表
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){
// 1 . 关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功")
}else {
this.$message.success("添加失败")
}
}).finally(() => {
// 2. 重新加载数据
this.getAll();
})
},
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("当前操作取消");
},
// 删除
handleDelete(row) {
},
//弹出编辑窗口
handleUpdate(row) {
},
总结
模仿添加操作制作删除功能,差别之处在于删除操作仅传递一个待删除的数据id到后台即可
删除操作
// 删除
handleDelete(row) {
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
this.$message.success("删除成功");
}else{
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
},
删除操作提示信息
// 删除
handleDelete(row) {
//1.弹出提示框
this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
type:'info'
}).then(()=>{
//2.做删除业务
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
this.$message.success("删除成功");
}else{
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
}).catch(()=>{
//3.取消删除
this.$message.info("取消删除操作");
});
},
总结
修改功能可以说是列表功能、删除功能与添加功能的合体。几个相似点如下:
页面也需要有一个弹窗用来加载修改的数据,这一点与添加相同,都是要弹窗
弹出窗口中要加载待修改的数据,而数据需要通过查询得到,这一点与查询全部相同,都是要查数据
查询操作需要将要修改的数据id发送到后台,这一点与删除相同,都是传递id到后台
查询得到数据后需要展示到弹窗中,这一点与查询全部相同,都是要通过数据模型绑定展示数据
修改数据时需要将被修改的数据传递到后台,这一点与添加相同,都是要传递数据
所以整体上来看,修改功能就是前面几个功能的大合体
查询并展示数据
//弹出编辑窗口
handleUpdate(row) {
axios.get("/books/"+row.id).then((res)=>{
if(res.data.flag){
//展示弹层,加载数据
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
}else{
this.$message.error("数据同步失败,自动刷新");
}
});
},
修改操作
//修改
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();
});
},
总结
目前的功能制作基本上达成了正常使用的情况,什么叫正常使用呢?也就是这个程序不出BUG,如果我们搞一个BUG出来,你会发现程序马上崩溃掉。比如后台手工抛出一个异常,看看前端接收到的数据什么样子
{
"timestamp": "2021-09-15T03:27:31.038+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/books"
}
面对这种情况,前端的同学又不会了,这又是什么格式?怎么和之前的格式不一样?
{
"flag": true,
"data":{
"id": 1,
"type": "计算机理论",
"name": "Spring实战 第5版",
"description": "Spring入门经典教程"
}
}
看来不仅要对正确的操作数据格式做处理,还要对错误的操作数据格式做同样的格式处理
首先在当前的数据结果中添加消息字段,用来兼容后台出现的操作消息
@Data
public class R{
private Boolean flag;
private Object data;
private String msg; //用于封装消息
}
后台代码也要根据情况做处理,当前是模拟的错误
@PostMapping
public R save(@RequestBody Book book) throws IOException {
Boolean flag = bookService.insert(book);
return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}
然后在表现层做统一的异常处理,使用SpringMVC提供的异常处理器做统一的异常处理
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public R doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
ex.printStackTrace();
return new R(false,null,"系统错误,请稍后再试!");
}
}
页面上得到数据后,先判定是否有后台传递过来的消息,标志就是当前操作是否成功,如果返回操作结果false,就读取后台传递的消息
//添加
handleAdd () {
//发送ajax请求
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();
});
},
总结
分页功能的制作用于替换前面的查询全部,其中要使用到elementUI提供的分页组件
<!--分页组件-->
<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) => {
});
},
后台提供对应的分页功能
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
IPage<Book> pageBook = bookService.getPage(currentPage, pageSize);
return new R(null != pageBook ,pageBook);
}
页面根据分页操作结果读取对应数据,并进行数据模型绑定
getAll() {
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
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){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if( currentPage > page.getPages()){
page = bookService.getPage((int)page.getPages(), pageSize);
}
return new R(true, page);
}
最后一个功能来做条件查询,其实条件查询可以理解为分页查询的时候除了携带分页数据再多带几个数据的查询。这些多带的数据就是查询条件。比较一下不带条件的分页查询与带条件的分页查询差别之处,这个功能就好做了
页面封装的数据:带不带条件影响的仅仅是一次性传递到后台的数据总量,由传递2个分页相关的数据转换成2个分页数据加若干个条件
后台查询功能:查询时由不带条件,转换成带条件,反正不带条件的时候查询条件对象使用的是null,现在换成具体条件,差别不大
查询结果:不管带不带条件,出来的数据只是有数量上的差别,其他都差别,这个可以忽略
经过上述分析,看来需要在页面发送请求的格式方面做一定的修改,后台的调用数据层操作时发送修改,其他没有区别
页面发送请求时,两个分页数据仍然使用路径变量,其他条件采用动态拼装url参数的形式传递
页面封装查询条件字段
pagination: {
//分页相关模型数据
currentPage: 1, //当前页码
pageSize:10, //每页显示的记录数
total:0, //总记录数
name: "",
type: "",
description: ""
},
页面添加查询条件字段对应的数据模型绑定名称
<div class="filter-container">
<el-input placeholder="图书类别" v-model="pagination.type" class="filter-item"/>
<el-input placeholder="图书名称" v-model="pagination.name" class="filter-item"/>
<el-input placeholder="图书描述" v-model="pagination.description" class="filter-item"/>
<el-button @click="getAll()" class="dalfBut">查询el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建el-button>
div>
将查询条件组织成url参数,添加到请求url地址中,这里可以借助其他类库快速开发,当前使用手工形式拼接,降低学习要求
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) => {
this.dataList = res.data.data.records;
});
},
后台代码中定义实体类封查询条件
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
System.out.println("参数=====>"+book);
IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);
return new R(null != pageBook ,pageBook);
}
对应业务层接口与实现类进行修正
public interface IBookService extends IService<Book> {
IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook);
}
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook){
IPage page = new Page(currentPage,pageSize);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(queryBook.getName()),Book::getName,queryBook.getName());
lqw.like(Strings.isNotEmpty(queryBook.getType()),Book::getType,queryBook.getType());
lqw.like(Strings.isNotEmpty(queryBook.getDescription()),Book::getDescription,queryBook.getDescription());
return bookDao.selectPage(page,lqw);
}
}
页面回显数据
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) => {
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
this.dataList = res.data.data.records;
});
},
总结