开山篇
- 了解 基于springboot官网创建项目,并导入运行 https://spring.io/projects/spring-boot
通过idea联网版创建springboot项目,web项目,并启动注意 parent�使用2.7.1
基于阿里云创建项目 https://start.aliyun.com
- 手动创建SpringBoot项目,创建maven工程并导入springboot依赖,创建引导类和处理器启动即可
- 文件隐藏
解析spring-boot-starter-parent,主要使用来做统一的jar包版本管理,版本仲裁
开发SpringBoot程序要继承spring-boot-starter-parent
2. spring-boot-starter-parent中定义了若干个依赖管理
3. 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
4. 继承parent的形式也可以采用引入依赖的形式实现效果- 解析starter,快速启动器
spring-boot-starter-xxx
按照功能不同会有不同功能的starter,我们可以把starter理解成一个集成了多个依赖的pom文件是一个封装好的依赖集合。
1. 开发SpringBoot程序需要导入坐标时通常导入对应的starter
2. 每个不同的starter根据功能不同,通常包含多个依赖坐标
3. 使用starter可以实现快速配置的效果,达到简化配置的目的
- 解析starter,快速启动器
parent的目的是避免版本冲突,starter的目的是简化依赖配置
8.解析引导类,其实就是通过SpringApplication.run(Application.class, args);启动容器,扫描引导类所在包加载bean
9.解析内嵌服务器,tomcat,jetty
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<exclusions>
<!-- 去除Tomcat容器 -->
<exclusion>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
</exclusion>
</exclusions>
</dependency>
<!-- 增加Jetty容器 -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-jetty</artifactid>
</dependency>
</dependencies>
配置
1.复制模块,因为练习需要大量的模块,所以复制工程
原则
保留工程基础结构
抹掉原始工程痕迹
1. 在工作空间中复制对应工程,并修改工程名称
2. 删除与Idea相关配置文件,仅保留src目录与pom.xml文件
3. 修改pom.xml文件中的artifactId与新工程/模块名相同
4. 删除name标签(可选)
5. 保留备份工程供后期使用
2.基础配置
properties配置和yaml的使用方式.wmv
使用properties的方式修改端口,修改banner,日志,缺点是代码可读性低,配置冗长
然后引入yml,yaml的方式配置,以及三种配置并存的优先级:properties > yml > yaml
以及yaml提示不生效的原因:是因为idea不认为他是一个配置文件,在项目结构中进行配置即可
b31477f1bac957aaa9bce1f099dc2b86.rar
3.yml的使用
yaml数据格式
yml中的数据格式.wmv
YAML(YAML Ain’t Markup Language),一种数据序列化格式
优点:
容易阅读
容易与脚本语言交互
以数据为核心,重数据轻格式
YAML文件扩展名
.yml(主流)
.yaml
yaml语法规则
大小写敏感属性
层级关系使用多行描述,每行结尾使用冒号结束
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
# 表示注释
核心规则:数据前面要加空格与冒号隔开
#单一属性
name: gouwa
age: 18
#对象
user:
name: goudan
age: 18
#数组
like:
- 抽烟
- 喝酒
- 烫头
#数组缩略写法
like2: [抽烟,喝酒,烫头]
#对象数组
users:
- name: gousheng
age: 18
- name: goudan
age: 20
#对象数组 格式2
users2:
-
name: gousheng
age: 18
-
name: goudan
age: 20
#对象数组缩略格式
user3: [{name: gouwa,age: 18},{name: goudan,age: 18}]
读取数据
yaml属性的读取方式.wmv
使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
1. 使用@Value配合SpEL读取单个数据
2. 如果数据存在多层级,依次书写层级名称即可
@Value("${name}")
private String name;
@Value("${user.age}")
private String age;
@Value("${like[1]}")
private String like;
@Value("${users[1].name}")
private String name2;
@RequestMapping("/value")
public String getValue(){
return "name:"+name+",age:"+age+",like:"+like+",name2:"+name2;
}
引用变量
center:
dataDir: /usr/local/fire/data
tmpDir: /usr/local/fire/tmp
logDir: /usr/local/fire/log
msgDir: /usr/local/fire/msgDir
如上👆🏻:如果配置中值出现大量重复的内容,则可以把他们提取成一个变量进行引用,如下:↓
basedir: /usr/local/fire
center:
dataDir: ${basedir}/data
tmpDir: ${basedir}/tmp
logDir: ${basedir}/log
msgDir: ${basedir}/msgDir
�如果出现转义字符可以使用双引号包裹进行解析
转义字符的解析方式.wmv
basedir: /usr/local/fire
center:
dataDir: ${basedir}/data
tmpDir: "${basedir}/tmp \t1 \t2 \t3"
logDir: ${basedir}/log
msgDir: ${basedir}/msgDir
@Value("${center.tmpDir}")
private String tmpDir;
@RequestMapping("/value")
public String getValue(){
System.out.println("tmpDir:"+tmpDir);
return "name:"+name+",age:"+age+",like:"+like+",name2:"+name2;
}
�
读取yaml全部属性数据
目前来说上一章引用变量的方式存在明显的痛点,因为如果想要引用一个属性就需要创建一个变量,随着属性的增多代码量也会增多
可以 1. 使用Environment对象封装全部配置信息 2. 使用@Autowired自动装配数据到Environment对象中
Environment.wmv
@Autowired
private Environment en;
@RequestMapping("/en")
public String getEn(){
System.out.println(en.getProperty("user.name"));
System.out.println(en.getProperty("like[0]"));
System.out.println(en.getProperty("user3[0].name"));
return null;
}
� :::info 确实解决了变量过多的问题,但是我们会发现用起来其实并不简单,在实际的开发中也是SpringBoot内部引入配置属性的主流方式其实是把一组数据封装到一个对象中,诸君请往下看↓ :::
重点自定义对象封装指定数据 主流方式
1.定义一组数据
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xuezhi?serverTimezone=UTC
username: root
password: root
�2.创建一个类,用于封装上面的这组数据
@ConfigurationProperties.wmv
:::info
1.使用@ConfigurationProperties注解指定前缀绑定配置信息到封装类中,注意数据的名字要和类的属性名一致
2.封装类需要定义为Spring管理的bean(@Component),否则无法进行属性注入
:::
package com.xuezhi.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
// 定义数据模型封装yaml中的对应数据
// 定义为让Spring管控的Bean
@Component
// 指定加载的数据
@ConfigurationProperties(prefix = "datasource")
public class DataSource {
private String driverClassName;
private String url;
private String userName;
private String password;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DataSource{" +
"driverClassName='" + driverClassName + '\'' +
", url='" + url + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
�测试:
@Autowired
private DataSource dataSource;
@RequestMapping("/db")
public String getDb(){
System.out.println(dataSource);
return null;
}
:::info
总结:
1.我们要想封装局部数据,首先需要提供一组对象格式的数据
2.然后提供一个用来封装的模型类,这个模型类通过@ConfigurationProperties注解指定封装的是那一组数据,把前缀告诉他就行了
3.我们的数据是由springboot读取的,那么模型类也需要交给Spring管控,使用@Component注解SpringBoot会自动把模型类扫描进Spring容器
这种数据封装的方式应用场景非常多,后面我们会定义格式各样的数据,交给SpringBoot框架中的技术使用,这些技术本身有一些配置信息,我们想改就可以把这些配置信息数据定义出来,他就会读取成为一个对象去使用,这也是为什么我们在yaml中配置一个东西在一些技术上就生效了的原因。其实就是通过这种形式把我们定义的一组一组的配置,加载成了一个又一个的对象,提供给一个又一个的技术去使用的。
:::
整合第三方技术
我们要学习的是整合思想,其中整合JUnit是最简单的整合方式,通过学习这四种技术的整合方式,我们一通百通
整合JUnit
整合Junit.wmv
直接创建一个maven工程进行测试即可,测试所需的所有东西都自动配置好了
什么技术都不需要勾选,直接点击下一步
创建用于测试的内容
在测试类中,通过自动装配注入测试对象,进行测试即可
:::info
springboot整合jUnit主要是通过@SpringBootTest注解,比起原始Spring的方式方便很多,具体步骤如下:
1. 导入测试对应的starter
2. 测试类使用@SpringBootTest修饰
3. 使用自动装配的形式添加要测试的对象
:::
:::info
注意:
1. 测试类如果存在于引导类所在包或子包中无需指定引导类
2. 测试类如果不存在于引导类所在的包或子包中需要通过classes 属性指定引导类
:::
比如我们把测试类提到com包下,而不放在和引导类所在的com.xuezhi包下
那么这时他就会抛出异常:
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
这个异常的意思是在当前包下没有找到使用@SpringBootConfiguration修饰的类(其实就是引导类,引导类使用了@SpringBootConfiguration修饰),我们的Spring容器要通过启动引导类生成,找不到Spring容器,就导致了自动装配失败,所以报错,异常信息往后看可以看到它提示我们使用@ContextConfiguration或者@SpringBootTest(classes=…)指定引导类解决该问题。那么我们只需要如下:
:::info
通过@SpringBootTest的classes属性手动指定引导类,从而加载Spring容器
:::
:::info
或者如原始Spring一样使用@ContextConfiguration手动指定引导类,从而加载Spring容器
:::
这两种方式都可以,产生这个问题的原因在于,测试类如果不存在于引导类所在的包或子包中则无法找到Spring容器从而导致了自动装配失败
整合MyBatis
整合MyBatis.wmv
:::info
知识加油站:@Mapper
如果Mapper.xml与Mapper.class在同一个包下且同名,spring的Mapper组件扫描器扫描Mapper.class的同时会自动扫描同名的Mapper.xml并装配到Mapper.class。
如果Mapper.xml与Mapper.class不在同一个包下或者不同名,就必须使用配置mapperLocations指定mapper.xml的位置。而使用了@Mapper注解的类采用注解开发不需要写xml映射文件,并会生成动态代理类交Mapper组件扫描器扫描即可
@Mapper是由Mybatis框架中定义的一个描述数据层接口的注解,注解往往起到的都是一个描述性作用,目的就是为了不再写mapper映射文件并被Spring容器识别到并产生自动代理的对象,并将其实现类对象存储到spring容器中。
@Mapper使用MyBatis或者搭配原始Spring去写的话是可以省略的,但是使用SpringBoot是必须要写的
搭配SpringBoot使用,@Mapper会告诉spring框架的此接口的实现类由Mybatis负责创建,并将其实现类对象存储到spring容器中。
:::
整合MyBatis只要创建一个SpringBoot项目并勾选相对应的技术栈即可
选择对应的技术栈
idea会自动帮我们导入MyBatis-spring-boot-starter的启动器,自动帮我们配置好整合的配置,我们只需要指定数据源即可
在yaml文件中配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: xzjyroot
pojo:
package com.xuezhi.pojo;
import lombok.Data;
import lombok.ToString;
import java.time.LocalDateTime;
@Data
@ToString
public class Items {
private Integer id;
private String name;
private double price;
private String detail;
private String pic;
private LocalDateTime createtime;
}
�mapper接口:
package com.xuezhi.mapper;
import com.xuezhi.pojo.Items;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ItemsMapper {
@Select("select * from items")
List<Items> getItems();
}
测试:
package com.xuezhi;
import com.xuezhi.mapper.ItemsMapper;
import com.xuezhi.pojo.Items;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class SpringBoot05MyBatisApplicationTests {
@Autowired
private ItemsMapper itemsMapper;
@Test
void contextLoads() {
List<Items> items = itemsMapper.getItems();
System.out.println(items);
}
}
2.4.1,2.3.9版本SpringBoot会出现时区和驱动过时问题
1. MySQL 8.X驱动强制要求设置时区
修改url,添加serverTimezone设定
修改MySQL数据库配置(略)
2. 驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
:::info
总结:1. 勾选MyBatis技术,也就是导入MyBatis对应的starter
2. 数据库连接相关信息转换成配置
3. 数据库SQL映射需要添加@Mapper被容器识别到
:::
整合MyBatis-Plus
整合MyBatisPllus.wmv :::info mp是国人开发的技术,在SpringBoot中并没有收录启动器,所以我们可以去maven的仓库自己去查找启动器的坐标,整合方式和MyBatis大同小异 :::
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
:::info
总结:
1.导入对应的starter
2.数据层使用BaseMapper简化开发
3.配置数据源
4.测试
由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
需要使用的第三方技术无法通过勾选确定时,需要手工添加坐标
:::
整合Druid
整合Druid.wmv
:::info
SpringBoot有默认的数据源(HikariDataSource),如果想用第三方的数据源比如Druid可以导入相应的starter,然后配置数据源即可。
:::
数据源主要是搭配数据库技术使用的所以导入MyBatis或MyBatisPlus的启动器
导入start
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.17</version>
</dependency>
�配置数据源有两种方式,推荐第一种
#第一种
#spring:
# datasource:
# druid:
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis
# username: root
# password: xzjyroot
#第二种
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: xzjyroot
type: com.alibaba.druid.pool.DruidDataSource
:::info
总结:
1. 整合Druid需要导入Druid对应的starter
2. 根据Druid提供的配置方式进行配置
整合第三方技术通用方式
导入对应技术的starter
根据提供的配置格式,配置非默认值对应的配置项
:::
使用SpringBoot整合SSMP案例
实体类开发————使用Lombok快速制作实体类
Dao开发————整合MyBatisPlus,制作数据层测试类
Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
Controller开发————基于Restful开发,使用PostMan测试接口功能
Controller开发————前后端开发协议制作
页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
列表、新增、修改、删除、分页、查询
项目异常处理
按条件查询————页面功能调整、Controller修正功能、Service修正功能
环境准备
:::info 数据库脚本 :::
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;
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 MVC+MyBatis开发从入门到项目实战', '全方位解析面向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+');
:::info pojo :::
package com.xuezhi.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
:::info 创建模块,导入启动器 :::
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xuezhi</groupId>
<artifactId>SpringBoot_08_SSMP</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.1</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
:::info yml :::
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: xzjyroot
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
global-config:
db-config:
# 表前缀
table-prefix: tbl_
# 日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
:::info 配置分页拦截器 :::
package com.xuezhi.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
:::info 测试分页查询 :::
@Test
public void test02(){
Page<Book> bookPage = new Page<Book>(1, 5);
Page<Book> bookPage1 = bookMapper.selectPage(bookPage, null);
System.out.println(bookPage1.getRecords());
}
页面
放到static目录下
页面.zip
访问pages/books.html
统一返回结果集
package com.xuezhi.pojo;
import lombok.Data;
@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,Object data,String msg){
this.flag = flag;
this.data = data;
this.msg = msg;
}
public R(Boolean flag,String msg){
this.flag = flag;
this.msg = msg;
}
public R(String msg){
this.flag = false;
this.msg = msg;
}
}
:::info
- 设计统一的返回值结果类型便于前端开发读取数据
2. 返回值结果类型可以根据需求自行设定,没有固定格式
3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议 :::查询列表
列表展现.wmv :::info dataList:里面放入当前页要展示的列表数据 ::: :::info 让钩子函数,在Vue对象初始化之后自动调用getAll方法,getAll使用axios发送get请求,调用then函数处理结果集,把查询出的列表数据放入到 Vue对象的dataList中,将查询数据返回到页面,利用前端数据双向绑定进行数据展示 :::增删改
数据新增.wmv
删除操作.wmv
修改功能.wmv :::info 增删改Controller ::: ```java package com.xuezhi.controller;
import com.xuezhi.pojo.Book; import com.xuezhi.pojo.R; import com.xuezhi.service.BooksService; import org.apache.ibatis.annotations.Delete; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*;
import java.sql.SQLException;
@RestController @RequestMapping(“/books”) public class BooksController { @Autowired private BooksService booksService; @GetMapping public R getAll(){ return new R(true,booksService.list()); }
@GetMapping("/{id}")
public R getById(@PathVariable Integer id){
return new R(true,booksService.getById(id));
}
@PostMapping
public R save(@RequestBody Book book) throws SQLException{
return new R(booksService.save(book));
}
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id){
System.out.println(id);
return new R(booksService.removeById(id));
}
@PutMapping
public R update(@RequestBody Book books){
return new R(booksService.updateById(books));
}
}
:::info
增删改页面逻辑
:::
```html
<!DOCTYPE html>
<html>
<head>
<!-- 页面meta -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>基于SpringBoot整合SSM案例</title>
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
<!-- 引入样式 -->
<link rel="stylesheet" href="../plugins/elementui/index.css">
<link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理</h1>
</div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input placeholder="图书类别" style="width: 200px;" class="filter-item"></el-input>
<el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input>
<el-input placeholder="图书描述" 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>
<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
<el-table-column type="index" align="center" label="序号"></el-table-column>
<el-table-column prop="type" label="图书类别" align="center"></el-table-column>
<el-table-column prop="name" label="图书名称" align="center"></el-table-column>
<el-table-column prop="description" label="描述" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<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>
<!-- 新增标签弹层 -->
<div class="add-form">
<el-dialog title="新增图书" :visible.sync="dialogFormVisible">
<el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleAdd()">确定</el-button>
</div>
</el-dialog>
</div>
<!-- 编辑标签弹层 -->
<div class="eidt-form">
<el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">
<el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleEdit()">确定</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</body>
<!-- 引入组件库 -->
<script src="../js/vue.js"></script>
<script src="../plugins/elementui/index.js"></script>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/axios-0.18.0.js"></script>
<script>
var vue = new Vue({
el: '#app',
data:{
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit:false,//编辑表单是否可见
formData: {},//表单数据
rules: {//校验规则
type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
},
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0//总记录数
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
},
methods: {
//列表
getAll() {
axios.get("/books").then((rs)=>{
this.dataList = rs.data.data;
});
},
//弹出添加窗口
handleCreate() {
//弹出新增弹层
this.dialogFormVisible = true;
//重置表单
this.resetForm();
},
//重置表单
resetForm() {
//重置表单数据
this.formData={};
},
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
if(res.data.flag){
//提示
this.$message.success("新增成功");
//关闭新增弹层
this.dialogFormVisible = false;
}else{
this.$message.error("新增失败");
}
}).finally(()=>{
//不管成功与否,都要重新查询列表
this.getAll();
})
},
//取消
cancel(){
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("新增操作取消");
},
// 删除
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("删除操作取消");
})
},
//弹出编辑窗口
handleUpdate(row) {
this.resetForm();
//查询当前用户并数据回显
axios.get("/books/"+row.id).then((res)=>{
if(res.data.flag && res.data.data != null){
this.formData = res.data.data;
//弹出编辑弹出层
this.dialogFormVisible4Edit = true;
}else{
this.$message.error("数据同步失败,已刷新页面");
}
}).finally(()=>{
this.getAll();
});
// 直接把row中的数据赋值给formData会有数据同步问题
// this.formData = {"id":row.id,"type":row.type,"name":row.name,"description":row.description}
},
//修改
handleEdit() {
console.log(this.formData);
axios.put("/books",this.formData).then((res)=>{
if(res.data.flag){
this.$message.success("修改成功");
this.dialogFormVisible4Edit = false;
}else{
this.$message.error("数据同步失败,刷新页面");
}
}).finally(()=>{
this.getAll();
})
},
//分页查询
//切换页码
handleCurrentChange(currentPage) {
},
//条件查询
}
})
</script>
</html>
全局异常处理
全局异常处理器和统一返回格式.wmv
后台系统bug导致数据格式不统一
:::info
可以使用SpringMVC提供的@RestControllerAdvice用来处理全局数据
顾名思义,@ControllerAdvice就是@Controller 的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用。
:::
package com.xuezhi.config;
import com.xuezhi.pojo.R;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler
public R doException(Exception e){
e.printStackTrace();
return new R("系统错误,请稍后再试!");
}
}
:::info 模拟异常 :::
@PostMapping
public R save(@RequestBody Book book) throws SQLException{
if("123".equals(book.getName())){
// 异常外抛
throw new SQLException();
}
return new R(booksService.save(book));
}
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
if(res.data.flag){
//提示
this.$message.success("新增成功");
//关闭新增弹层
this.dialogFormVisible = false;
}else{
// 使用后台提供的消息
this.$message.error(res.data.msg);
}
}).finally(()=>{
//不管成功与否,都要重新查询列表
this.getAll();
})
},
业务消息一致性处理
目前我们的业务消息有来自于前端的也有来自于后端的,我们可以在后端统一处理
:::info
例如:
:::
package com.xuezhi.controller;
import com.xuezhi.pojo.Book;
import com.xuezhi.pojo.R;
import com.xuezhi.service.BooksService;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLException;
@RestController
@RequestMapping("/books")
public class BooksController {
@Autowired
private BooksService booksService;
@GetMapping
public R getAll(){
return new R(true,booksService.list());
}
@GetMapping("/{id}")
public R getById(@PathVariable Integer id){
Book book = booksService.getById(id);
return new R(true,book,book == null?"数据同步失败,已刷新页面!":"");
}
@PostMapping
public R save(@RequestBody Book book) throws SQLException{
if("123".equals(book.getName())){
throw new SQLException();
}
boolean flag = booksService.save(book);
return new R(flag,flag?"新增成功^_^":"新增失败-_-!");
}
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id){
boolean flag = booksService.removeById(id);
return new R(flag,flag?"删除成功^_^":"数据同步失败,已刷新页面!");
}
@PutMapping
public R update(@RequestBody Book books){
boolean flag = booksService.updateById(books);
return new R(flag,flag?"修改成功^_^":"修改失败-_-!");
}
}
分页
分页查询.wmv
:::info
分页数据模版
:::
:::info
把getAll方法,改成分页查询
:::
//分页查询
getAll() {
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((rs)=>{
this.pagination.currentPage = rs.data.data.current;
this.pagination.total = rs.data.data.total;
this.dataList = rs.data.data.records;
});
},
//切换页码
handleCurrentChange(currentPage) {
this.pagination.currentPage = currentPage;
this.getAll();
},
@GetMapping("/{currentPage}/{pageSize}")
public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize){
Page<Book> bookPage = new Page<Book>(currentPage, pageSize);
Page<Book> page = booksService.page(bookPage);
return new R(true,page);
}
分页删除bug
:::info 如果最后一页只有一条数据,并且删除他,会导致一下bug ::: :::info 解决: :::
@GetMapping("/{currentPage}/{pageSize}")
public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize){
Page<Book> bookPage = new Page<Book>(currentPage, pageSize);
Page<Book> page = booksService.page(bookPage);
// 如果当前页大于总页码数,则查询最后一页数据
if(currentPage>page.getPages()){
bookPage = new Page<Book>(page.getPages(), pageSize);
page = booksService.page(bookPage);
}
return new R(true,page);
}
条件查询
条件查询.wmv
我们发现查询查询的模版并没有绑定模型数据,所以我们需要自己绑定一下,因为条件查询是跟着分页走的所以我们把数据写到 分页的模型里
:::info
组装查询参数,并拼接到url,携带到后台
:::
//分页查询
getAll() {
//组装条件查询请求参数
let param = "?q";
param += "&type="+this.pagination.type;
param += "&name="+this.pagination.name;
param += "&description="+this.pagination.description;
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((rs)=>{
//分页
this.pagination.currentPage = rs.data.data.current;
this.pagination.total = rs.data.data.total;
this.dataList = rs.data.data.records;
});
},
:::info 修改分页查询Controller,把业务逻辑放到Service层 :::
@GetMapping("/{currentPage}/{pageSize}")
public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize,Book book){
IPage<Book> page = booksService.getPage(currentPage,pageSize,book);
return new R(true,page);
}
:::info 根据条件查询接口 :::
public interface BooksService extends IService<Book> {
IPage<Book> getPage(Integer currentPage, Integer pageSize, Book book);
}
:::info 根据条件查询实现 :::
@Service
public class BooksServiceImpl extends ServiceImpl<BookMapper, Book> implements BooksService {
@Autowired
private BookMapper bookMapper;
public IPage<Book> getPage(Integer currentPage, Integer pageSize, Book book) {
Page<Book> page = new Page(currentPage, pageSize);
// 组装查询条件
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<Book>();
wrapper.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());
// 分页查询
Page<Book> bookPage = bookMapper.selectPage(page, wrapper);
// 避免删除之后分页bug,如果当前页>总页数,则查询最后一页
if(currentPage>bookPage.getPages()){
page = new Page(bookPage.getPages(), pageSize);
bookPage = bookMapper.selectPage(page, wrapper);
}
return bookPage;
}
}
总结
1. pom.xml
配置起步依赖
2. application.yml
设置数据源、端口、框架技术相关配置等
3. dao
继承BaseMapper、设置@Mapper
4. dao测试类
5. service
调用数据层接口或MyBatis-Plus提供的接口快速开发
6. service测试类
7. controller
基于Restful开发,使用Postman测试跑通功能
8. 页面
放置在resources目录下的static目录中