1、案例实现方案分析
Lombok
快速制作实体类Dao
开发————整合MyBatisPlus
,制作数据层测试类Service
开发————基于MyBatisPlus
进行增量开发,制作业务层测试类Controller
开发————基于Restful
开发,使用PostMan
测试接口功能Controller
开发————前后端开发协议制作VUE+ElementUI
制作,前后端联调,页面数据处理,页面消息处理
Controller
修正功能、Service
修正功能2、SSMP案例制作流程解析
勾选SpringMVC与MySQL坐标
添加依赖
<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>
修改配置文件为yml格式
设置端口为80方便访问
server:
port: 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;
}
注意:
@Data
为当前实体类在编译期设置对应的get/set
方法,toString
方法,hashCode
方法,equals
方法等- 但是不提供构造器方法
1、技术实现方案
MyBatis-Plus
Druid
2、导入MyBatis-Plus
与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>
3、配置数据源与MyBatisPlus
对应的基础配置(id
生成策略使用数据库自增策略)
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
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
4、继承BaseMapper
并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
5、制作测试类测试结果(基本CRUD)
@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(6);
book.setType("测试数据abc");
book.setName("测试数据abc");
book.setDescription("测试数据abc");
bookDao.updateById(book);
}
/**
* 删除一条数据
*/
@Test
void testDelete() {
bookDao.deleteById(7);
}
/**
* 查询所有数据
*/
@Test
void testGetAll() {
List<Book> books = bookDao.selectList(null);
books.forEach(System.out::println);
}
}
6、开启MyBatis-Plus
运行日志
MyBatis-Plus
的日志mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
使用配置方式开启日志,设置日志输出方式为标准输出
7、分页功能
①分页操作需要设定分页对象IPage
@Test
void testPage() {
IPage page = new Page(2,2);
bookDao.selectPage(page, null);
}
②IPage
对象中封装了分页操作中的所有数据
③分页操作是在MyBatis-Plus
的常规操作基础上增强得到,内部是动态的拼写SQL
语句,因此需要增强对应的功能, 使用MyBatis-Plus
拦截器实现
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//定义Mp拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加用于分页的拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
8、按条件查询
使用QueryWrapper
对象封装查询条件,推荐使用LambdaQueryWrapper
对象,所有查询操作封装成方法调用
@Test
void testGetBy() {
//SELECT id,type,name,description FROM tbl_book WHERE (name LIKE ?)
QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Spring");
bookDao.selectList(queryWrapper);
}
@Test
void testGetBy2() {
String name = "1";
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
/*if (name != null) {
queryWrapper.like(Book::getName, name);
}*/
queryWrapper.le(name != null, Book::getName, name);
bookDao.selectList(queryWrapper);
}
小结:
①使用
QueryWrapper
对象封装查询条件②推荐使用
LambdaQueryWrapper
对象③所有查询操作封装成方法调用
④查询条件支持动态条件拼装
1、Service
层接口定义与数据层接口定义具有较大区别,不要混用
2、接口定义
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(Integer currentPage, int pageSize);
}
3、实现类定义
@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(Integer currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
return bookDao.selectPage(page, null);
}
}
4、测试类定义
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void testGetById() {
System.out.println(bookService.getById(3));
}
@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(6);
book.setType("测试数据update");
book.setName("测试数据abc");
book.setDescription("测试数据abc");
bookService.update(book);
}
@Test
void testDelete() {
bookService.delete(8);
}
@Test
void testGetAll() {
List<Book> books = bookService.getAll();
books.forEach(System.out::println);
}
@Test
void testPage() {
IPage<Book> page = bookService.getPage(2, 2);
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getRecords());
System.out.println(page.getTotal());
System.out.println(page.getSize());
}
}
5、快速开发
快速开发方案:
MyBatis-Plus
提供有业务层通用接口(ISerivce
)与业务层通用实现类(ServiceImpl
)①接口定义
public interface IBookService extends IService<Book> {
}
②实现类定义
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}
③实现类追加功能
@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 modifyBook(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public boolean deleteBook(Integer id) {
return bookDao.deleteById(id) > 0;
}
}
小结:
- 使用通用接口(
ISerivce
)快速开发Service
- 使用通用实现类(
ServiceImpl
)快速开发ServiceImpl
- 可以在通用接口基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
Restful
进行表现层接口开发Postman
测试表现层接口功能1、功能测试
@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.modifyBook(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id) {
return bookService.deleteBook(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);
}
}
小结
①基于
Restful
制作表现层接口
新增:
POST
删除:
DELETE
修改:
PUT
查询:
GET
②接收参数
- 实体数据:
@RequestBody
- 路径变量:
@PathVariable
2、表现层消息一致性处理
@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.modifyBook(book));
}
@DeleteMapping("{id}")
public R delete(@PathVariable Integer id) {
return new R(bookService.deleteBook(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));
}
}
小结:
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议
resources
目录下的static
目录中(建议执行clean
)//列表
getAll() {
//发送异步请求
axios.get("/books").then((res)=>{
console.log(res.data);
})
},
小结:
①单体项目中页面放置在
resources/static
目录下②
created
钩子函数用于初始化页面时发起调用③页面使用
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) {
//1.关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
} else {
this.$message.error("添加失败");
}
}).finally(()=>{
//2.重新加载数据
this.getAll();
});
},
④取消添加
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("当前操作取消");
},
小结:
- 请求方式使用
POST
调用后台对应操作- 添加操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 弹出添加
Div
时清除表单数据
// 删除
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("取消操作");
});
},
小结:
- 请求方式使用
Delete
调用后台对应操作- 删除操作需要传递当前行数据对应的id值到后台
- 删除操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 删除操作前弹出提示框避免误操作
1、弹出修改窗口
//弹出编辑窗口
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();
});
},
小结:
- 加载要修改数据通过传递当前行数据对应的id值到后台查询数据
- 利用前端数据双向绑定将查询到的数据进行回显
2、修改
//修改
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();
});
},
3、取消添加和修改
//取消
cancel(){
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("当前操作取消");
},
小结:
- 请求方式使用
PUT
调用后台对应操作- 修改操作结束后动态刷新页面加载数据(同新增)
- 根据操作结果不同,显示对应的提示信息(同新增)
Bug
导致数据格式不统一性//作为SpringMVC的异常处理
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
@ExceptionHandler(Exception.class)
public R doException(Exception ex) {
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new R("服务器故障,请稍后再试");
}
}
@Data
public class R {
private Boolean flag;
private Object data;
private String message;
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 message) {
this.flag = flag;
this.message = message;
}
public R(String message) {
this.flag = false;
this.message = message;
}
}
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
//判断当前操作是否成功
if (res.data.flag) {
//1.关闭弹层
this.dialogFormVisible = false;
this.$message.success(res.data.message);
} else {
this.$message.error(res.data.message);
}
}).finally(()=>{
//2.重新加载数据
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 ? "添加成功^_^":"添加失败-_-!");
}
小结:
- 使用注解
@RestControllerAdvice
定义SpringMVC
异常处理器用来处理异常的- 异常处理器必须被扫描加载,否则无法生效
- 表现层返回结果的模型类中添加消息属性用来传递消息到页 面
1、页面使用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>
2、定义分页组件需要使用的数据并将数据绑定到分页组件
data:{
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:4,//每页显示的记录数
total:0,//总记录数
}
},
3、分页查询
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return new R(true, bookService.getPage(currentPage, pageSize));
}
4、替换查询全部功能为分页功能,加载分页数据
//分页
getAll() {
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;
this.dataList = res.data.data.records;
});
},
5、切换页码
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前选中页码值
this.pagination.currentPage = currentPage;
//执行查询
this.getAll();
},
小结:
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
6、删除最后一页的唯一一条数据,会出现以下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);
}
1、查询条件数据封装
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:4,//每页显示的记录数
total:0,//总记录数
type: "",
name: "",
description: ""
}
2、页面数据模型绑定
<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>
3、组织数据成为get请求发送的数据
//分页
getAll() {
//组织参数,拼接url请求地址
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;
});
},
4、Controller接收参数
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {
System.out.println("参数==>" + 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);
}
5、业务层接口功能开发
@Override
public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<Book>();
queryWrapper.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType())
.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName())
.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
IPage page = new Page(currentPage, pageSize);
bookDao.selectPage(page, queryWrapper);
return page;
}