● 案例实现方案分析
◆ 实体类开发——使用Lombok快速制作实体类
◆ DAO开发——整合MyBatisPlus,制作数据层测试类
◆ Service开发——基于MybatisPlus进行增量开发,制作业务层测试类
◆ Controller开发——基于Restful开发,使用PostMan测试接口功能
◆ Controller开发——前后端开发协作制作
◆ 页面开发——基于VUE+ElmentUI制作,前后端联调,页面数据处理,页面消息处理
▶ 列表、新增、修改、删除、分页、查询
◆项目异常处理
◆按条件查询——页面功能调整、Controller修正功能,Service修正功能
● 先开发基础CRUD功能,做一层测一层
● 调通页面,确认异步提交成功后,制作所有功能
●添加分页功能与查询功能
1.勾选SpringMVC与Mysql坐标
2.修改配置文件为yml格式
3.设置端口为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;
}
● 为当前实体类在编译器设置get/set方法,toString方法,hashCode方法,equals方法等
● 技术实现方案
◆ MybatisPlus
◆ Durid
● 导入MybatisPlus与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>
● 配置数据源与MybatisPlus的基础配置(id生成策略使用数据库自增策略)
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?servierTimezone=UTC
username: root
password: root
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
● 继承BaseMapper并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
● 制作测试类测试结果
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookDao;
@Test
public void testGetById(){
System.out.println(bookDao.selectById(1));
}
@Test
void testSave(){
Book book = new Book();
book.setType("测试数据abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.insert(book);
}
@Test
void testUpdate(){
Book book = new Book();
book.setId(14);
book.setType("测试数据update");
book.setName("测试数据update");
book.setDescription("测试数据update");
bookDao.updateById(book);
}
...
}
1.手工导入start坐标(2个)
2.配置数据源与MybatisPlus对应的配置
3.开发Dao接口(继承BaseMapper)
4.制作测试类测试Dao功能是否有效
● 为方便调试可以开启MyBatisPlus的日志
● 使用配置方式开启日志,设置日志输出方式为标准输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
● 分页操作需要设定分页对象IPage
● IPage对象中封装了分页操作中的所有数据
◆ 数据
◆ 当前页码值
◆ 每页数据总量
◆ 最大页码值
◆ 数据总量
● 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1.定义Mp拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
1.使用IPage封装分页数据
2.分页操作依赖MyBatisPlus分页拦截器实现功能
3.借助MyBatisPlus日志查阅执行SQL语句
● 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void testGetByCondition(){
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lw = new LambdaQueryWrapper<>();
lw.like(Book::getName,"Spring");
bookDao.selectPage(page,lw);
}
@Test
void testGetByCondition3(){
QueryWrapper<Book> lw = new QueryWrapper<>();
lw.like("name","Spring");
bookDao.selectList(lw);
}
● 支持动态拼写查询条件
@Test
void testGetByCondition3(){
QueryWrapper<Book> lw = new QueryWrapper<>();
lw.like("name","Spring");
bookDao.selectList(lw);
}
1.使用QueryWrapper对象封装查询条件
2.推荐使用LambdaQueryWrapper对象
3.所有查询操作封装成方法调用
4.查询条件支持动态条件拼装
● Service层接口定义与数据层接口定义具有较大区别,不要混用
◆selectByUserNameAndPassword(String username,String password);
◆login(String username,String password);
public interface BookService {
boolean save(Book book);
boolean delete(Integer id);
boolean update(Book book);
Book getById(Integer id);
List<Book> getAll();
IPage<Book>getByPage(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 delete(Integer id) {
return bookDao.deleteById(id)>0;
}
@Override
public boolean update(Book book) {
return bookDao.updateById(book)>0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getByPage(int currentPage, int pageSize) {
IPage page = new Page<Book>(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
@SpringBootTest
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
void testGetById(){
bookService.getById(9);
}
@Test
void testGetAll(){
bookService.getAll();
}@Test
void testGetByPage(){
bookService.getByPage(1,5);
}
......
}
1.Service接口名称定义成业务名称,并与Dao接口名称进行区分
2.制作测试类测试Service功能是否有效
● 快速开发方案
◆ 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl
◆ 在通用类基础上做功能重载或功能追加
◆ 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
public interface IBookService extends IService<Book> {
boolean saveBook(Book book);
boolean modify(Book book);
boolean delete(Integer id);
}
● 实现类定义
@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 modify(Book book) {
return bookDao.updateById(book) >0;
}
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id)>0;
}
}
1.使用通用接口(ISerivce)快速开发Service
2.使用通用实现类(ServiceImpl)快速开发ServiceImpl
3.以在通用接口基础上做功能重载或功能追加
4.注意重载时不要覆盖原始操作,避免原始提供的功能丢失
● 基于Restful进行表现层接口开发
● 使用Postman测试表现层接口功能
● 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
@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.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));
}
}
1.设计统一的返回值结果类型便于前端开发读取数据
2.返回值结果类型可以根据需求自行设定,没有固定格式
3.返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议
● 前端发送异步请求,调用后端接口
getAll() {
axios.get("/books").then((res)=>{
console.log(res.data);
});
},
1.单体项目中页面放置在resources/static目录下
2.created钩子函数用于初始化页面时发起调用
3.页面使用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){
//关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else{
this.$message.error("添加失败")
}
}).finally(()=>{
//重新加载数据
this.getAll();
});
},
● 取消
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("当前操作取消");
},
1.请求方式使用POST调用后台对应操作
2.添加操作结束后动态刷新页面加载数据
3.根据操作结果不同,显示对应的提示信息
4.弹出添加Div时清除表单数据
// 删除
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("取消删除操作");
});
},
1.请求方式使用Delete调用后台对应操作
2.删除操作需要传递当前行数据对应的id值到后台
3.删除操作结束后动态刷新页面加载数据
4.根据操作结果不同,显示对应的提示信息
5.删除操作前弹出提示框避免误操作
● 弹出修改窗口
//弹出编辑窗口
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();
});
},
● 删除消息维护
// 删除
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("取消删除操作");
});
},
1.加载要修改数据通过传递当前行数据对应的id值到后台查询数据
2.利用前端数据双向绑定将查询到的数据进行回显
● 修改
//修改
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();
});
},
● 取消添加和修改
cancel(){
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("操作取消");
},
1.请求方式使用PUT调用后台对应操作
2.修改操作结束后动态刷新页面加载数据(同新增)
3.根据操作结果不同,显示对应的提示信息(同新增)
● 对异常进行统一处理,出现异常后,返回指定信息
//作为springmvc的异常处理器
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
@ExceptionHandler(Exception.class)
public R doException(Exception ex){
ex.printStackTrace();
return new R("服务器故障,请稍后再试");
}
}
● 修改表现层返回结果的模型类,封装出现异常后对应的信息
◆ flag:false
◆ Data: null
◆ 消息(msg): 要显示信息
@Data
public class R {
private boolean flag;
private Object data;
private String msg;
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 msg) {
this.flag = flag;
this.msg = msg;
}
public R(String msg) {
this.flag = false;
this.msg = msg;
}
}
● 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
if (res.data.flag){
//关闭弹层
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
}else{
this.$message.error(res.data.msg);
}
}).finally(()=>{
//重新加载数据
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 ? "添加成功^_^" : "添加失败-_-!");
}
1.使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
2.异常处理器必须被扫描加载,否则无法生效
3.表现层返回结果的模型类中添加消息属性用来传递消息到页面
● 页面使用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>
● 定义分页组件需要使用的数据并将数据绑定到分页组件
data:{
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0//总记录数
}
},
● 替换查询全部功能为分页功能
getAll() {
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res)=>{
//console.log(res.data);
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;
});
},
● 删除功能维护
◆ 基于业务需求维护删除功能
@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);
}
● 分页页码值切换
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前选中的页码值
this.pagination.currentPage=currentPage;
//执行查询
this.getAll();
},
1.使用el分页组件
2.定义分页组件绑定的数据模型
3.异步调用获取分页数据
4.分页数据页面回显
● 查询条件数据封装
◆ 单独封装
◆ 与分页操作混合封装
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0,//总记录数
type: "",
name: "",
description: ""
}
● 页面数据模型绑定
<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>
● 组织数据成为get请求发送的数据
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)=>{
//console.log(res.data);
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;
});
},
● Controller接收参数
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book 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);
}
● 业务层接口功能开发
@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);
bookDao.selectPage(page,lqw);
return page;
}
1.定义查询条件数据模型(当前封装到分页数据模型中)
2.异步调用分页功能并通过请求参数传递数据到后台