文章目录
- 前言
- 一、表现层与前端数据传输协议定义
- 二、表现层与前端数据传输协议实现
- 三、异常处理器
- 四、项目异常处理
- 五、前后台协议联调实现CRUD
- - - 查询
- - - 添加
- - - 修改
- - - 删除
- 总结
前言
为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。
(博客的参考源码可以在我主页的资源里找到,如果在学习的过程中有什么疑问欢迎大家在评论区向我提出)
1. 本文建立在SSM整合快速入门案例(一)的基础上进行编写,该博客可以在我的ssm专栏里找到
一、表现层与前端数据传输协议定义
1. 情景引入
后端返回的数据格式不统一,不利于前端访问数据,需要统一格式
2. 解决方案
前端接收数据格式-创建结果模型类,封装数据到data属性中
3. 不足与改进1.0
- 在ssm快速入门的基础案例中我们发现增删改查返回到前端页面的结果都是true,不利于区分
- 因此我们在结果模型类data中添加一个code属性,代表具体的查询结果(示例中用200X代表操作种类,用0和1代表操作是否成功)
4. 不足与改进2.0
- 当查询空时数据data返回null,页面渲染空数据,此时展示什么给用户看呢?
- 因此我们在结果模型类中添加一个message属性用于解决该类问题
5. 表现层数据封装
- 设置统一数据返回结果类
- Result类中的字段并不是固定的,可以根据需要自行增减,提供若干个构造方法,方便操作
二、表现层与前端数据传输协议实现
结果模型类属于表现层,故将其定义在controller包下
1. 在controller包中创建编辑结果模型类Result,并定义几个构造方法模拟需求
public class Result {
//此处省略gerter、setter方法
//描述统一格式中的数据
private Object data;
//描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
private Integer code;
//描述统一格式中的消息,可选属性
private String msg;
public Result() {
}
public Result(Integer code, Object data) {
this.data = data;
this.code = code;
}
public Result(Integer code, Object data, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
2. 在controller包中创建定义状态码类Code
Code类的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET OK,GET ALL OK,GET PAGE OK
//状态码
public class Code {
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
}
3. 修改优化BookController
- 所有方法都返回Result类型
- return语句中进行操作成功与否判断并返回状态码和数据(增删改查返回数据的结构有所差别,对应了结果模型类中不同的构造方法)
//统一每一个控制器方法返回值
("/books")
public class BookController {
private BookService bookService;
public Result save( Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);
}
public Result update( Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);
}
("/{id}")
public Result delete( Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);
}
("/{id}")
public Result getById( Integer id) {
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}
public Result getAll() {
List<Book> bookList = bookService.getAll();
Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
String msg = bookList != null ? "" : "数据查询失败,请重试!";
return new Result(code,bookList,msg);
}
}
4. 测试示例
三、异常处理器
1. 情景导入
程序在开发中不可避免的会遇到异常现象,无法正常返回数到表现层,影响了业务的正常运行和用户体验,因此我们需要对其进行处理
2. 异常的分类
- 框架内部抛出的异常: 因使用不合规导致(比如mybatis配置中写错相关信息)
- 数据层抛出的异常: 因外部服务器故障导致 (例如: 服务器访问超时、sql语句书写错误)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如: 遍历业务书写操作,导致索引异常等
- 表现层抛出的异常:因数据收集、校验等规则导致 (例如: 不匹配的数据类型间导致异常)
- 工具类抛出的异常: 因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
3. 异常处理器
- 各个层级均出现异常,异常处理代码书写在哪一层
所有的异常全部抛到表现层进行分类处理
- 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决
采用AOP思想,当程序出异常时都到AOP思想定义的方法中去处理
- 是否自己单独写AOP
其实spring提供了基于AOP思想的异常处理器来集中统一解决异常,一般格式如下:
- 名称: @RestControllerAdvice
类型:类注解
位置: Rest风格开发的控制器增强类定义上方
作用:用于标识当前类为REST风格对应的异常处理器,为Rest风格开发的控制器类做增强,
说明:此注解自带@ResponseBody注解与@Component注解,具备对应的功能- 名称: @ExceptionHandler
类型:方法注解
位置:专用于异常处理的控制器方法上方
作用:设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常- 示例:
四、项目异常处理
1. 项目异常分类
- 业务异常 (BusinessException)
规范的用户行为产生的异常和不规范的用户行为操作产生的异常- 系统异常 (SystemException)
项目运行过程中可预计且无法避免的异常其他异常- 其他异常 (Exception)
编程人员未预期到的异常
2. 项目异常处理方案
- 业务异常 (BusinessException
发送对应消息传递给用户,提醒规范操作.- 系统异常 (SystemException)
发送固定消息传递给用户,安抚用户;
发送特定消息给运维人员,提醒维护;
记录日志.- 其他异常 (Exception)
发送固定消息传递给用户,安抚用户;
发送特定消息给编程人员,提醒维护 (纳入预期范围内);
记录日志.
3. 定义异常类
- 创建exception包,创建定义系统异常类SystemException
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{
//异常状态码
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
- 创建定义异常类SystemException
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{
//异常状态码
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
4. 在Code类中添加对应的异常状态码
public static final Integer SYSTEM_ERR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;
public static final Integer BUSINESS_ERR = 60002;
5. 在业务层实现类BookServiceImpl中模拟异常
public Book getById(Integer id) {
//模拟业务异常,包装成自定义异常
if(id == 1){
throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
int i = 1/0;
}catch (Exception e){
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return bookDao.getById(id);
}
6. 在Controller包中创建编写项目异常处理类ProjectExceptionAdvice
//1.@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
public class ProjectExceptionAdvice {
//2.@ExceptionHandler用于设置当前处理器类对应的异常类型
(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(ex.getCode(),null,ex.getMessage());
}
(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
(Exception.class)
public Result doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}
7. 模拟异常访问与文件结构示例
8. 小结:项目异常案例处步骤
- 自定义项目系统级异常
- 自定义项目业务级异常
- 自定义异常编码
- 触发自定义异常
- 拦截并处理异常
- 异常处理器效果对比
五、前后台协议联调实现CRUD
1. 导入前端页面
具体代码可以在我主页上传的的资源里找到
2. 在config包下编辑创建springmvc路径支持类SpringMvcSupport
class SpringMvcSupport extends WebMvcConfigurationSupport {
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
public
3. 加载springmvc支持类(在springmvc配置中扫描config包)
({"org.example.controller","org.example.config"})
public class SpringMvcConfig {
}
4. 运行tomcat,访问页面
- - 查询
5. 查询获取数据(渲染数据是前端双向绑定做的)
- 注释及相关的查询js代码
- 发送ajax请求
- 请求类型是get
- 请求路径是rest风格“/books”
- 请求结果模型dataList
- 从res.data中取数据
- 进一步从自定义的data字段中取数据
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data.data;
});
}
- 重启tomcat再次访问books.html
- - 添加
6. 添加功能
- 为了实现判断是否添加失败的功能,BookDao中的方法的返回值由void改为int,代表行计数(即该操作改变了多少行)
public interface BookDao {
// @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
public int save(Book book);
("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
public int update(Book book);
("delete from tbl_book where id = #{id}")
public int delete(Integer id);
("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
("select * from tbl_book")
public List<Book> getAll();
}
- 修改业务层实现类,当操作成功则返回值大于1,为true;反之,为false
class BookServiceImpl implements BookService {
private BookDao bookDao;
public boolean save(Book book) {
return bookDao.save(book) > 0;
}
public boolean update(Book book) {
return bookDao.update(book) > 0;
}
public boolean delete(Integer id) {
return bookDao.delete(id) > 0;
}
public Book getById(Integer id) {
if(id == 1){
throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
}
// //将可能出现的异常进行包装,转换成自定义异常
// try{
// int i = 1/0;
// }catch (Exception e){
// throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
// }
return bookDao.getById(id);
}
public List<Book> getAll() {
return bookDao.getAll();
}
}
public
- 注释及相关的添加操作js代码
- 发送ajax请求
- 添加的请求类型是post
- 请求的rest风格路径是“/books”
- 提交的内容是表单数据,经查询,本案例中添加表单自定义标识符叫formData
- $message.error() 在该前端框架中代表切换失败的样式
- 获得的数据res.data,自定义状态码code,获取的数据中的状态码res.data.code
- 添加操作完后重新获取所有数据this.getAll()
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
console.log(res.data);
//如果操作成功,关闭弹层,显示数据
if(res.data.code == 20011){
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else if(res.data.code == 20010){
this.$message.error("添加失败");
}else{
this.$message.error(res.data.msg);
}
}).finally(()=>{
this.getAll();
});
},
- 运行结果示例
添加成功示例
添加失败示例
- - 修改
7. 修改功能
- 注释及相关的查询js代码
- 修改分两步,弹出编辑窗口根据id查询数据 + 编辑后保存加载数据
- 根据上文的设计可知从row获取行数据
- 根据上文的设计可知弹窗的标签名为dialogFormVisible4Edit,值为true代表展示弹层
//弹出编辑窗口
handleUpdate(row) {
// console.log(row); //row.id 查询条件
//查询数据,根据id查询
axios.get("/books/"+row.id).then((res)=>{
// console.log(res.data.data);
if(res.data.code == 20041){
//展示弹层,加载数据
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
}else{
this.$message.error(res.data.msg);
}
});
},
//编辑
handleEdit() {
//发送ajax请求
axios.put("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层,显示数据
if(res.data.code == 20031){
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
}else if(res.data.code == 20030){
this.$message.error("修改失败");
}else{
this.$message.error(res.data.msg);
}
}).finally(()=>{
this.getAll();
});
},
- 运行示例
- - 删除
8. 删除功能
- 删除应该出现一个提示框,提示用户是否删除
- 删除的提交类型为delete
- 相关注释及js代码
handleDelete(row) {
//1.弹出提示框
this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
type:'info'
}).then(()=>{
//2.做删除业务
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.code == 20021){
this.$message.success("删除成功");
}else{
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
}).catch(()=>{
//3.取消删除
this.$message.info("取消删除操作");
});
}
- 运行示例
总结
欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下。
(博客的参考源码可以在我主页的资源里找到,如果在学习的过程中有什么疑问欢迎大家在评论区向我提出)