劳动保障局瓯海劳务市场和做网站,自己做的视频网站上传电影,做淘宝客网站,门户网站标题居中加大#x1f648;作者简介#xff1a;练习时长两年半的Java up主 #x1f649;个人主页#xff1a;程序员老茶 #x1f64a; ps:点赞#x1f44d;是免费的#xff0c;却可以让写博客的作者开心好久好久#x1f60e; #x1f4da;系列专栏#xff1a;Java全栈#xff0c;… 作者简介练习时长两年半的Java up主 个人主页程序员老茶 ps:点赞是免费的却可以让写博客的作者开心好久好久 系列专栏Java全栈计算机系列火速更新中 格言种一棵树最好的时间是十年前其次是现在 动动小手点个关注不迷路感谢宝子们一键三连 目录 课程名Java内容/作用知识点/设计/实验/作业/练习学习基于SpringBoot实现SSMP整合 SSMP整合综合案例0.模块创建1.实体类开发2.数据层开发——基础CRUD查看MP运行日志 3.数据层开发——分页功能制作4.数据层开发——条件查询功能制作5.业务层开发业务层快速开发 6.表现层开发7.表现层消息一致性处理8.前后端联通性测试9.页面基础功能开发9.1 列表功能非分页版9.2 添加功能9.3 删除功能9.4 修改功能 10.业务消息一致性处理11.页面功能开发11.1 分页功能11.2 删除功能维护11.3 条件查询功能 课程名Java
内容/作用知识点/设计/实验/作业/练习
学习基于SpringBoot实现SSMP整合
SSMP整合综合案例
整体案例中需要采用的技术如下先了解一下做到哪一个说哪一个
实体类开发————使用Lombok快速制作实体类Dao开发————整合MyBatisPlus制作数据层测试Service开发————基于MyBatisPlus进行增量开发制作业务层测试类Controller开发————基于Restful开发使用PostMan测试接口功能Controller开发————前后端开发协议制作页面开发————基于VUEElementUI制作前后端联调页面数据处理页面消息处理 列表新增修改删除分页查询 项目异常处理按条件查询————页面功能调整、Controller修正功能、Service修正功能
可以看的出来东西还是很多的希望通过这个案例各位小伙伴能够完成基础开发的技能训练。整体开发过程采用做一层测一层的形式进行过程完整战线较长希望各位能跟进进度完成这个小案例的制作。
0.模块创建
对于这个案例如果按照企业开发的形式进行应该制作后台微服务前后端分离的开发。 我知道这个对初学的小伙伴要求太高了咱们简化一下。后台做单体服务器前端不使用前后端分离的制作了。
一个服务器即充当后台服务调用又负责前端页面展示降低学习的门槛。
下面我们就可以创建一个新的模块加载要使用的技术对应的starter修改配置文件格式为yml格式并把web访问端口先设置成80。
pom.xml
dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency
/dependenciesapplication.yml
server:port: 801.实体类开发
本案例对应的模块表结构如下
-- ----------------------------
-- Table structure for tbl_book
-- ----------------------------
DROP TABLE IF EXISTS tbl_book;
CREATE TABLE tbl_book (id int(11) NOT NULL AUTO_INCREMENT,type varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,name varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,description varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB AUTO_INCREMENT 51 CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO tbl_book VALUES (1, 计算机理论, Spring实战 第5版, Spring入门经典教程深入理解Spring原理技术内幕);
INSERT INTO tbl_book VALUES (2, 计算机理论, Spring 5核心原理与30个类手写实战, 十年沉淀之作手写Spring精华思想);
INSERT INTO tbl_book VALUES (3, 计算机理论, Spring 5 设计模式, 深入Spring源码剖析Spring源码中蕴含的10大设计模式);
INSERT INTO tbl_book VALUES (4, 计算机理论, Spring MVCMyBatis开发从入门到项目实战, 全方位解析面向Web应用的轻量级框架带你成为Spring MVC开发高手);
INSERT INTO tbl_book VALUES (5, 计算机理论, 轻量级Java Web企业应用实战, 源码级剖析Spring框架适合已掌握Java基础的读者);
INSERT INTO tbl_book VALUES (6, 计算机理论, Java核心技术 卷I 基础知识原书第11版, Core Java 第11版Jolt大奖获奖作品针对Java SE9、10、11全面更新);
INSERT INTO tbl_book VALUES (7, 计算机理论, 深入理解Java虚拟机, 5个维度全面剖析JVM大厂面试知识点全覆盖);
INSERT INTO tbl_book VALUES (8, 计算机理论, Java编程思想第4版, Java学习必读经典,殿堂级著作赢得了全球程序员的广泛赞誉);
INSERT INTO tbl_book VALUES (9, 计算机理论, 零基础学Java全彩版, 零基础自学编程的入门图书由浅入深详解Java语言的编程思想和核心技术);
INSERT INTO tbl_book VALUES (10, 市场营销, 直播就该这么做主播高效沟通实战指南, 李子柒、李佳琦、薇娅成长为网红的秘密都在书中);
INSERT INTO tbl_book VALUES (11, 市场营销, 直播销讲实战一本通, 和秋叶一起学系列网络营销书籍);
INSERT INTO tbl_book VALUES (12, 市场营销, 直播带货淘宝、天猫直播从新手到高手, 一本教你如何玩转直播的书10堂课轻松实现带货月入3W); 根据上述表结构制作对应的实体类
实体类
public class Book {private Integer id;private String type;private String name;private String description;
} 实体类的开发可以自动通过工具手工生成get/set方法然后覆盖toString()方法方便调试等等。不过这一套操作书写很繁琐有对应的工具可以帮助我们简化开发介绍一个小工具lombok。
Lombok一个Java类库提供了一组注解简化POJO实体类开发SpringBoot目前默认集成了lombok技术并提供了对应的版本控制所以只需要提供对应的坐标即可在pom.xml中添加lombok的坐标。
dependencies!--lombok--dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency
/dependencies 使用lombok可以通过一个注解Data完成一个实体类对应的gettersettertoStringequalshashCode等操作的快速添加
import lombok.Data;
Data
public class Book {private Integer id;private String type;private String name;private String description;
} 到这里实体类就做好了是不是比不使用lombok简化好多这种工具在Java开发中还有N多后面课程中遇到了能用的东西时在不增加各位小伙伴大量的学习时间的情况下尽量多给大家介绍一些
总结
实体类制作使用lombok简化开发 导入lombok无需指定版本由SpringBoot提供版本Data注解
2.数据层开发——基础CRUD
数据层开发本次使用MyBatisPlus技术数据源使用前面学习的Druid学都学了都用上
步骤①导入MyBatisPlus与Druid对应的starter当然mysql的驱动不能少
dependenciesdependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.3/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.6/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependency
/dependencies步骤②配置数据库连接相关的数据源配置
server:port: 80spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTCusername: rootpassword: root步骤③使用MP的标准通用接口BaseMapper加速开发别忘了Mapper和泛型的指定
Mapper
public interface BookDao extends BaseMapperBook {
}步骤④制作测试类测试结果这个测试类制作是个好习惯不过在企业开发中往往都为加速开发跳过此步且行且珍惜吧
SpringBootTest
public class BookDaoTestCase {Autowiredprivate BookDao bookDao;Testvoid testGetById(){System.out.println(bookDao.selectById(1));}Testvoid testSave(){Book book new Book();book.setType(测试数据123);book.setName(测试数据123);book.setDescription(测试数据123);bookDao.insert(book);}Testvoid testUpdate(){Book book new Book();book.setId(17);book.setType(测试数据abcdefg);book.setName(测试数据123);book.setDescription(测试数据123);bookDao.updateById(book);}Testvoid testDelete(){bookDao.deleteById(16);}Testvoid testGetAll(){bookDao.selectList(null);}
}温馨提示
MP技术默认的主键生成策略为雪花算法生成的主键ID长度较大和目前的数据库设定规则不相符需要配置一下使MP使用数据库的主键生成策略方式嘛还是老一套做配置。在application.yml中添加对应配置即可具体如下
server:port: 80spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTCusername: rootpassword: rootmybatis-plus:global-config:db-config:table-prefix: tbl_ #设置表名通用前缀id-type: auto #设置主键id字段的生成策略为参照数据库设定的策略当前数据库设置id生成策略为自增查看MP运行日志
在进行数据层测试的时候因为基础的CRUD操作均由MP给我们提供了所以就出现了一个局面开发者不需要书写SQL语句了这样程序运行的时候总有一种感觉一切的一切都是黑盒的作为开发者我们啥也不知道就完了。如果程序正常运行还好如果报错了这个时候就很崩溃你甚至都不知道从何下手因为传递参数、封装SQL语句这些操作完全不是你干预开发出来的所以查看执行期运行的SQL语句就成为当务之急。
SpringBoot整合MP的时候充分考虑到了这点通过配置的形式就可以查阅执行期SQL语句配置如下
mybatis-plus:global-config:db-config:table-prefix: tbl_id-type: autoconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 再来看运行结果此时就显示了运行期执行SQL的情况。
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession2c9a6717] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl6ca30b8a] will not be managed by SpringPreparing: SELECT id,type,name,description FROM tbl_bookParameters: Columns: id, type, name, descriptionRow: 1, 计算机理论, Spring实战 第5版, Spring入门经典教程深入理解Spring原理技术内幕Row: 2, 计算机理论, Spring 5核心原理与30个类手写实战, 十年沉淀之作手写Spring精华思想Row: 3, 计算机理论, Spring 5 设计模式, 深入Spring源码剖析Spring源码中蕴含的10大设计模式Row: 4, 计算机理论, Spring MVCMyBatis开发从入门到项目实战, 全方位解析面向Web应用的轻量级框架带你成为Spring MVC开发高手Row: 5, 计算机理论, 轻量级Java Web企业应用实战, 源码级剖析Spring框架适合已掌握Java基础的读者Row: 6, 计算机理论, Java核心技术 卷I 基础知识原书第11版, Core Java 第11版Jolt大奖获奖作品针对Java SE9、10、11全面更新Row: 7, 计算机理论, 深入理解Java虚拟机, 5个维度全面剖析JVM大厂面试知识点全覆盖Row: 8, 计算机理论, Java编程思想第4版, Java学习必读经典,殿堂级著作赢得了全球程序员的广泛赞誉Row: 9, 计算机理论, 零基础学Java全彩版, 零基础自学编程的入门图书由浅入深详解Java语言的编程思想和核心技术Row: 10, 市场营销, 直播就该这么做主播高效沟通实战指南, 李子柒、李佳琦、薇娅成长为网红的秘密都在书中Row: 11, 市场营销, 直播销讲实战一本通, 和秋叶一起学系列网络营销书籍Row: 12, 市场营销, 直播带货淘宝、天猫直播从新手到高手, 一本教你如何玩转直播的书10堂课轻松实现带货月入3WRow: 13, 测试类型, 测试数据, 测试描述数据Row: 14, 测试数据update, 测试数据update, 测试数据updateRow: 15, -----------------, 测试数据123, 测试数据123Total: 15 其中清晰的标注了当前执行的SQL语句是什么携带了什么参数对应的执行结果是什么所有信息应有尽有。
此处设置的是日志的显示形式当前配置的是控制台输出当然还可以由更多的选择根据需求切换即可 总结 手工导入starter坐标2个mysql驱动1个 配置数据源与MyBatisPlus对应的配置 开发Dao接口继承BaseMapper 制作测试类测试Dao功能是否有效 使用配置方式开启日志设置日志输出方式为标准输出即可查阅SQL执行日志
3.数据层开发——分页功能制作
前面仅仅是使用了MP提供的基础CRUD功能实际上MP给我们提供了几乎所有的基础操作这一节说一下如果实现数据库端的分页操作
MP提供的分页操作API如下
Test
void testGetPage(){IPage page new Page(2,5);bookDao.selectPage(page, null);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());
} 其中selectPage方法需要传入一个封装分页数据的对象可以通过new的形式创建这个对象当然这个对象也是MP提供的别选错包了。创建此对象时就需要指定分页的两个基本数据
当前显示第几页每页显示几条数据
可以通过创建Page对象时利用构造方法初始化这两个数据
IPage page new Page(2,5); 将该对象传入到查询方法selectPage后可以得到查询结果但是我们会发现当前操作查询结果返回值仍然是一个IPage对象这又是怎么回事
IPage page bookDao.selectPage(page, null); 原来这个IPage对象中封装了若干个数据而查询的结果作为IPage对象封装的一个数据存在的可以理解为查询结果得到后又塞到了这个IPage对象中其实还是为了高度的封装一个IPage描述了分页所有的信息。下面5个操作就是IPage对象中封装的所有信息了
Test
void testGetPage(){IPage page new Page(2,5);bookDao.selectPage(page, null);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的内部机制。
对于MySQL的分页操作使用limit关键字进行而并不是所有的数据库都使用limit关键字实现的这个时候MP为了制作的兼容性强将分页操作设置为基础查询操作的升级版你可以理解为IPhone6与IPhone6S-PLUS的关系。
基础操作中有查询全部的功能而在这个基础上只需要升级一下PLUS就可以得到分页操作。所以MP将分页操作做成了一个开关你用分页功能就把开关开启不用就不需要开启这个开关。而我们现在没有开启这个开关所以分页操作是没有的。这个开关是通过MP的拦截器的形式存在的其中的原理这里不分析了有兴趣的小伙伴可以学习MyBatisPlus这门课程进行详细解读。具体设置方式如下
定义MP拦截器并将其设置为Spring管控的bean
Configuration
public class MPConfig {Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
} 上述代码第一行是创建MP的拦截器栈这个时候拦截器栈中没有具体的拦截器第二行是初始化了分页拦截器并添加到拦截器栈中。如果后期开发其他功能需要添加全新的拦截器按照第二行的格式继续add进去新的拦截器就可以了。
总结
使用IPage封装分页数据分页操作依赖MyBatisPlus分页拦截器实现功能借助MyBatisPlus日志查阅执行SQL语句
4.数据层开发——条件查询功能制作
除了分页功能MP还提供有强大的条件查询功能。以往我们写条件查询要自己动态拼写复杂的SQL语句现在简单了MP将这些操作都制作成API接口调用一个又一个的方法就可以实现各种套件的拼装。这里给大家普及一下基本格式详细的操作还是到MP的课程中查阅吧
下面的操作就是执行一个模糊匹配对应的操作由like条件书写变为了like方法的调用
Test
void testGetBy(){QueryWrapperBook qw new QueryWrapper();qw.like(name,Spring);bookDao.selectList(qw);
} 其中第一句QueryWrapper对象是一个用于封装查询条件的对象该对象可以动态使用API调用的方法添加条件最终转化成对应的SQL语句。第二句就是一个条件了需要什么条件使用QueryWapper对象直接调用对应操作即可。比如做大于小于关系就可以使用lt或gt方法等于使用eq方法等等此处不做更多的解释了。
这组API使用还是比较简单的但是关于属性字段名的书写存在着安全隐患比如查询字段name当前是以字符串的形态书写的万一写错编译器还没有办法发现只能将问题抛到运行器通过异常堆栈告诉开发者不太友好。
MP针对字段检查进行了功能升级全面支持Lambda表达式就有了下面这组API。由QueryWrapper对象升级为LambdaQueryWrapper对象这下就变了上述问题的出现
Test
void testGetBy2(){String name 1;LambdaQueryWrapperBook lqw new LambdaQueryWrapperBook();lqw.like(Book::getName,name);bookDao.selectList(lqw);
} 为了便于开发者动态拼写SQL防止将null数据作为条件使用MP还提供了动态拼装SQL的快捷书写方式
Test
void testGetBy2(){String name 1;LambdaQueryWrapperBook lqw new LambdaQueryWrapperBook();//if(name ! null) lqw.like(Book::getName,name); //方式一JAVA代码控制lqw.like(name ! null,Book::getName,name); //方式二API接口提供控制开关bookDao.selectList(lqw);
} 其实就是个格式没有区别。关于MP的基础操作就说到这里吧如果这一块知识不太熟悉的小伙伴还是去完整的学习一下MP的知识吧这里只是蜻蜓点水的用了几个操作而已。
总结 使用QueryWrapper对象封装查询条件 推荐使用LambdaQueryWrapper对象 所有查询操作封装成方法调用 查询条件支持动态条件拼装
5.业务层开发
数据层开发告一段落下面进行业务层开发其实标准业务层开发很多初学者认为就是调用数据层怎么说呢这个理解是没有大问题的更精准的说法应该是组织业务逻辑功能并根据业务需求对数据持久层发起调用。有什么差别呢目标是为了组织出符合需求的业务逻辑功能至于调不调用数据层还真不好说有需求就调用没有需求就不调用。
一个常识性的知识普及一下业务层的方法名定义一定要与业务有关例如登录操作
login(String username,String password); 而数据层的方法名定义一定与业务无关是一定不是可能也不是有可能例如根据用户名密码查询
selectByUserNameAndPassword(String username,String password); 我们在开发的时候是可以根据完成的工作不同划分成不同职能的开发团队的。比如一个哥们制作数据层他就可以不知道业务是什么样子拿到的需求文档要求可能是这样的
接口传入用户名与密码字段查询出对应结果结果是单条数据
接口传入ID字段查询出对应结果结果是单条数据
接口传入离职字段查询出对应结果结果是多条数据 但是进行业务功能开发的哥们拿到的需求文档要求差别就很大
接口传入用户名与密码字段对用户名字段做长度校验4-15位对密码字段做长度校验8到24位对喵喵喵字段做特殊字符校验不允许存在空格查询结果为对象。如果为null返回BusinessException封装消息码INFO_LOGON_USERNAME_PASSWORD_ERROR 你比较一下能是一回事吗差别太大了所以说业务层方法定义与数据层方法定义差异化很大只不过有些入门级的开发者手懒或者没有使用过公司相关的ISO标准化文档而已。
多余的话不说了咱们做案例就简单制作了业务层接口定义如下
public interface BookService {Boolean save(Book book);Boolean update(Book book);Boolean delete(Integer id);Book getById(Integer id);ListBook getAll();IPageBook getPage(int currentPage,int pageSize);
} 业务层实现类如下转调数据层即可
Service
public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;Overridepublic Boolean save(Book book) {return bookDao.insert(book) 0;}Overridepublic Boolean update(Book book) {return bookDao.updateById(book) 0;}Overridepublic Boolean delete(Integer id) {return bookDao.deleteById(id) 0;}Overridepublic Book getById(Integer id) {return bookDao.selectById(id);}Overridepublic ListBook getAll() {return bookDao.selectList(null);}Overridepublic IPageBook getPage(int currentPage, int pageSize) {IPage page new Page(currentPage,pageSize);bookDao.selectPage(page,null);return page;}
} 别忘了对业务层接口进行测试测试类如下
SpringBootTest
public class BookServiceTest {Autowiredprivate IBookService bookService;Testvoid testGetById(){System.out.println(bookService.getById(4));}Testvoid testSave(){Book book new Book();book.setType(测试数据123);book.setName(测试数据123);book.setDescription(测试数据123);bookService.save(book);}Testvoid testUpdate(){Book book new Book();book.setId(17);book.setType(-----------------);book.setName(测试数据123);book.setDescription(测试数据123);bookService.updateById(book);}Testvoid testDelete(){bookService.removeById(18);}Testvoid testGetAll(){bookService.list();}Testvoid testGetPage(){IPageBook page new PageBook(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());}}总结
Service接口名称定义成业务名称并与Dao接口名称进行区分制作测试类测试Service功能是否有效
业务层快速开发
其实MP技术不仅提供了数据层快速开发方案业务层MP也给了一个通用接口个人观点不推荐使用凑合能用吧其实就是一个封装继承的思想代码给出实际开发慎用
业务层接口快速开发
public interface IBookService extends IServiceBook {//添加非通用操作API接口
} 业务层接口实现类快速开发关注继承的类需要传入两个泛型一个是数据层接口另一个是实体类
Service
public class BookServiceImpl extends ServiceImplBookDao, Book implements IBookService {Autowiredprivate BookDao bookDao;//添加非通用操作API
} 如果感觉MP提供的功能不足以支撑你的使用需要其实是一定不能支撑的因为需求不可能是通用的在原始接口基础上接着定义新的API接口就行了此处不再说太多了就是自定义自己的操作了但是不要和已有的API接口名冲突即可。
总结
使用通用接口ISerivce快速开发Service使用通用实现类ServiceImplM,T快速开发ServiceImpl可以在通用接口基础上做功能重载或功能追加注意重载时不要覆盖原始操作避免原始提供的功能丢失
6.表现层开发
终于做到表现层了做了这么多都是基础工作。其实你现在回头看看哪里还有什么SpringBoot的影子前面1,2步就搞完了。继续完成表现层制作吧咱们表现层的开发使用基于Restful的表现层接口开发功能测试通过Postman工具进行
表现层接口如下:
RestController
RequestMapping(/books)
public class BookController2 {Autowiredprivate IBookService bookService;GetMappingpublic ListBook getAll(){return bookService.list();}PostMappingpublic Boolean save(RequestBody Book book){return bookService.save(book);}PutMappingpublic 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 IPageBook getPage(PathVariable int currentPage,PathVariable int pageSize){return bookService.getPage(currentPage,pageSize, null);}
} 在实用Postman测试时关注提交类型对应上即可不然就会报405的错误码了
普通GET请求
PUT请求传递json数据后台实用RequestBody接收数据 GET请求传递路径变量后台实用PathVariable接收数据
总结
基于Restful制作表现层接口 新增POST删除DELETE修改PUT查询GET 接收参数 实体数据RequestBody路径变量PathVariable
7.表现层消息一致性处理
目前我们通过Postman测试后业务层接口功能时通的但是这样的结果给到前端开发者会出现一个小问题。不同的操作结果所展示的数据格式差异化严重
增删改操作结果
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入门经典教程}
} 表现层开发格式也需要转换一下 结果这么一折腾全格式统一现在后端发送给前端的数据格式就统一了免去了不少前端解析数据的麻烦。
总结 设计统一的返回值结果类型便于前端开发读取数据 返回值结果类型可以根据需求自行设定没有固定格式 返回值结果模型类用于后端与前端进行数据格式统一也称为前后端数据协议
8.前后端联通性测试
后端的表现层接口开发完毕就可以进行前端的开发了。
将前端人员开发的页面保存到lresources目录下的static目录中建议执行maven的clean生命周期避免缓存的问题出现。 在进行具体的功能开发之前先做联通性的测试通过页面发送异步提交axios这一步调试通过后再进行进一步的功能开发
//列表
getAll() {axios.get(/books).then((res){console.log(res.data);});
}, 只要后台代码能够正常工作前端能够在日志中接收到数据就证明前后端是通的也就可以进行下一步的功能开发了
总结
单体项目中页面放置在resources/static目录下created钩子函数用于初始化页面时发起调用页面使用axios发送异步请求获取数据后确认前后端是否联通
9.页面基础功能开发
9.1 列表功能非分页版
列表功能主要操作就是加载完数据将数据展示到页面上此处要利用VUE的数据模型绑定发送请求得到数据然后页面上读取指定数据即可
页面数据模型定义
data:{dataList: [],//当前页要展示的列表数据...
}, 异步请求获取数据
//列表
getAll() {axios.get(/books).then((res){this.dataList res.data.data;});
}, 这样在页面加载时就可以获取到数据并且由VUE将数据展示到页面上了
总结
将查询数据返回到页面利用前端数据绑定进行数据展示
9.2 添加功能
添加功能用于收集数据的表单是通过一个弹窗展示的因此在添加操作前首先要进行弹窗的展示添加后隐藏弹窗即可。因为这个弹窗一直存在因此当页面加载时首先设置这个弹窗为不可显示状态需要展示切换状态即可
默认状态
data:{dialogFormVisible: false,//添加表单是否可见...
}, 切换为显示状态
//弹出添加窗口
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();});
},将要保存的数据传递到后台通过post请求的第二个参数传递json数据到后台根据返回的操作结果决定下一步操作 如何是true就关闭添加窗口显示添加成功的消息如果是false保留添加窗口显示添加失败的消息 无论添加是否成功页面均进行刷新动态加载数据对getAll操作发起调用
取消添加操作
//取消
cancel(){this.dialogFormVisible false;this.$message.info(操作取消);
},总结
请求方式使用POST调用后台对应操作添加操作结束后动态刷新页面加载数据根据操作结果不同显示对应的提示信息弹出添加Div时清除表单数据
9.3 删除功能
模仿添加操作制作删除功能差别之处在于删除操作仅传递一个待删除的数据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(取消删除操作);});
} 总结
请求方式使用Delete调用后台对应操作删除操作需要传递当前行数据对应的id值到后台删除操作结束后动态刷新页面加载数据根据操作结果不同显示对应的提示信息删除操作前弹出提示框避免误操作
9.4 修改功能
修改功能可以说是列表功能、删除功能与添加功能的合体。几个相似点如下 页面也需要有一个弹窗用来加载修改的数据这一点与添加相同都是要弹窗 弹出窗口中要加载待修改的数据而数据需要通过查询得到这一点与查询全部相同都是要查数据 查询操作需要将要修改的数据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();});
},总结
加载要修改数据通过传递当前行数据对应的id值到后台查询数据同删除与查询全部利用前端双向数据绑定将查询到的数据进行回显同查询全部请求方式使用PUT调用后台对应操作同新增传递数据修改操作结束后动态刷新页面加载数据同新增根据操作结果不同显示对应的提示信息同新增
10.业务消息一致性处理
目前的功能制作基本上达成了正常使用的情况什么叫正常使用呢也就是这个程序不出BUG如果我们搞一个BUG出来你会发现程序马上崩溃掉。比如后台手工抛出一个异常看看前端接收到的数据什么样子
{timestamp: 2021-09-15T03:27:31.03800: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();});
},总结
使用注解RestControllerAdvice定义SpringMVC异常处理器用来处理异常的异常处理器必须被扫描加载否则无法生效表现层返回结果的模型类中添加消息属性用来传递消息到页面
11.页面功能开发
11.1 分页功能
分页功能的制作用于替换前面的查询全部其中要使用到elementUI提供的分页组件
!--分页组件--
div classpagination-containerel-paginationclasspagiantioncurrent-changehandleCurrentChange:current-pagepagination.currentPage:page-sizepagination.pageSizelayouttotal, prev, pager, next, jumper:totalpagination.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){IPageBook 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();
},总结
使用el分页组件定义分页组件绑定的数据模型异步调用获取分页数据分页数据页面回显
11.2 删除功能维护
由于使用了分页功能当最后一页只有一条数据时删除操作就会出现BUG最后一页无数据但是独立展示对分页查询功能进行后台功能维护如果当前页码值大于最大页码值重新执行查询。其实这个问题解决方案很多这里给出比较简单的一种处理方案
GetMapping({currentPage}/{pageSize})
public R getPage(PathVariable int currentPage,PathVariable int pageSize){IPageBook page bookService.getPage(currentPage, pageSize);//如果当前页码值大于了总页码值那么重新执行查询操作使用最大页码值作为当前页码值if( currentPage page.getPages()){page bookService.getPage((int)page.getPages(), pageSize);}return new R(true, page);
}11.3 条件查询功能
最后一个功能来做条件查询其实条件查询可以理解为分页查询的时候除了携带分页数据再多带几个数据的查询。这些多带的数据就是查询条件。比较一下不带条件的分页查询与带条件的分页查询差别之处这个功能就好做了 页面封装的数据带不带条件影响的仅仅是一次性传递到后台的数据总量由传递2个分页相关的数据转换成2个分页数据加若干个条件 后台查询功能查询时由不带条件转换成带条件反正不带条件的时候查询条件对象使用的是null现在换成具体条件差别不大 查询结果不管带不带条件出来的数据只是有数量上的差别其他都差别这个可以忽略 经过上述分析看来需要在页面发送请求的格式方面做一定的修改后台的调用数据层操作时发送修改其他没有区别 页面发送请求时两个分页数据仍然使用路径变量其他条件采用动态拼装url参数的形式传递 页面封装查询条件字段 pagination: {
//分页相关模型数据currentPage: 1, //当前页码pageSize:10, //每页显示的记录数total:0, //总记录数name: ,type: ,description:
},页面添加查询条件字段对应的数据模型绑定名称 div classfilter-containerel-input placeholder图书类别 v-modelpagination.type classfilter-item/el-input placeholder图书名称 v-modelpagination.name classfilter-item/el-input placeholder图书描述 v-modelpagination.description classfilter-item/el-button clickgetAll() classdalfBut查询/el-buttonel-button typeprimary classbutT clickhandleCreate()新建/el-button
/div将查询条件组织成url参数添加到请求url地址中这里可以借助其他类库快速开发当前使用手工形式拼接降低学习要求 getAll() {//1.获取查询条件,拼接查询条件param ?namethis.pagination.name;param typethis.pagination.type;param descriptionthis.pagination.description;console.log(----------------- param);axios.get(/books/this.pagination.currentPage/this.pagination.pageSizeparam).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);IPageBook pageBook bookService.getPage(currentPage,pageSize);return new R(null ! pageBook ,pageBook);
}对应业务层接口与实现类进行修正 public interface IBookService extends IServiceBook {IPageBook getPage(Integer currentPage,Integer pageSize,Book queryBook);
}Service
public class BookServiceImpl2 extends ServiceImplBookDao,Book implements IBookService {public IPageBook getPage(Integer currentPage,Integer pageSize,Book queryBook){IPage page new Page(currentPage,pageSize);LambdaQueryWrapperBook lqw new LambdaQueryWrapperBook();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 ?namethis.pagination.name;param typethis.pagination.type;param descriptionthis.pagination.description;console.log(----------------- param);axios.get(/books/this.pagination.currentPage/this.pagination.pageSizeparam).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;});
},总结
定义查询条件数据模型当前封装到分页数据模型中异步调用分页功能并通过请求参数传递数据到后台
往期专栏Java全栈开发数据结构与算法计算机组成原理操作系统数据库系统物联网控制原理与技术