文章目录
web应用图书管理系统介绍
- 案例效果演示
-
案例实现方案分析
-
实体类开发————使用Lombok快速制作实体类
-
Dao开发————整合MyBatisPlus,制作数据层测试类
-
Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
-
Controller开发————基于Restful开发,使用PostMan测试接口功能
-
Controller开发————前后端开发协议制作
-
页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
- 列表、新增、修改、删除、分页、查询
-
项目异常处理
-
按条件查询————页面功能调整、Controller修正功能、Service修正功能
-
-
SSMP案例制作流程解析
-
先开发基础CRUD功能,做一层测一层
-
调通页面,确认异步提交成功后,制作所有功能
-
添加分页功能与查询功能
-
配置
- 勾选SpringMVC与MySQL坐标
- 修改配置文件为yml格式
- 设置端口为80方便访问
一、实体类开发
-
实体类制作
-
使用lombok简化开发
- 导入lombok无需指定版本,由SpringBoot提供版本
- @Data注解
- Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发
- lombok版本由SpringBoot提供,无需指定版本
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
- 常用注解:@Data
- 为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
二、数据层开发
- 技术实现方案
- MyBatisPlus
- Druid
2.1 开发环境配置
- 手工导入starter坐标(2个)
- 配置数据源与MyBatisPlus对应的配置
- 开发Dao接口(继承BaseMapper)
- 制作测试类测试Dao功能是否有效
- 使用配置方式开启日志,设置日志输出方式为标准输出
- 导入MyBatisPlus与Druid对应的starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
- 配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
server:
port: 81
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/new_db?serverTimezone=UTC
username: root
password: 2001
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 继承BaseMapper并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
- 制作测试类测试结果
@SpringBootTest
public class BookDaoTest {
@Autowired
private BookDao bookDao;
@Test
void testSave() {
Book book = new Book();
book.setName("测试数据");
book.setType("测试类型");
book.setDescription("测试描述数据");
bookDao.insert(book);
}
@Test
void testGetById() {
System.out.println(bookDao.selectById(13));
}
...
}
-
为方便调试可以开启MyBatisPlus的日志
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.2 分页功能
- 使用IPage封装分页数据
- 分页操作依赖MyBatisPlus分页拦截器实现功能
- 借助MyBatisPlus日志查阅执行SQL语句
- 分页操作需要设定分页对象IPage
@Test
void testGetPage(){
IPage page = new Page(1,5);
bookDao.selectPage(page,null);
}
-
IPage对象中封装了分页操作中的所有数据
- 数据
- 当前页码值
- 每页数据总量
- 最大页码值
- 数据总量
-
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
2.3 条件查询功能
- 使用QueryWrapper对象封装查询条件
- 推荐使用LambdaQueryWrapper对象
- 所有查询操作封装成方法调用
- 查询条件支持动态条件拼装
- 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void testGetByCondition(){
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
@Test
void testGetByCondition(){
QueryWrapper<Book> qw = new QueryWrapper<Book>();
qw.like("name","Spring");
bookDao.selectList(qw);
}
- 支持动态拼写查询条件
@Test
void testGetByCondition(){
String name = "Spring";
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(name),Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
三、业务层开发
- Service接口名称定义成业务名称,并与Dao接口名称进行区分
- 制作测试类测试Service功能是否有效
-
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;
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
}
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public Book getById(Integer id) {
return bookDao.selectById(id);
}
public List<Book> getAll() {
return bookDao.selectList(null);
}
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);
}
… …
}
3.1 快速开发
- 使用通用接口(ISerivce)快速开发Service
- 使用通用实现类(ServiceImpl<M,T>)快速开发ServiceImpl
- 可以在通用接口基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
- 实现类定义
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
}
- 实现类追加功能
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
}
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
@Autowired
private BookDao bookDao;
public Boolean insert(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean modify(Book book) {
return bookDao.updateById(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Book get(Integer id) {
return bookDao.selectById(id);
}
}
四、表现层开发
- 基于Restful制作表现层接口
- 新增:POST
- 删除:DELETE
- 修改:PUT
- 查询:GET
- 接收参数
- 实体数据:@RequestBody
- 路径变量:@PathVariable
-
基于Restful进行表现层接口开发
-
使用Postman测试表现层接口功能
-
功能测试
@RestController @RequestMapping("/books") public class BookController { @Autowired private IBookService bookService; @GetMapping public List<Book> getAll(){ return bookService.list(); } }
-
表现层接口开发
@RestController @RequestMapping("/books") public class BookController { @Autowired private IBookService bookService; @PostMapping public Boolean save(@RequestBody Book book){ return bookService.insert(book); } @PutMapping public Boolean update(@RequestBody Book book){ return bookService.modify(book); } @DeleteMapping("/{id}") public Boolean delete(@PathVariable Integer id){ return bookService.delete(id); } } @RestController @RequestMapping("/books") public class BookController { @Autowired private IBookService bookService; @GetMapping("/{id}") public Book getById(@PathVariable Integer id){ return bookService.getById(id); } @GetMapping public List<Book> getAll(){ return bookService.list(); } @GetMapping("/{currentPage}/{pageSize}") public List<Book> getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){ return bookService.getPage(currentPage,pageSize).getRecords(); } }
3.1 消息一致性处理
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议
- 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
@Data
public class R{
private Boolean flag;
private Object data;
}
@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;
@PostMapping
public R save(@RequestBody Book book){
Boolean flag = bookService.insert(book);
return new R(flag);
}
@PutMapping
public R update(@RequestBody Book book){
Boolean flag = bookService.modify(book);
return new R(flag);
}
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id){
Boolean flag = bookService.delete(id);
return new R(flag);
}
@GetMapping("/{id}")
public R getById(@PathVariable Integer id){
Book book = bookService.getById(id);
return new R(true,book);
}
@GetMapping
public R getAll(){
List<Book> bookList = bookService.list();
return new R(true ,bookList);
}
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
return new R(true,page);
}
}
3.2 前后端协议联调
-
单体项目中页面放置在resources/static目录下
-
created钩子函数用于初始化页面时发起调用
-
页面使用axios发送异步请求获取数据后确认前后端是否联通
-
前后端分离结构设计中页面归属前端服务器
-
单体工程中页面放置在resources目录下的static目录中(建议执行clean)
-
前端发送异步请求,调用后端接口
//列表
getAll() {
axios.get("/books").then((res)=>{
console.log(res.data);
});
},
- 列表页
//列表
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data.data;
});
},
-
将查询数据返回到页面,利用前端数据双向绑定进行数据展示
- 请求方式使用POST调用后台对应操作
- 添加操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 弹出添加Div时清除表单数据
-
弹出添加窗口
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
},
- 清除数据
//重置表单
resetForm() {
this.formData = {};
},
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
- 添加
//添加
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("操作取消");
},
- 删除
- 请求方式使用Delete调用后台对应操作
- 删除操作需要传递当前行数据对应的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) {
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
this.$message.success("删除成功");
}else{
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
}
- 弹出修改窗口
- 加载要修改数据通过传递当前行数据对应的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("数据同步失败,自动刷新");
}
});
},
- 删除消息维护
//删除
handleDelete(row) {
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
this.$message.success("删除成功");
}else{
this.$message.error("数据同步失败,自动刷新");
}
}).finally(()=>{
this.getAll();
});
}
- 修改
- 请求方式使用PUT调用后台对应操作
- 修改操作结束后动态刷新页面加载数据(同新增)
- 根据操作结果不同,显示对应的提示信息(同新增)
//修改
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("操作取消");
},
3.3 业务消息一致性处理
- 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
- 异常处理器必须被扫描加载,否则无法生效
- 表现层返回结果的模型类中添加消息属性用来传递消息到页面
- 对异常进行统一处理,出现异常后,返回指定信息
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public R doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
ex.printStackTrace();
return new R(false,null,"系统错误,请稍后再试!");
}
}
- 修改表现层返回结果的模型类,封装出现异常后对应的信息
- flag:false
- Data: null
- 消息(msg): 要显示信息
@Data
public class R{
private Boolean flag;
private Object data;
private String msg;
public R(Boolean flag,Object data,String msg){
this.flag = flag;
this.data = data;
this.msg = msg;
}
}
- 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
//添加
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();
});
},
- 可以在表现层Controller中进行消息统一处理
- 目的:国际化
@PostMapping
public R save(@RequestBody Book book) throws IOException {
Boolean flag = bookService.insert(book);
return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}
- 页面消息处理
//添加
handleAdd () {
//发送ajax请求
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();
});
},
3.4 分页功能
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
- 页面使用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) => {});
},
分页查询
- 使用路径参数传递分页数据或封装对象传递数据
@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();
},
3.5 删除功能维护
- 基于业务需求维护删除功能
- 对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询
@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);
}
3.6 条件查询功能
- 定义查询条件数据模型(当前封装到分页数据模型中)
- 异步调用分页功能并通过请求参数传递数据到后台
- 查询条件数据封装
- 单独封装
- 与分页操作混合封装
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>
- 组织数据成为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) => {
this.dataList = res.data.data.records;
});
},
- Controller接收参数
@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);
}
}
- Controller调用业务层分页条件查询接口
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
IPage<Book> pageBook = bookService.getPage(currentPage,pageSize,book);
return new R(null != pageBook ,pageBook);
}
- 页面回显数据
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;
});
},
五、总结
基于SpringBoot的SSMP整合案例
- pom.xml
配置起步依赖 - application.yml
设置数据源、端口、框架技术相关配置等 - dao
继承BaseMapper、设置@Mapper - dao测试类
- service
调用数据层接口或MyBatis-Plus提供的接口快速开发 - service测试类
- controller
基于Restful开发,使用Postman测试跑通功能 - 页面
放置在resources目录下的static目录中