文章目录
SpringBoot+SpringMVC+MyBatis-Plus
一、简介
1、Spring Boot 是 Pivotal 团队在 Spring 的基础上提供的⼀套全新的开源框架,其⽬的是为了简化 Spring 应⽤的搭建和开发过程。
Spring Boot 去除了⼤量的 XML 配置⽂件,简化了复杂的依赖管理。
2、Spring Boot 具有 Spring 的⼀切优秀特性,Spring 能做的事,Spring Boot 都可以做,⽽且使⽤更加简单,功能更加丰富,性能更加稳定⽽健壮。随着近些年来微服务技术的流⾏,Spring Boot 也成了时下炙⼿可热的技术。
3、Spring Boot 集成了⼤量常⽤的第三⽅库配置,Spring Boot 应⽤中这些第三⽅库⼏乎可以是零配置的开箱即⽤(out-of-the-box),⼤部分的 Spring Boot 应⽤都只需要⾮常少量的配置代码(基于 Java 的配置),开发者能够更加专注于业务逻辑。
二、创建 SpringBoot 项⽬
下来我们通过 Intellij IDEA 创建第⼀个 Spring Boot 项⽬。
Intellij IDEA ⼀般可以通过两种⽅式创建 Spring Boot 项⽬:
- 使⽤ Maven 创建
- 使⽤ Spring Initializr 创建
Maven ⽅式创建
1、⾸先,⽂件—>新建—>项⽬
2、其次,选择名称、组名和存放路径。
下来,引⼊ Spring Boot 的相关依赖:
<project>
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>最新版本号</version>
<relativePath/>
</parent>
<dependencies>
<!--web相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
...
</project>
3、最后,编写 Spring Boot 启动类
创建⼀个名为 XxxApplication 主程序,⽤来启动 Spring Boot 应⽤
@SpringBootApplication
public class XxxApplication {
public static void main(String[] args) {
SpringApplication.run(XxxApplication.class, args);
}
}
注意:⼀般 Boot 项⽬的启动名称都为 项⽬名+Application ⽅式。
创建好的项⽬结构如下:
Spring Initializr ⽅式创建
IntelliJ IDEA ⽀持⽤户使⽤ Spring 项⽬创建向导(Spring Initializr )快速地创建⼀个 Spring Boot 项⽬
⾸先,选择项⽬创建向导,分别处理如下操作:
-
- 项⽬名称
-
- 存放位置
-
- 组
-
- JDK设置
注意:如果start.spring.io⽹速不好,可以点击⻮轮按钮修改成国内镜像:https://start.aliyun.io
三、Yaml 概述
Spring Boot 提供了⼤量的⾃动配置,极⼤地简化了 spring 应⽤的开发过程,当⽤户创建了⼀个 Spring Boot 项⽬后,即使不进⾏任何配置,该项⽬也能顺利的运⾏起来。当然,⽤户也可以根据⾃身的需要使⽤配置⽂件修改Spring Boot 的默认设置。
SpringBoot 默认使⽤以下 2 种全局的配置⽂件,其⽂件名是固定的。
- application.properties
- application.yml
其中,application.yml 是⼀种使⽤ YAML 语⾔编写的⽂件,它与 application.properties ⼀样,可以在 SpringBoot 启动时被⾃动读取,修改 Spring Boot ⾃动配置的默认值。
注意:当两种⽂件都同时存在时,以 application.properties 为主。
概述
YAML 全称 YAML Ain’t Markup Language,它是⼀种以数据为中⼼的标记语⾔,⽐ XML 和 JSON 更适合作为配置⽂件。
想要使⽤ YAML 作为属性配置⽂件(以 .yml 或 .yaml 结尾),需要将 SnakeYAML 库添加到 classpath 下,Spring Boot 中的 spring-boot-starter-web 或 spring-boot-starter 都对 SnakeYAML 库做了集成, 只要项⽬中引⽤了这两个 Starter 中的任何⼀个,Spring Boot 会⾃动添加 SnakeYAML 库到 classpath 下。
下⾯是⼀个简单的 application.yml 属性配置⽂件。
server:
port: 8080
语法
YAML 的语法如下:
- 使⽤缩进表示层级关系。
- 缩进时不允许使⽤ Tab 键,只允许使⽤空格。
- 缩进的空格数不重要,但同级元素必须左侧对⻬。
- ⼤⼩写敏感。
- Key的冒号和值中间有空格隔开。
例如:
server:
port: 8082
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/guanwei
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: guanwei
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: false
数据结构
YAML ⽀持以下三种数据结构:
-
字⾯量:单个的、不可拆分的值
-
对象:键值对的集合
-
数组:⼀组按次序排列的值
1、字⾯量写法
字⾯量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、以及⽇期等。
在 YAML 中,使⽤“key: value”的形式表示⼀对键值对,如 name: 关为。
字⾯量直接写在键值对的“value”中即可,且默认情况下字符串是不需要使⽤单引号或双引号的。
name: 魯迅
2、对象
在 YAML 中,对象可能包含多个属性,每⼀个属性都是⼀对键值对。
YAML 为对象提供了 2 种写法:
- 普通写法:使⽤缩进表示对象与属性的层级关系。
- ⾏内写法:使⽤ Map 格式表示对象中属性和值的关系。
#对象写法
#第一种写法
user1:
name: '杨康'
id: 1
gender: '女'
#第二种写法
user2:{ 'name': '王五','id': '2','gender': '男' }
3、数组
在 YAML 中,数组也和对象⼀样,存在两种写法,分别也是:普通写法和⾏内写法。
# 普通写法
array1:
- 张三
- 李四
- 王五
# ⾏内写法
array2: [张三,李四,王五]
4、复合结构写法
#复合写法
classroom:
id: 1
name: '软件一班'
teacher:
id: 1
name: '张老师'
desc: '班级管理者'
students:
- id: 1
name: '李四'
gender: '女'
- id: 2
name: '薛哲'
gender: '男'
四、Spring Boot 使⽤ MVC
1、什么是MVC
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
- 是将业务逻辑、数据、显示分离的方法来组织代码。
- MVC主要作用是降低了视图与业务逻辑间的双向偶合。
- MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。
最典型的MVC就是JSP + Servlet + JavaBean的模式。
2、SpringMVC三层架构
Java SpringMVC的工程结构一般来说分为三层,自下而上是Modle层(模型,数据访问层)、Cotroller层(控制,逻辑控制层)、View层(视图,页面显示层),其中Modle层分为两层:dao层、service层,MVC架构分层的主要作用是解耦。
采用分层架构的好处,普遍接受的是系统分层有利于系统的维护,系统的扩展。就是增强系统的可维护性和可扩展性。
对于Spring这样的框架,(View\Web)表示层调用控制层(Controller),控制层调用业务层(Service),业务层调用数据访问层(Dao)。
Service层:业务层,用来实现业务逻辑。能调用dao层或者service层,返回数据对象DO或者业务对象BO,BO通常由DO转化、整合而来,可以包含多个DO的属性,也可以是只包含一个DO的部分属性。通常为了简便,如果无需转化,service也可以直接返回DO。外部调用(HTTP、RPC)方法也在这一层,对于外部调用来说,service一般会将外部调用返回的DTO转化为BO。是专注业务逻辑,对于其中需要的数据库操作,都通过Dao去实现。主要去负责一些业务处理,比如取得连接、关闭数据库连接、事务回滚,一些复杂的逻辑业务处理就放到service层。
DAO层:负责访问数据库进行数据的操作,取得结果集,之后将结果集中的数据取出封装到VO类对象之后返回给service层。数据层,直接进行数据库的读写操作,返回数据对象DO,DO与数据库表一一对应。Dao的作用是封装对数据库的访问:增删改查,不涉及业务逻辑,只是达到按某个条件获得指定数据的要求。
Cotroller层:叫做控制层,主要的功能是处理用户发送的请求。主要处理外部请求。调用service层,将service层返回的BO/DO转化为DTO/VO并封装成统一返回对象返回给调用方。如果返回数据用于前端模版渲染则返回VO,否则一般返回DTO。不论是DTO还是VO,一般都会对BO/DO中的数据进行一些转化和整合,比如将gender属性中的0转化“男”,1转化为“女”等。controller的功能主要有5点:参数校验、调用service层接口实现业务逻辑、转换业务/数据对象、组装返回对象、异常处理。
View层:叫做显示层,主要是负责现实数据。
在实际开发中dao层要先定义出自己的操作标准即标准接口,就是为了解耦合。
1、稍微小一点的公司一般就3层:
dao->数据层
service -> 业务实现
web -> web 接口
2、稍微大一点的公司一般最多就7层:
(日志接入,对查问题有帮助的地方才接入日志)
-
common—>公共方法,公共类,工具类,只有关键地方加日志
-
dao->数据层,不加日志
-
domain->实体类,不加日志
-
rpc->调用接口,出入参加日志
-
sdk-> 给外部提供sdk,例如对外接口,只是提供接口定义,该层没有业务逻辑,真实的sdk逻辑实现是在service层,不加日志
-
service -> 业务实现逻辑,单元测试进行层,日志接入:关键地方可以加, 实现sdk层的接口方法出入参加日志
-
web -> web 接口,对外提供http 接口,一般都有逻辑,http 是直接对 c的,看到的ip 是c端用户的,出入参加日志
SpringBoot 使⽤ MVC
虽然 SpringMVC 相较于 Servlet 在使⽤上已经有了极⼤的提升,但是在环境搭建、配置⽂件和对注解的使⽤上仍然有些繁琐,有些同学在想有没有⼀种⽅案,可以快速搭建 SpringMVC 环境?
引⼊依赖
这⾥集成了 SpringMVC、Servlet等相关依赖并设置了兼容版本。
<!--SpringMVC相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3、spring_boot_mvc1
1、建包(controller)
2、创建类——FirstController
//如何请求和响应
// 日志注解
@Slf4j
// IoC注解
@Controller//将FirstController的对象new出来
// 访问路径注解 http://localhost:8080/first
@RequestMapping("/first")// '/'根目录
public class FirstController {
// http://localhost:8080/first/a
@RequestMapping("/a")
public String a() {//String代表的是跳转的路径/名称
log.info("用户访问了FirstController的a方法");
System.out.println("用户访问了FirstController的a方法");
// /1234.html
return "1234"; // 跳转的路径,需要和前缀后缀组合
// return "a/1234";
}
}
注:@RequestMapping负责访问 return +路径 负责响应
3、创建类——SpringBootMvc1Application
@SpringBootApplication
public class SpringBootMvc1Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMvc1Application.class, args);
}
}
注:后端没错,是路径错误
4、创建如下目录
1234.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>1234</h1>
</body>
</html>
application.yml
spring:
mvc:
view:
prefix: / # 跳转的前缀
suffix: .html # 跳转的后缀
顶格敲代码有提示
//带参数的请求
@RequestMapping("/b")
public String b(String name, int age) {
log.info("用户访问了FirstController的b方法,参数name是{},age是{}", name, age);//占位符比较轻松
System.out.println("用户访问了FirstController的b方法,参数name是" + name + ",age是" + age);//字符串拼接,麻烦
return "1234";
}
5、直接访问会报错,没有进行赋值
6、给html咋赋值,用? =,访问成功
相比于servlet而言,不仅拿到了值,还把类型转过来了
注意:
像以上这种,路径未发生改变,就跳转页面中去了, 这种方式叫做转发
7、重定向书写
//重定向
@RequestMapping("/c")
public String c() {
log.info("重定向问题");
return "redirect:/1234.html"; // 必须写完整路径
}
注:相当于将前缀、后缀、字符串响应效果结合起来,拼接到一起去了(必须写完整路径)
8、多参数封装问题
// 多参数封装问题
@RequestMapping("/d1")
public String d1(String ename, String job, int mgr, String hireDate, double sal, double comm, int deptNo) {
log.info("多个参数分别是:{},{},{},{},{},{},{}", ename, job, mgr, hireDate, sal, comm, deptNo);
return "redirect:/1234.html";
}
@RequestMapping("/d2")
public String d2(Emp emp) {
log.info("emp:{}", emp);
return "redirect:/1234.html";
}
建立bean包,创建Emp类
@Data
public class Emp {
private Integer empNo;
private String ename;
private String job;
private Integer mgr;
private String hireDate;
private Double sal;
private Double comm;
private Integer deptNo;
private Integer state;
}
改写1234.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="first/d1">
姓名:<input name="ename"/><br/>
职位:<input name="job"/><br/>
上司:<input name="mgr"/><br/>
入职时间:<input name="hireDate" type="date"/><br/>
工资:<input name="sal"/><br/>
奖金:<input name="comm"/><br/>
部门ID:<input name="deptNo"/><br/>
<button>提交</button>
</form>
<hr/>
<form action="first/d2">
姓名:<input name="ename"/><br/>
职位:<input name="job"/><br/>
上司:<input name="mgr"/><br/>
入职时间:<input name="hireDate" type="date"/><br/>
工资:<input name="sal"/><br/>
奖金:<input name="comm"/><br/>
部门ID:<input name="deptNo"/><br/>
<button>提交</button>
</form>
</body>
</html>
第一种方式:
点击提交
又跳转回来了
值从页面传过来了
第二种方式:
点击提交
又跳转回来了
值从页面传过来了 ,是从Emp中按属性名找,但是保证有setter方法,会调用setter方法给赋值,相比较servlet而言,springmvc简单
9、不跳转的情况,springmvc将数据返回回去(ajax)
创建SecondController类
@Slf4j//日志信息
//不跳转情况
@Controller
@RequestMapping("/second")
public class SecondController {
@RequestMapping("/a")
//告诉springMVC,返回内容不再是页面名称,而是响应数据
@ResponseBody
public String a() {//String代表:响应的数据数据内容
log.info("SecondController`a method!");
return "SecondController`a method!";
}
@ResponseBody:写啥,就显示啥
啥类型都能支持,数字串、数组、集合 、对象
@RequestMapping("/b")
@ResponseBody
public String b() {
return "1242323";
}
@RequestMapping("/c")
@ResponseBody
public String[] c() {
return new String[]{"aa", "bb", "cc", "dd"};
}
@RequestMapping("/d")
@ResponseBody
public List<String> d() {
return Arrays.asList("aaa", "bbb", "ccc");
}
@RequestMapping("/e")
@ResponseBody
public Emp e() {
return new Emp();
}
4、spring_boot_project_assort
一、准备工作
建立数据库bookstore
#创建数据库
create database bookstore;
use bookstore;
#创建表
## 分类
create table assort
(
id int primary key auto_increment,
name varchar(20) not null unique ,
`desc` varchar(1000) ,
state int default 1
);
#录入测试数据
insert into assort values(null,'小说','网络小说前沿阵地',1);
insert into assort values(null,'散文','古今中外散文合集',1);
insert into assort values(null,'诗集','唐诗三百首',1);
导依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
注意:以前不用数据库链接池时,假如当你想查询所有用户信息的时候,首先,先建立Java与mysql的数据连接通道,java会给mysql发送一个servlet请求,mysql执行完这个请求,会把结果返回给java,java得到这个结果之后,会把连接通道关闭。
如果还有其他请求,光是建立连接和断开连接就花费大量时间,消耗资源,不划算。
在resources中编写application.yml文件
## 必须提供账号,密码,url和驱动类
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/bookstore
driver-class-name: com.mysql.cj.jdbc.Driver
## 日志 将内容输出到控制台中去
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
二、查询功能
1.写bean包
创建Assort类
@Data
public class Assort implements Serializable {
private Integer id;
private String name;
private String desc;
private Integer state;
}
2.写mapper包
创建AssortMapper类
@Mapper
public interface AssortMapper {
@Select("select * from assort where state=1")
List<Assort> findAllAssort();
}
3.写service包
创建一个接口AssortService
public interface AssortService {
JsonResult find();
}
4.写util包
创建一个类JsonResult
// 统一返回格式
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResult<T> {
private Boolean success;
private Integer code;
private String error;
private T data;
}
4.实现类,先创建impl包,在创建AssortServiceImpl类
//先new对象
@Service
public class AssortServiceImpl implements AssortService {
@Resource//给赋值
private AssortMapper mapper;
@Override
public JsonResult find() {//将list转换成JsonResult格式
List<Assort> list = mapper.findAllAssort();
if (list.size() == 0) {
return new JsonResult(false, 404, "查询不出来数据!", null);
}
return new JsonResult(true, 200, null, list);
}
}
5.写controller包
创建一个类AssortController
@RestController // ===@Controller+@ResponseBody
@RequestMapping("/assort")
public class AssortController {
@Resource
private AssortService service;
@RequestMapping("/find")
public JsonResult find() {//JsonResult什么类型都能返回
return service.find();
}
@RequestMapping("/delete")
public JsonResult delete(int id){
return service.delete(id);
}
}
注:@RestController 的作用:
1、将AssortController的对象new出来
2、所有的方法的返回结果都是内容输出
关系流程:
- 1、controller是接收页面请求的(页面找controller)
- 2、(controller)接收到页面请求找service
- 3、 service 找mapper
- 4、mapper找数据库
数据传递(若是查询):
- 1、数据库将数据给mapper
- 2、mapper将数据给service
- 3、service将数据给controller
- 4、controller将数据给页面
6.编写启动类
@SpringBootApplication
public class SpringBootProjectAssortApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootProjectAssortApplication.class, args);
}
}
7.打开Postman软件进行测试
8.搭建如下目录
9.打开ELement网站,找样式
10.index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入elementUI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<el-container style="height: 800px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">用户查询</el-menu-item>
<el-menu-item index="1-2">用户添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>分类管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类查询</el-menu-item>
<el-menu-item index="2-2">分类添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>图书管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">图书查询</el-menu-item>
<el-menu-item index="3-2">图书添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>
<el-main>
<img src="img/background.webp"/>
</el-main>
</el-container>
</el-container>
</div>
</body>
</html>
<!--引入vue-->
<script src="js/vue.js"></script>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<!-- 引入elementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app'
})
</script>
11.assort.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入elementUI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<el-container style="height: 800px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">用户查询</el-menu-item>
<el-menu-item index="1-2">用户添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>分类管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类查询</el-menu-item>
<el-menu-item index="2-2">分类添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>图书管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">图书查询</el-menu-item>
<el-menu-item index="3-2">图书添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="id" label="编号" width="140">
</el-table-column>
<el-table-column prop="name" label="分类名" width="140">
</el-table-column>
<el-table-column prop="desc" label="备注" width="120">
</el-table-column>
<el-table-column prop="state" label="状态">
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
</div>
</body>
</html>
<!--引入vue-->
<script src="js/vue.js"></script>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<!-- 引入elementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
//这里定义变量
tableData: []
}
},
methods: {
loadAssortData() {
let _this = this
// 从后端获取到数据,把数据给tableData赋值
axios.get('assort/find')
.then((response) => {
// 获取到后端返回的数据
let d = response.data
_this.tableData = d.data
})
}
},
created(){
// 页面加载后执行
this.loadAssortData()
}
})
</script>
总结:
前端的书写步骤:
1、引入Vue;
4、在一个div标签中加入样式
5、new Vue对象
1、确立绑定
el: '#app'
2、定义变量区域
data() { return { //这里定义变量 tableData: [] }
3、定义函数
methods: { loadAssortData() { let _this = this // 从后端获取到数据,把数据给tableData赋值 axios.get('assort/find') .then((response) => { // 获取到后端返回的数据 let d = response.data _this.tableData = d.data }) } },
定义一个函数:
1、从后端中获取到数据,把数据赋给已定义的变量
2、通过axios发送请求和返回结果
4、调用函数
created(){ // 页面加载后执行 this.loadAssortData() }
注意:所有的函数或者变量想要被调用,都必须以this.___开头
12、先看Postman好没好,确定后端有没有问题
再看浏览器,右键检查,查看报错
三、删除功能
后端部分
mapper包
@Mapper
public interface AssortMapper {
@Update("update assort set state=0 where id=#{id}")
void deleteAssort(int id);
}
service包
public interface AssortService {
JsonResult delete(int id);
}
@Service
public class AssortServiceImpl implements AssortService {
@Resource
private AssortMapper mapper;
@Override
public JsonResult delete(int id) {
mapper.deleteAssort(id);
return new JsonResult(true,200,null,null);
}
}
controller包
@RestController // ===@Controller+@ResponseBody
@RequestMapping("/assort")
public class AssortController {
@Resource
private AssortService service;
@RequestMapping("/delete")
public JsonResult delete(int id){
return service.delete(id);
}
}
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入elementUI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<el-container style="height: 800px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">用户查询</el-menu-item>
<el-menu-item index="1-2">用户添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>分类管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类查询</el-menu-item>
<el-menu-item index="2-2">分类添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>图书管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">图书查询</el-menu-item>
<el-menu-item index="3-2">图书添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="id" label="编号" width="60">
</el-table-column>
<el-table-column prop="name" label="分类名" width="120">
</el-table-column>
<el-table-column prop="desc" label="备注" width="190">
</el-table-column>
<el-table-column prop="state" label="状态" width="60">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini" @click="showUpdateDialog(scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger" @click="deleteAssort(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
<!--修改的模态框-->
<el-dialog title="编辑分类" :visible.sync="showUpdateAssortDialog">
{{errorMsg}}
<el-form :model="updateForm">
<el-form-item label="分类名称" :label-width="formLabelWidth">
<el-input v-model="updateForm.name" autocomplete="off" @blur="checkNameIsExist"></el-input>
</el-form-item>
<el-form-item label="备注信息" :label-width="formLabelWidth">
<el-input v-model="updateForm.desc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showUpdateAssortDialog=false">取 消</el-button>
<el-button type="primary" @click="updateAssort">确 定</el-button>
</div>
</el-dialog>
</div>
</body>
</html>
<!--引入vue-->
<script src="js/vue.js"></script>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<!-- 引入elementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
//这里定义变量
tableData: [],
},
methods: {
loadAssortData() {
let _this = this
// 从后端获取到数据,把数据给tableData赋值
axios.get('assort/find')
.then((response) => {
// 获取到后端返回的数据
let d = response.data
_this.tableData = d.data
})
},
deleteAssort(row) {
let _this = this
this.$confirm('此操作将永久删除该分类, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定了 调用后端的删除操作并传递id,成功后刷新页面
// axios.get('assort/delete?id='+row.id)
axios.get('assort/delete', {
params: {
id: row.id
}
}).then((response) => {
//_this.tableData = response.data.data
//console.log(_this.tableData)
_this.loadAssortData()
this.$message({
type: 'success',
message: '已成功删除'
});
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
created() {
// 页面加载后执行
this.loadAssortData()
}
})
</script>
四、修改功能
Vue与Axios的作用
1、Vue:对前端页面数据处理
2、Axios:像中介,Vue想要数据不能通过controller,而是通过axios找到数据,再给vue的某个变量赋值。
后端部分
Mapper包
@Mapper
public interface AssortMapper {
@Update("update assort set name=#{name},`desc`=#{desc} where id=#{id}")
void updateAssort(Assort assort);
}
service包
public interface AssortService {
JsonResult update(Assort assort);
}
@Service
public class AssortServiceImpl implements AssortService {
@Resource
private AssortMapper mapper;
@Override
public JsonResult update(Assort assort) {
mapper.updateAssort(assort);
return new JsonResult(true, 200, null, null);
}
}
controller包
@RestController // ===@Controller+@ResponseBody
@RequestMapping("/assort")
public class AssortController {
@Resource
private AssortService service;
@RequestMapping("/update")
public JsonResult update(Assort assort) {
return service.update(assort);
}
}
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入elementUI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<el-container style="height: 800px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">用户查询</el-menu-item>
<el-menu-item index="1-2">用户添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>分类管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类查询</el-menu-item>
<el-menu-item index="2-2">分类添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>图书管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">图书查询</el-menu-item>
<el-menu-item index="3-2">图书添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="id" label="编号" width="60">
</el-table-column>
<el-table-column prop="name" label="分类名" width="120">
</el-table-column>
<el-table-column prop="desc" label="备注" width="190">
</el-table-column>
<el-table-column prop="state" label="状态" width="60">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini" @click="showUpdateDialog(scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger" @click="deleteAssort(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
<!--修改的模态框-->
<el-dialog title="编辑分类" :visible.sync="showUpdateAssortDialog">
{{errorMsg}}
<el-form :model="updateForm">
<el-form-item label="分类名称" :label-width="formLabelWidth">
<el-input v-model="updateForm.name" autocomplete="off" @blur="checkNameIsExist"></el-input>
</el-form-item>
<el-form-item label="备注信息" :label-width="formLabelWidth">
<el-input v-model="updateForm.desc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showUpdateAssortDialog=false">取 消</el-button>
<el-button type="primary" @click="updateAssort">确 定</el-button>
</div>
</el-dialog>
</div>
</body>
</html>
<!--引入vue-->
<script src="js/vue.js"></script>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<!-- 引入elementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
//这里定义变量
tableData: [],
showUpdateAssortDialog: false,
formLabelWidth: '120px',
updateForm: {
id: '',
name: '',
desc: ''
},
isExist: false,
errorMsg: ''
}
},
methods: {
loadAssortData() {
let _this = this
// 从后端获取到数据,把数据给tableData赋值
axios.get('assort/find')
.then((response) => {
// 获取到后端返回的数据
let d = response.data
_this.tableData = d.data
})
},
deleteAssort(row) {
let _this = this
this.$confirm('此操作将永久删除该分类, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定了 调用后端的删除操作并传递id,成功后刷新页面
// axios.get('assort/delete?id='+row.id)
axios.get('assort/delete', {
params: {
id: row.id
}
}).then((response) => {
//_this.tableData = response.data.data
//console.log(_this.tableData)
_this.loadAssortData()
this.$message({
type: 'success',
message: '已成功删除'
});
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
showUpdateDialog(row) {
//给模态框的属性赋值
this.updateForm.name = row.name//修改后的值
this.updateForm.desc = row.desc//修改后的值
this.updateForm.id = row.id//修改后的值
this.showUpdateAssortDialog = true
},
updateAssort() {
if (this.isExist) {
let _this = this
axios.get('assort/update', {//修改前
params: {
id: _this.updateForm.id,
name: _this.updateForm.name,
desc: _this.updateForm.desc
}
}).then((response) => {//修改后
_this.loadAssortData()//重查
_this.showUpdateAssortDialog = false//不显示
this.$message({
type: 'success',
message: '已成功修改'
});
})
}
},
created() {
// 页面加载后执行
this.loadAssortData()
}
})
</script>
五、修改优化
注意:在修改之前,要进行数据校验,判断能不能修改
后端部分
mapper包
@Mapper
public interface AssortMapper {
@Select("select count(0) from assort where name=#{name}")
int checkNameIsExist(String name);
}
service包
public interface AssortService {
JsonResult find();
JsonResult checkNameIsExist(String name);
}
@Service
public class AssortServiceImpl implements AssortService {
@Resource
private AssortMapper mapper;
@Override
public JsonResult checkNameIsExist(String name) {
int row = mapper.checkNameIsExist(name);
if (row > 0) {
return new JsonResult(false, 500, "分类名称已被使用", null);
}
return new JsonResult(true, 200, null, null);
}
}
controller包
@RestController // ===@Controller+@ResponseBody
@RequestMapping("/assort")
public class AssortController {
@Resource
private AssortService service;
@RequestMapping("/exist")
public JsonResult exist(String name) {
return service.checkNameIsExist(name);
}
}
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入elementUI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<el-container style="height: 800px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">用户查询</el-menu-item>
<el-menu-item index="1-2">用户添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>分类管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类查询</el-menu-item>
<el-menu-item index="2-2">分类添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>图书管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">图书查询</el-menu-item>
<el-menu-item index="3-2">图书添加</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="id" label="编号" width="60">
</el-table-column>
<el-table-column prop="name" label="分类名" width="120">
</el-table-column>
<el-table-column prop="desc" label="备注" width="190">
</el-table-column>
<el-table-column prop="state" label="状态" width="60">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini" @click="showUpdateDialog(scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger" @click="deleteAssort(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
<!--修改的模态框-->
<el-dialog title="编辑分类" :visible.sync="showUpdateAssortDialog">
{{errorMsg}}
<el-form :model="updateForm">
<el-form-item label="分类名称" :label-width="formLabelWidth">
<el-input v-model="updateForm.name" autocomplete="off" @blur="checkNameIsExist"></el-input>
</el-form-item>
<el-form-item label="备注信息" :label-width="formLabelWidth">
<el-input v-model="updateForm.desc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showUpdateAssortDialog=false">取 消</el-button>
<el-button type="primary" @click="updateAssort">确 定</el-button>
</div>
</el-dialog>
</div>
</body>
</html>
<!--引入vue-->
<script src="js/vue.js"></script>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<!-- 引入elementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
//这里定义变量
tableData: [],
showUpdateAssortDialog: false,
formLabelWidth: '120px',
updateForm: {
id: '',
name: '',
desc: ''
},
isExist: false,
errorMsg: ''
}
},
methods: {
loadAssortData() {
let _this = this
// 从后端获取到数据,把数据给tableData赋值
axios.get('assort/find')
.then((response) => {
// 获取到后端返回的数据
let d = response.data
_this.tableData = d.data
})
},
deleteAssort(row) {
let _this = this
this.$confirm('此操作将永久删除该分类, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定了 调用后端的删除操作并传递id,成功后刷新页面
// axios.get('assort/delete?id='+row.id)
axios.get('assort/delete', {
params: {
id: row.id
}
}).then((response) => {
//_this.tableData = response.data.data
//console.log(_this.tableData)
_this.loadAssortData()
this.$message({
type: 'success',
message: '已成功删除'
});
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
showUpdateDialog(row) {
//给模态框的属性赋值
this.updateForm.name = row.name//修改后的值
this.updateForm.desc = row.desc//修改后的值
this.updateForm.id = row.id//修改后的值
this.showUpdateAssortDialog = true
},
updateAssort() {
if (this.isExist) {
let _this = this
axios.get('assort/update', {//修改前
params: {
id: _this.updateForm.id,
name: _this.updateForm.name,
desc: _this.updateForm.desc
}
}).then((response) => {//修改后
_this.loadAssortData()//重查
_this.showUpdateAssortDialog = false//不显示
this.$message({
type: 'success',
message: '已成功修改'
});
})
}
},
checkNameIsExist() {
// 获取到名称
let name = this.updateForm.name
let _this = this
// 发送给后端
axios.get('assort/exist', {
params: {
name: name
}
}).then((response) => {
let success = response.data.success
if (success) {
_this.isExist = true
_this.errorMsg = ''
} else {
_this.isExist = false
_this.errorMsg = response.data.error
}
})
}
},
created() {
// 页面加载后执行
this.loadAssortData()
}
})
</script>
测试结果
修改失败
修改成功
5、⽂件结构
默认情况下,Spring Boot 将从类路径或 ServletContext 的根⽬录中的名为 src/main/resources/static 的⽬录提供静态内容。
在静态内容当中我们可以放js,css样式等⽂件,除Web服务,我们还可以使⽤ Spring MVC 来提供动态 HTML 内容。Spring MVC ⽀持各种模板技术,包括Thymeleaf,FreeMarker 和 JSP。当然 SpringBoot 不推荐⽤ JSP 来作为视图层,通常情况我们把模板放在 src/main/resources/templates下。
以下⽬录就是典型的模板与静态资源⽬录结构:
6、相关注解
注:查询推荐使用@GetMapping,登录查询建议使用PostMapping
7、配置⽂件
server:
port: 8081 # 端口号
servlet:
context-path: /dailyblue # 加路径
# 起服务名
spring:
application:
name: dailyblue
启动并访问
启动启动类,并打开浏览器输⼊路径访问:
8、spring_boot_mvc2
项目结构
1、导依赖,父项目中已经导入mvc依赖
2、 Controller包——>FirstController类
@Slf4j//日志
@RestController//new对象\方法的返回结果都是内容输出
@RequestMapping("/first")//二级目录,以便 查找
public class FirstController {
// 查询使用get请求 http://localhost:8080/first
@GetMapping//只能用一次
public String find() {
log.info("FirstController`find method!");
return "FirstController`find method!";
}
@GetMapping("/page")//分页(第一种写法)
public String find1(int page) {
log.info("FirstController`find1 method!page:{}", page);
return "FirstController`find1 method!page:{}" + page;
}
// http://localhost:8080/first/1
@GetMapping("/{page}")//分页(第二种写法)
public String find2(@PathVariable int page) {
log.info("FirstController`find2 method!page:{}", page);
return "FirstController`find2 method!page:{}" + page;
}
// http://localhost:8080/first/search/admin
@GetMapping("/search/{name}")//模糊查询 根据名字查
public String find3(@PathVariable String name) {
log.info("FirstController`find3 method!name:{}", name);
return "FirstController`find3 method!name:{}" + name;
}
@DeleteMapping("/{id}")//删除 @PathVariable指的是:不再是?的传递,而是拼接
public String delete(@PathVariable int id) {//@PathVariable 从请求路径获取参数值的注解
log.info("FirstController`delete method!id:{}", id);
return "FirstController`delete method!id:{}" + id;
}
@PutMapping//只能用一次
public String update(Student1 student) {
log.info("FirstController`update method!student:{}", student);
return "FirstController`update method!student:{}" + student;
}
@PostMapping//只能用一次
public String save(Student1 student) {
log.info("FirstController`save method!student:{}", student);
return "FirstController`save method!student:{}" + student;
}
@GetMapping("/a")
public String a(@RequestParam(defaultValue = "1", name = "abc") int page) {//@RequestParam指的是:防止为空,要有默认值
log.info("FirstController`a method!page:{}", page);
return "FirstController`a method!page:" + page;
}
}
注意:1、@PostMapping、@DeleteMapping和@PutMapping不能通过超链接,超链接只能是get请求
2、post/put(Query) get(Body)
3、编写SpringBootMVC2Application
@SpringBootApplication
public class SpringBootMVC2Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMVC2Application.class, args);
}
}
4.创建bean包,写Student1类
@ConfigurationProperties(prefix = "student1")
@Data
@Component
public class Student1 {
private Integer id;
private String name;
private String gender;
}
写Rooml类
@Data
@Component
public class Room implements Serializable {
private String name;
private String address;
}
5、测试及结果
1.
2.
3.
-
5.
6.
7.
springmvc 完整运行流程
- 中央处理器:中转和转发
- 处理映射器:寻址
- 处理器适配器:执行Controller
- 视图解析器:负责解析你的视图路径
9、Web启动器
Spring MVC 是 Spring 提供的⼀个基于 MVC 设计模式的轻量级 Web 开发框架,其本身就是 Spring 框架的⼀部分,可以与 Spring ⽆缝集成,性能⽅⾯具有先天的优越性,是当今业界最主流的 Web 开发框架之⼀。
Spring Boot 是在 Spring 的基础上创建⼀款开源框架,它提供了 spring-boot-starter-web(Web 场景启动器) 来为 Web 开发予以⽀持。spring-boot-starter-web 为我们提供了嵌⼊的 Servlet 容器以及 SpringMVC 的依赖,并为 Spring MVC 提供了⼤量⾃动配置,可以适⽤于⼤多数 Web 开发场景。
Spring Boot 为 Spring MVC 提供了⾃动配置,并在 Spring MVC 默认功能的基础上添加了以下特性:
-
引⼊了 ContentNegotiatingViewResolver 和 BeanNameViewResolver(视图解析器)
-
对包括 WebJars 在内的静态资源的⽀持
-
⾃动注册 Converter、GenericConverter 和 Formatter (转换器和格式化器)
-
对 HttpMessageConverters 的⽀持(Spring MVC 中⽤于转换 HTTP 请求和响应的消息转换器)
-
⾃动注册 MessageCodesResolver(⽤于定义错误代码⽣成规则)
-
⽀持对静态⾸⻚(index.html)的访问
-
⾃动使⽤ ConfigurableWebBindingInitializer
只要我们在 Spring Boot 项⽬中的 pom.xml 中引⼊了 spring-boot-starter-web ,即使不进⾏任何配置,也可以直接使⽤ Spring MVC 进⾏ Web 开发。
注意:由于 spring-boot-starter-web 默认替我们引⼊了核⼼启动器 spring-boot-starter,因此,当 SpringBoot 项⽬中的 pom.xml 引⼊了 spring-boot-starter-web 的依赖后,就⽆须在引⼊ spring-boot-starter 核⼼启动器的依赖了。
五、配置绑定
所谓“配置绑定”就是把配置⽂件中的值与 JavaBean 中对应的属性进⾏绑定。通常,我们会把⼀些配置信息(例如,数据库配置)放在配置⽂件中,然后通过 Java 代码去读取该配置⽂件,并且把配置⽂件中指定的配置封装到JavaBean(实体类)中。
SpringBoot 提供了以下 2 种⽅式进⾏配置绑定:
- 使⽤ @ConfigurationProperties 注解
- 使⽤ @Value 注解
1、@ConfigurationPreperties注解
通过 Spring Boot 提供的 @ConfigurationProperties 注解,可以将全局配置⽂件中的配置数据绑定到 JavaBean中。
下⾯演示如何通过 @ConfigurationProperties 注解进⾏配置绑定。
application.yml
创建一个包static,编写application.yml文件,添加以下⾃定义属性。
student1:
id: 1
name: '张三'
gender: '男'
age: 19
loves: [ '足球','游戏','网球' ]
room:
name: '软件一班'
address: '2#409'
实体类
@ConfigurationProperties:从yml文件中读取数据(批量读),然后将数据封装到Bean包中的Studnet类中
/**
* 将配置⽂件中配置的每⼀个属性的值,映射到这个组件中
*
* @ConfigurationProperties:告诉 SpringBoot 将本类中的所有属性和配置⽂件中相关的配置进⾏绑定
* prefix = "student":配置⽂件中哪个下⾯的所有属性进⾏⼀⼀映射
*
* 只有这个组件是容器中的组件,才能使⽤容器提供的@ConfigurationProperties功能
*/
@Component
@ConfigurationProperties(prefix = "student")
@PropertySource(value = "classpath:student.properties")
@Data
public class Student implements Serializable {
private Integer id;
private String name;
private String gender;
private String[] loves;
private Room room;
}
注意
只有在容器中的组件,才会拥有 SpringBoot 提供的强⼤功能。如果我们想要使⽤
@ConfigurationProperties 注解进⾏配置绑定,那么⾸先就要保证该对 JavaBean 对象在 IoC 容器中,所以需要⽤到 @Component 注解来添加组件到容器中。
JavaBean 上使⽤了注解 @ConfigurationProperties(prefix = “person”) ,它表示将这个 JavaBean 中的所有属性与配置⽂件中以“person”为前缀的配置进⾏绑定。
控制层类
创建一个SecondController
@RequestMapping("/second")
@RestController
public class SecondController {
@Resource
private Student student;
@GetMapping
public Student a() {
return student;
}
}
启动并查看
问题
如何解决呢?
在 pom.xml 中引⼊如下注解:
<!-- 是一个可选的依赖项,主要用于在开发阶段自动检测和处理@Configuration类中的属性。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2、@Value注解
当我们只需要读取配置⽂件中的某⼀个配置时,可以通过 @Value 注解获取。
创建一个ThirdController,l利用@Value注解(一次赋一个值)将yml文件中的数据读取出来
@RestController
@RequestMapping("/third")
public class ThirdController {
@Value("${student1.id}")
private Integer id;
@Value("${student1.name}")
private String name;
@Value("${student1.room.name}")
private String className;
@GetMapping
public String a() {
return "id:" + id + ",name:" + name + ",className:" + className;
}
}
3、区别
@Value 和 @ConfigurationProperties 注解都能读取配置⽂件中的属性值并绑定到 JavaBean 中,但两者存在以下不同。
使⽤位置不同
@ConfigurationProperties:标注在 JavaBean 的类名上。
@Value:标注在 JavaBean 的属性上。
功能不同
@ConfigurationProperties:⽤于批量绑定配置⽂件中的配置。
@Value:只能⼀个⼀个的指定需要绑定的配置。
松散绑定⽀持不同
@ConfigurationProperties:⽀持松散绑定(松散语法),例如实体类 Student 中有⼀个属性为 firstName,那么配置⽂件中的属性名⽀持以下写法:
- student.firstName
- student.first-name
- student.first_name
- STUDENT_FIRST_NAME
@Vaule:不⽀持松散绑定。
SpEL ⽀持不同
@ConfigurationProperties:不⽀持 SpEL 表达式。
@Value:⽀持 SpEL 表达式。
复杂类型封装
@ConfigurationProperties:⽀持所有类型数据的封装,例如 Map、List、Set、以及对象等;
@Value:只⽀持基本数据类型的封装,例如字符串、布尔值、整数等类型。
应⽤场景不同
@Value 和 @ConfigurationProperties 两个注解之间,并没有明显的优劣之分,它们只是适合的应⽤场景不同⽽已。
若只是获取配置⽂件中的某项值,则推荐使⽤ @Value 注解。
若专⻔编写了⼀个 JavaBean 来和配置⽂件进⾏映射,则建议使⽤ @ConfigurationProperties 注解。
我们在选⽤时,根据实际应⽤场景选择合适的注解能达到事半功倍的效果。
4、@PropertySource 注解
- 如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置⽂件会⼗分的臃肿且难以维护。
- 因此我们通常会将与 Spring Boot ⽆关的配置(例如⾃定义配置)提取出来,写在⼀个单独的配置⽂件中,并在对应的 JavaBean 上使⽤ @PropertySource 注解指向该配置⽂件。
- 将与 student 相关的⾃定义配置移动到 src/main/resources 下的 student.properties中(注意,必须把 application.properties 或 application.yml 中的相关配置删除)
1、编写上述student.properties文件
student.id=1
student.name=张三
student.sex=男
student.age=19
student.loves=足球,游戏,网球
student.room.name=软件一班
student.room.address=南2楼3楼302
2、删除yml文件
3、Student 类中引⼊@PropertySource 注解即可
@Component
@ConfigurationProperties(prefix = "student")
@PropertySource(value = "classpath:student.properties")
@Data
public class Student implements Serializable {
private Integer id;
private String name;
private String gender;
private String[] loves;
private Room room;
}
4、启动查看
注意
- @PropertySource 只对 properties ⽂件可以进⾏加载,但对于 yml 或者 yaml 不能⽀持。
- 如果读取中⽂时出现乱码,使⽤ @PropertySource(value = “classpath:student.properties”,encoding=“UTF-8”)
六、导⼊Spring配置
默认情况下,Spring Boot 中是不包含任何的 Spring 配置⽂件的,即使我们⼿动添加 Spring 配置⽂件到项⽬中,也不会被识别。
那么 Spring Boot 项⽬中真的就⽆法导⼊ Spring 配置吗?答案是否定的。
Spring Boot 为了我们提供了以下 2 种⽅式来导⼊ Spring 配置:
- 使⽤ @ImportResource 注解加载 Spring 配置⽂件
- 使⽤全注解⽅式加载 Spring 配置
1、@ImportResource注解
在主启动类上使⽤ @ImportResource 注解可以导⼊⼀个或多个 Spring 配置⽂件,并使其中的内容⽣效。
创建类
在 pojo 包下创建⼀个类 DemoA ,代码如下:
@Data
public class DemoA {
private String name;
}
引⼊ spring.xml
在 resource ⽂件夹下创建 spring.xml 配置⽂件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="da" class="com.dailyblue.java.spring.boot.pojo.DemoA">
<property name="name" value="关玉"/>
</bean>
</beans>
控制层
控制层的类注⼊刚才的 DemoA,代码如下:
@RestController
@RequestMapping("/first")
public class FirstController {
@Resource
private DemoA da;
@GetMapping("/c")
public DemoA c() {
return da;
}
}
启动类
修改启动类,引⼊ @ImportResource 注解,代码如下:
@SpringBootApplication
@ImportResource(locations = {"classpath:/spring.xml"})
public class DailyblueBootApplication {
public static void main(String[] args) {
SpringApplication.run(DailyblueBootApplication.class, args);
}
}
2、全注解⽅式加载Spring配置
Spring Boot 推荐我们使⽤全注解的⽅式加载 Spring 配置,其实现⽅式如下:
使⽤ @Configuration 注解定义配置类,替换 Spring 的配置⽂件。
配置类内部可以包含有⼀个或多个被 @Bean 注解的⽅法,这些⽅法会被AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类扫描,构建 bean 定义(相当于 Spring 配置⽂件中的标签),⽅法的返回值会以组件的形式添加到容器中,组件的 id 就是⽅法名。
代码部分很简单,这⾥仅仅书写配置加载类:
@Configuration
public class DemoB {
@Bean
public DemoC getDemoC(){
return new DemoC();
}
}
七、定制Spring MVC
Spring Boot 抛弃了传统 xml 配置⽂件,通过配置类(标注 @Configuration 的类,相当于⼀个 xml 配置⽂件)以JavaBean 形式进⾏相关配置。
Spring Boot 对 Spring MVC 的⾃动配置可以满⾜我们的⼤部分需求,但是我们也可以通过⾃定义配置类并实现WebMvcConfigurer 接⼝的⽅式来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。
SpringBoot 1.5 及以前是通过继承 WebMvcConfigurerAdapter 抽象类来定制 Spring MVC 配置的,但在SpringBoot 2.0 后,这个抽象类就被弃⽤了,改为实现 WebMvcConfigurer 接⼝来定制 Spring MVC 配置。
WebMvcConfigurer 是⼀个基于 Java 8 的接⼝,该接⼝定义了许多与 Spring MVC 相关的⽅法,其中⼤部分⽅法都是 default 类型的,且都是空实现。因此我们只需要定义⼀个配置类实现 WebMvcConfigurer 接⼝,并重写相应的⽅法便可以定制 Spring MVC 的配置。
在 Spring Boot 项⽬中,我们可以通过以下 2 中形式定制 Spring MVC:
- 扩展 Spring MVC
- 全⾯接管 Spring MVC
下⾯,我们分别对这两种定制 Spring MVC 的形式进⾏介绍。
1、扩展 Spring MVC
如果 Spring Boot 对 Spring MVC 的⾃动配置不能满⾜我们的需要,我们还可以通过⾃定义⼀个WebMvcConfigurer 类型的配置类,来扩展 Spring MVC。
这样不但能够保留 Spring Boot 对 Spring MVC 的⾃动配置,享受 Spring Boot ⾃动配置带来的便利,还能额外增加⾃定义的 Spring MVC 配置。
注意:配置类标注 @Configuration,但不标注 @EnableWebMvc 注解。
创建静态⻚⾯
在 static ⽂件夹下创建 login.html 和 index.html ⻚⾯
配置类
创建⼀个类实现 WebMvcConfigurer 接⼝
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//当访问 "/" 或 "/index.html" 时,都直接跳转到登陆⻚⾯
registry.addViewController("/").setViewName("login.html");
registry.addViewController("/index.html").setViewName("login.html");
}
}
效果
2、全⾯接管Spring MVC
在⼀些特殊情况下,我们可能需要抛弃 Spring Boot 对 Spring MVC 的全部⾃动配置,完全接管 Spring MVC。
此时我们可以⾃定义⼀个 WebMvcConfigurer 类型的配置类,并在该类上标注 @EnableWebMvc 注解,来实现完全接管 Spring MVC。
注意:完全接管 Spring MVC 后,Spring Boot 对 Spring MVC 的⾃动配置将全部失效。
就修改⼀个地⽅:
@Configuration
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//当访问 "/" 或 "/index.html" 时,都直接跳转到登陆⻚⾯
registry.addViewController("/").setViewName("login.html");
registry.addViewController("/index.html").setViewName("login.html");
}
}
引⼊ @EnableWebMvc 注解
⻚⾯效果
控制台
直接访问 login.html
Spring Boot 能够访问位于静态资源⽂件夹中的静态⽂件,这是在 Spring Boot 对 Spring MVC 的默认⾃动配置中定义的。当我们全⾯接管 Spring MVC 后,Spring Boot 对 Spring MVC 的默认配置都会失效,此时再访问静态资源⽂件夹中的静态资源就会报错了。
八、Spring Boot 拦截器
我们对拦截器并不陌⽣,⽆论是 Struts 2(一个优秀、开源、免费的MVC框架) 还是 Spring MVC 中都提供了拦截器功能,它可以根据 URL 对请求进⾏拦截。
主要应⽤于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上。
Spring Boot 同样提供了拦截器功能。
在 Spring Boot 项⽬中,使⽤拦截器功能通常需要以下 3 步:
定义拦截器
注册拦截器
指定拦截规则(如果是拦截所有,静态资源也会被拦截)
spring_boot_mvc3
项目结构
过滤器(Filter)
实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。
实现方式
1、实现Filter接口
2、继承HttpFilter(简单)
controller包
@Slf4j
@RestController
@RequestMapping("/first")
public class FirstController {
//返回的数据类型和处理乱码
// @GetMapping(produces = "application/json;charset = UTF-8")
@GetMapping
public String a() {
log.info("This is FirstController`a method!");
return "This is FirstController`a method!";
}
}
filter包
@WebFilter("/*")
//@Component
/*
此处不能使用@Component,在SpringMVC的流程中有中央控制器(DispatcherServlet),它是servlet,
而springmvc是在servlet基础上进行的操作,Filter和Servlet是同一级别,在servlet中去控制filter做不到,
因为filter比servlet优先级更高,它是对servlet拦截,在servlet之前执行,所以不能new对象。
*/
@Slf4j
public class FirstFilter extends HttpFilter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("FirstFilter被初始化了!");
}
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("FirstFilter的请求方法执行了");
chain.doFilter(request, response);//放行
log.info("FirstFilter的响应方法执行了");
}
}
启动类
@ServletComponentScan("com.zgh.filter")
@SpringBootApplication
public class SpringBootMVC3Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMVC3Application.class, args);
}
}
注意:此处如果filter没有被Tomcat加载上,就加入扫包注解@ServletComponentScan(“包路径”)
spring不管理对象,Tomcat进行管理(SpringBoot在启动的时候会有一个Tomcat)
效果
SpringMVC中也有一个类似Filter的过滤器,叫Interceptor。
1、定义拦截器
在 Spring Boot 中定义拦截器⼗分的简单,只需要创建⼀个拦截器类,并实现 HandlerInterceptor 接⼝即可。
HandlerInterceptor 接⼝中定义以下 3 个⽅法,如下表。
注:postHandle、afterCompletion的共同点和区别
相同点:都在响应之后执行
区别:postHandle:遇到异常、拦截就不会执行
afterCompletion:始终执行
2、注册拦截器
创建⼀个实现了 WebMvcConfigurer 接⼝的配置类(使⽤了 @Configuration 注解的类),重写addInterceptors() ⽅法,并在该⽅法中调⽤ registry.addInterceptor() ⽅法将⾃定义的拦截器注册到容器中。
在配置类 SpringMvcConfig 中,添加以下⽅法注册拦截器,代码如下:
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(你的拦截器类对象);
}
}
3、指定拦截规则
修改 SpringMvcConfig 配置类中 addInterceptors() ⽅法的代码,继续指定拦截器的拦截规则,代码如下:
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(你的拦截器类对象).order(1).addPathPatterns("/**") //拦截所
有请求,包括静态资源⽂件
.excludePathPatterns("/", "/login", "/index.html", "/user/login",
"/css/**", "/images/**", "/js/**", "/fonts/**"); //放⾏登录⻚,登陆操作,静态资源
}
}
在指定拦截器拦截规则时,调⽤了⼏个⽅法,这⼏个⽅法的说明如下:
- addPathPatterns:该⽅法⽤于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。
- excludePathPatterns:该⽅法⽤于排除拦截路径,即指定不需要被拦截器拦截的请求。
- order:多个拦截器执⾏顺序优先级,值越⼩优先级越⾼。
⾄此,拦截器的基本功能已经完成。
4、拦截器实现
controller包
@Slf4j
@RestController
@RequestMapping("/first")
public class FirstController {
//返回的数据类型和处理乱码
// @GetMapping(produces = "application/json;charset = UTF-8")
@GetMapping
public String a() {
log.info("This is FirstController`a method!");
return "This is FirstController`a method!";
}
@GetMapping("/b")
public String b(int a) {
log.info("This is FirstController`b method!a:" + a);
return "This is FirstController`b method!a:" + a;
}
@GetMapping("/c")
public String c(int a) {
log.info("This is FirstController`c method!a:" + a);
int m = 9 / a;
return "This is FirstController`c method!a:" + a;
}
@GetMapping("/d")
public String d() {
log.info("This is FirstController`d method!");
return "This is FirstController`d method!";
}
}
interceptor包
@Component
@Slf4j
public class FirstInterceptor implements HandlerInterceptor {
static {
log.info("我是FirstInterceptor的静态块");
}
public FirstInterceptor() {
log.info("我是FirstInterceptor的构造器");
}
// 请求方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("访问了FirstInterceptor的preHandle方法!");
return true;
}
// 响应方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("访问了FirstInterceptor的postHandle方法!");
}
// 响应方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("访问了FirstInterceptor的afterCompletion方法!");
}
}
@Component
@Slf4j
public class SecondInterceptor implements HandlerInterceptor {
static {
log.info("我是SecondInterceptor的静态块");
}
public SecondInterceptor() {
log.info("我是SecondInterceptor的构造器");
}
// 请求方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("访问了SecondInterceptor的preHandle方法!");
return true;
}
// 响应方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("访问了SecondInterceptor的postHandle方法!");
}
// 响应方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("访问了SecondInterceptor的afterCompletion方法!");
}
}
@Component
@Slf4j
public class ThirdInterceptor implements HandlerInterceptor {
static {
log.info("我是ThirdInterceptor的静态块");
}
public ThirdInterceptor() {
log.info("我是ThirdInterceptor的构造器");
}
// 请求方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("访问了ThirdInterceptor的preHandle方法!");
return true;
}
// 响应方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("访问了ThirdInterceptor的postHandle方法!");
}
// 响应方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("访问了ThirdInterceptor的afterCompletion方法!");
}
}
config包
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
//注册
@Resource
private FirstInterceptor firstInterceptor;
@Resource
private SecondInterceptor secondInterceptor;
@Resource
private ThirdInterceptor thirdInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(firstInterceptor).order(8).addPathPatterns("/first/**");
registry.addInterceptor(secondInterceptor)
.order(19).addPathPatterns("/first/**").excludePathPatterns("/first/d");
registry.addInterceptor(thirdInterceptor).order(12).addPathPatterns("/**");
}
}
启动类
@ServletComponentScan("com.zgh.filter")
@SpringBootApplication
public class SpringBootMVC3Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMVC3Application.class, args);
}
}
5、拦截器验证登陆
前端结构
util包
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResult<T> {
private Boolean success;
private Integer code;
private String error;
private T data;
}
public class ResultCode {
public static JsonResult success() {
return new JsonResult(true, 200, null, null);
}
public static JsonResult success(Object data) {
return new JsonResult(true, 200, null, data);
}
public static JsonResult error(String error) {
return new JsonResult(false, 401, error, null);
}
}
interceptor包
@Slf4j
@Component
public class IsLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("开始校验用户是否登陆");
// 验证用户是否登陆
// 获取用户传递过来的uuid
String uuid1 = request.getHeader("uuid");
String uuid2 = (request.getSession().getAttribute("uuid") == null)
? null : request.getSession().getAttribute("uuid").toString();
log.info("用户发送过来的uuid:{}",uuid1);
log.info("服务器中存放的uuid:{}",uuid2);
if (uuid1 == null) {
log.info("用户未登录");
response.getWriter().println(ResultCode.error("用户未登录"));
return false;
}
if (uuid1.equals(uuid2)) {
log.info("用户已登陆");
return true;
}
log.info("用户未登录");
response.getWriter().println(ResultCode.error("用户未登录"));
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//response.getWriter().println(ResultCode.error("用户未登录"));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// response.getWriter().println(ResultCode.error("用户未登录"));
}
}
controller包
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@PostMapping("/login")
public JsonResult login(String username, String password, HttpServletRequest request) {
// 校验登陆是否成功
if (!(username.startsWith("adm") && password.contains("123"))) {
return ResultCode.error("账号或者密码错误!");
}
// 服务端保存用户信息
HttpSession session = request.getSession();
// 生成一个唯一的卡号,和你的信息绑定
String uuid = UUID.randomUUID().toString();
log.info("生成的uuid:{}", uuid);
session.setAttribute("uuid", uuid);
// 返回用户信息
return ResultCode.success(uuid);
}
@GetMapping("/is")
public JsonResult is() {
return ResultCode.success();
}
}
static包
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{errorMsg}}<br/>
账号:<input v-model="username"/><br/>
密码:<input v-model="password" type="password"/><br/>
<button @click="login">登陆</button>
</div>
</body>
</html>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
username: '',
password: '',
errorMsg: ''
}
},
methods: {
login() {
let _this = this
let data = new URLSearchParams()
data.append('username', this.username)
data.append('password', this.password)
axios({
method: 'post',
url: 'user/login',
data: data
}).then((response) => {
if (response.data.success) {
// 保存服务器生成的uuid
let uuid = response.data.data
localStorage.setItem('uuid', uuid)
// 跳转到a.html
location.href = 'a.html'
} else {
_this.errorMsg = response.data.error
}
})
}
}
})
</script>
a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
a.html
</body>
</html>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script>
new Vue({
el: '#app',
created() {
let _this = this
axios.get('user/is', {
headers: {
uuid: localStorage.getItem('uuid')
}
})
.then((response) => {
if (!response.data.success) {
// location.href = 'login.html'
alert('用户未登录')
} else {
alert('用户已登录')
}
})
}
})
</script>
b.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
b.html
</body>
</html>
c.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
c.html
</body>
</html>
流程
用户登录验证?
1、 校验登陆是否成功
2、服务端保存用户信息
3、生成一个唯一的卡号,和你的信息绑定
4、返回用户信息
用拦截器拦截用否登录实现步骤?
原因:为了避免用户在未登录的情况下,访问一些敏感的数据或信息,此时拦截器就发挥了作用。
步骤:
1、通过拦截器拦截请求,得到用户的令牌信息(UUID1),这是用户在登录传递过来的;
再得到浏览器中存放的令牌信息(UUID2),这是用户在登录时存入的;
2、将两个UUID进行比对,若UUID1或UUID2为null,说明用户未登录;
若比对成功,继续访问;若比对失败,拦截请求,并返回错误信息。
面试题
1、过滤器和拦截器的区别?
1.运行顺序不同(如图):过滤器是在 Servlet 容器接收到请求之后,但在 Servlet被调用之前运行的;而拦截器则是在 Servlet 被调用之后,但在响应被发送到客户端之前运行的。
配置方式不同:过滤器是在 web.xml 中进行配置;而拦截器的配置则是在 Spring的配置文件中进行配置,或者使用注解进行配置。
Filter 依赖于 Servlet 容器,而 Interceptor 不依赖于 Servlet 容器
Filter 在过滤是只能对 request 和 response 进行操作,而 interceptor 可以对request、response、handler、modelAndView、exception 进行操作。
2、过滤器和拦截器谁先执行?
Filter需要在web.xml中配置,依赖于Servlet
Interceptor需要在SpringMVC中配置,依赖于框架
Filter的执行顺序在Interceptor之前
原因:filer是与servlet一个级别,springmvc相当于一个servlet,filter是在springmvc之前执行,interceptor是springmvc的一个组件,是在springmvc之后执行,因此Filter的执行顺序在Interceptor之前。
3、preHandle、postHandle、afterCompletion区别?
1、preHandle
调用时间:Controller方法处理之前 执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序一个接一个执行 若返回false,则中断执行,注意:不会进入afterCompletion
2、postHandle
调用前提:preHandle返回true 调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作 执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序倒着执行。 备注:postHandle虽然post打头,但post、get方法都能处理
3、afterCompletion
调用前提:preHandle返回true 调用时间:DispatcherServlet进行视图的渲染之后 多用于清理资源
4、登录的流程?
- 用户访输入网址导航到登录页面;
- 用户在登录页面中输入登录信息传递给前端页面;
- 前端实现表单验证,对用户名和密码进行校验,例如检查字段是否为空、格式是否有误等。
- 用户点击登录按钮后,前端发送请求给后端服务器。
- 后端服务器接收到登录请求,首先对用户输入的用户名进行验证,以确保用户账号的存在。
- 后端服务器使用安全的方式从数据库中检索存储的用户信息,包括用户名和对应的哈希密码。
- 后端服务器将用户输入的密码进行加密,通常会使用密码哈希算法(如MD5、SHA-256等)与一些安全盐(Salt)进行混合。
- 加密后的密码与从数据库中检索到的哈希密码进行比对。如果两者匹配,则表示用户输入的密码是正确的。
- 如果账号和密码验证不通过,后端服务器会将封装好的对应的错误信息发送回给前端,用户可以选择继续登录。
- 如果密码验证通过,后端服务器会生成一个令牌(Token),用于标识用户的身份认证,并在下次的请求中进行验证。
- 后端服务器将令牌作为响应的一部分发送回前端,前端会将该令牌保存在Cookie或LocalStorage中,以便后续的请求能够使用它进行身份验证。
- 用户登录成功后,可以跳转到应用的主界面或指定的页面。
5、post和get的区别?
特点不同:
GET请求的数据会附在URL之后,而POST方法提交的数据放在请求体中,所以POST方法的安全性比GET方法要高,但是执行效率却比Post方法好。
用途不同:
get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;
传输的数据量不同:
GET方法传输的数据量(有限制)一般限制在2KB,而Chrome,FireFox浏览器理论上对于URL是没有限制的,它真正的限制取决于操作系统本身;
POST方法对于数据大小是无限制的,真正影响到数据大小的是服务器处理程序的能力。
九、默认异常处理
在⽇常的 Web 开发中,会经常遇到⼤⼤⼩⼩的异常,此时往往需要⼀个统⼀的异常处理机制,来保证客户端能接收较为友好的提示。Spring Boot 同样提供了⼀套默认的异常处理机制。
Spring Boot 默认异常处理机制
Spring Boot 提供了⼀套默认的异常处理机制,⼀旦程序中出现了异常,Spring Boot 会⾃动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息。
对于浏览器⽽⾔
对于浏览器客户端⽽⾔,Spring Boot 会响应⼀个 “whitelabel” 错误视图,以 HTML 格式呈现错误信息:
对于机器客户端⽽⾔
对于机器客户端(Apipost)⽽⾔,Spring Boot 将⽣成 JSON 响应,来展示异常消息。
{
"timestamp": "2022-11-02T14:49:56.885+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/dailyblue/first/d"
}
Spring Boot异常处理⾃动配置原理
Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了⾃动配置,该配置类向容器中注⼊了以下4 个组件。
- ErrorPageCustomizer:该组件会在在系统发⽣异常后,默认将请求转发到“/error”上。
- BasicErrorController:处理默认的“/error”请求。
- DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。
- DefaultErrorAttributes:⽤于⻚⾯上共享异常信息。
下⾯,我们依次对这四个组件进⾏详细的介绍。
1、ErrorPageCustomizer
ErrorMvcAutoConfiguration 向容器中注⼊了⼀个名为 ErrorPageCustomizer 的组件,它主要⽤于定制错误⻚⾯的响应规则。
可以指定特定的错误页面来处理不同类型的错误。例如,您可以将特定的HTTP状态代码映射到自定义的错误页面。
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath
dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
ErrorPageCustomizer 通过 registerErrorPages() ⽅法来注册错误⻚⾯的响应规则。当系统中发⽣异常后,ErrorPageCustomizer 组件会⾃动⽣效,并将请求转发到 “/error”上,交给 BasicErrorController 进⾏处理,其部分代码如下:
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//将请求转发到 /errror(this.properties.getError().getPath())上
ErrorPage errorPage
= new
ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath
()));
// 注册错误⻚⾯
errorPageRegistry.addErrorPages(errorPage);
}
2、BasicErrorController
当系统发生异常并被转发到"/error"路径时,BasicErrorController会根据异常的类型和HTTP状态代码,返回相应的错误响应。
ErrorMvcAutoConfiguration 还向容器中注⼊了⼀个错误控制器组件 BasicErrorController,代码如下:
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search =
SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver>
errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
BasicErrorController 的定义如下:
//BasicErrorController ⽤于处理 “/error” 请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
......
/**
* 该⽅法⽤于处理浏览器客户端的请求发⽣的异常
* ⽣成 html ⻚⾯来展示异常信息
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse
response) {
//获取错误状态码
HttpStatus status = getStatus(request);
//getErrorAttributes 根据错误信息来封装⼀些 model 数据,⽤于⻚⾯显示
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request,
getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
//为响应对象设置错误状态码
response.setStatus(status.value());
//调⽤ resolveErrorView() ⽅法,使⽤错误视图解析器⽣成 ModelAndView 对象(包含错误⻚⾯地址和⻚⾯内容)
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error",
model);
}
/**
* 该⽅法⽤于处理机器客户端的请求发⽣的错误
* 产⽣ JSON 格式的数据展示错误信息
*/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request,
getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
......
}
Spring Boot 通过 BasicErrorController 进⾏统⼀的错误处理(例如默认的“/error”请求)。Spring Boot 会⾃动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() ⽅法进⾏处理。
换句话说,当使⽤浏览器访问出现异常时,会进⼊ BasicErrorController 控制器中的 errorHtml() ⽅法进⾏处理,当使⽤安卓、IOS、Postman 等机器客户端访问出现异常时,就进⼊error() ⽅法处理。
在 errorHtml() ⽅法中会调⽤⽗类(AbstractErrorController)的 resolveErrorView() ⽅法,代码如下:
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse
response, HttpStatus status,Map<String, Object> model) {
//获取容器中的所有的错误视图解析器来处理该异常信息
for (ErrorViewResolver resolver : this.errorViewResolvers) {
//调⽤错误视图解析器的 resolveErrorView 解析到错误视图⻚⾯
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
从上述源码可以看出,在响应⻚⾯的时候,会在⽗类的 resolveErrorView ⽅法中获取容器中所有的ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),⼀起来解析异常信息。
3、DefaultErrorViewResolver
DefaultErrorViewResolver根据异常的类型、HTTP状态代码和配置的错误视图路径来决定使用哪个错误视图。
可以通过自定义DefaultErrorViewResolver来定制错误视图的解析方式。
ErrorMvcAutoConfiguration 还向容器中注⼊了⼀个默认的错误视图解析器组件 DefaultErrorViewResolver,代码如下:
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调⽤它们的 resolveErrorView() ⽅法对异常信息进⾏解析,其中⾃然也包括 DefaultErrorViewResolver(默认错误信息解析器)。
DefaultErrorViewResolver 的部分代码如下:
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<HttpStatus.Series, String> SERIES_VIEWS;
static {
Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
......
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
//尝试以错误状态码作为错误⻚⾯名进⾏解析
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//尝试以 4xx 或 5xx 作为错误⻚⾯⻚⾯进⾏解析
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//错误模板⻚⾯,例如 error/404、error/4xx、error/500、error/5xx
String errorViewName = "error/" + viewName;
//当模板引擎可以解析这些模板⻚⾯时,就⽤模板引擎解析
TemplateAvailabilityProvider provider =
this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//在模板能够解析到模板⻚⾯的情况下,返回 errorViewName 指定的视图
return new ModelAndView(errorViewName, model);
}
//若模板引擎不能解析,则去静态资源⽂件夹下查找 errorViewName 对应的⻚⾯
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍历所有静态资源⽂件夹
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
//静态资源⽂件夹下的错误⻚⾯,例如error/404.html、error/4xx.html、
error / 500. html、error / 5 xx.html
resource = resource.createRelative(viewName + ".html");
//若静态资源⽂件夹下存在以上错误⻚⾯,则直接返回
if (resource.exists()) {
return new ModelAndView(new
DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception ex) {
}
}
return null;
}
......
}
DefaultErrorViewResolver 解析异常信息的步骤如下:
- 根据错误状态码(例如 404、500、400 等),⽣成⼀个错误视图 error/status,例如 error/404、
error/500、error/400。
- 尝试使⽤模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates ⽬录下,查找
error/status.html,例如 error/404.html、error/500.html、error/400.html。
- 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流
程,否则跳转到第 4 步。
- 依次从各个静态资源⽂件夹中查找 error/status.html,若在静态⽂件夹中找到了该错误⻚⾯,则返回并结束
整个解析流程,否则跳转到第 5 步。
- 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结
束整个解析流程,否则跳转第 6 步。
- 处理默认的 “/error ”请求,使⽤ Spring Boot 默认的错误⻚⾯(Whitelabel Error Page)。
4、DefaultErrorAttributes
DefaultErrorAttributes是一个错误属性类,用于在页面上共享异常信息。
当处理异常时,DefaultErrorAttributes会从异常对象中提取相关属性,例如错误消息、异常堆栈等,
并将它们添加到模型中,以便将这些信息显示在错误视图上。
ErrorMvcAutoConfiguration 还向容器中注⼊了⼀个组件默认错误属性处理⼯具 DefaultErrorAttributes,代码如下:
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search =
SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理⼯具,它可以从请求中获取异常或错误信息,并将其封装为⼀个 Map 对象返回,其部分代码如下:
public class DefaultErrorAttributes implements ErrorAttributes,
HandlerExceptionResolver, Ordered {
......
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest,
options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") !=
null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean
includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
......
}
在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调⽤ DefaultErrorAttributes 的getErrorAttributes() ⽅法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到⻚⾯或 JSON 数据中。该 model 数据主要包含以下属性:
- timestamp:时间戳;
- status:错误状态码
- error:错误的提示
- exception:导致请求处理失败的异常对象
- message:错误/异常消息
- trace: 错误/异常栈信息
- path:错误/异常抛出时所请求的URL路径
十、全局异常处理
在项⽬开发中出现异常时很平常不过的事情,我们处理异常也有很多种⽅式。可能如下:
public int div(int a ,int b){
int c = 0;
try{
c = a / b;
}catch (Exception ex){
ex.printStackTrace();
}
return c;
}
如果我们这样处理异常,代码中就会出现特别多的异常处理模块,这样代码可读性就会变得⾮常差,⽽且业务模块逻辑会夹杂特别多的⾮业务逻辑。但是在项⽬开发的过程中我们应该将主要精⼒放在业务模块,除了必要的异常处理模块最好不要再包含其他⽆关紧要的代码。
那么我们如何处理项⽬中⽆处不在的异常呢?这就引出了我们要介绍的全局异常处理⽅法,主要有两种⽅式:
- 使⽤ @RestControllerAdvice 和 @ExceptionHandler 注解
- 使⽤ ErrorController 类来实现
1、@RestControllerAdvice⽅式
全局异常处理类
书写⼀个全局异常处理类,代码如下:
// 注解@RestControllerAdvice表示这是⼀个控制器增强类,当控制器发⽣异常且符合类中定义的拦截异常类,将会被拦截。
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 对那些异常进⾏拦截
@ExceptionHandler(Exception.class)
public JsonResult globalException(HttpServletResponse response, Exception e) {
log.info("进⼊了全局异常处理⽅法");
log.info("异常信息是:{}", e.getMessage());
return ResultTool.fail(e.getMessage());
}
}
注:
e.getMessage()
是获取异常信息的方法
控制器
书写⼀个控制器,代码如下:
@RequestMapping("/second")
@RestController
public class SecondController {
@RequestMapping("/login")
public String login() {
System.out.println("aaa");
return "login";
}
// 这个⽅法会出现错误
@RequestMapping("/b")
public String b() {
System.out.println("bbb");
System.out.println(9/0);
return "b";
}
}
启动
启动并调⽤错误⽅法,⻚⾯效果:
控制台效果:
2、使⽤ErrorController类
系统默认的错误处理类为 BasicErrorController,当出现错误时显示默认的错误⻚⾯。这⾥编写⼀个⾃⼰的错误处理类,上⾯默认的处理类将不会起作⽤。
getErrorPath()返回的路径服务器将会重定向到该路径对应的处理类,本例中为error⽅法。
@RestController
@Slf4j
public class HttpErrorController implements ErrorController {
private final static String ERROR_PATH = "/error";
@RequestMapping(path = ERROR_PATH)
public JsonResult error(HttpServletRequest request, HttpServletResponse response) {
log.info("访问/error" + " 错误代码:" + response.getStatus());
return ResultTool.fail();
}
public String getErrorPath() {
return ERROR_PATH;
}
}
还是⽤刚才的错误控制器⽅法,继续启动并访问,浏览器效果:
控制台效果:
两者区别
注解 @RestControllerAdvice ⽅式处理控制器抛出的异常或所定义的异常。此时请求已经进⼊控制器中。
类 ErrorController ⽅式可以处理所有的异常,包括未进⼊控制器的错误,⽐如404,401等错误
如果应⽤中两者共同存在,则 @RestControllerAdvice ⽅式处理控制器抛出的异常,类 ErrorController ⽅式
未进⼊控制器的异常。
- @RestControllerAdvice ⽅式可以定义多个拦截⽅法,拦截不同的异常类,并且可以获取抛出的异常信息,⾃
由度更⼤。
4、spring_boot_mvc4
项目结构
第一种方式(推荐)
controller包
@RestController
@RequestMapping("/first")
public class FirstController {
@Resource
private FirstService service;
@GetMapping("/a")
public JsonResult a() {
int a = 9 / 0;
return ResultTool.success();
}
@GetMapping("/b")
public JsonResult b() {
return ResultTool.success();
}
@GetMapping("/c")
public JsonResult c(){
return service.a();
}
}
handler包
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public JsonResult nullException(Exception e) {
log.info("代码出现了异常情况!");
log.info("异常信息是:{}", e.getMessage());
return ResultTool.error(e.getMessage());
}
@ExceptionHandler(ArithmeticException.class)
public JsonResult arithmeticException(Exception e) {
log.info("代码出现了异常情况!");
log.info("异常信息是:{}", e.getMessage());
return ResultTool.error(e.getMessage());
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public JsonResult arrayIndexOutOfBoundsException(Exception e) {
log.info("代码出现了异常情况!");
log.info("异常信息是:{}", e.getMessage());
return ResultTool.error(e.getMessage());
}
}
service包
public interface FirstService {
JsonResult a();
}
@Service
public class FirstServiceImpl implements FirstService {
@Override
public JsonResult a() {
String[] a={"aa", "bb"};
a[4]="bbb";
return ResultTool.success(a);
}
}
util包
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult<T> {
private boolean success;
private Integer code;
private String error;
private T data;
}
public class ResultTool {
public static JsonResult success() {
return new JsonResult(true, 200, null, null);
}
public static JsonResult success(Object data) {
return new JsonResult(true, 200, null, data);
}
public static JsonResult error(String error) {
return new JsonResult(false, 401, error, null);
}
}
启动类
@SpringBootApplication
public class SpringBootMVC4Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMVC4Application.class, args);
}
}
效果
第二种方式
注:注释掉@RestControllerAdvice
//@RestControllerAdvice
handler包
@Slf4j
@RestController
@RequestMapping("/error")
public class HttpErrorController implements ErrorController {
@GetMapping
public JsonResult global() {
log.info("我处理错误信息");
return ResultTool.error("error");
}
public String getErrorPath() {
return "/error";
}
}
效果
十一、整合MyBatis-Plus
对于数据访问层,⽆论是 SQL(关系型数据库) 还是 NOSQL(⾮关系型数据库),
Spring Boot 都默认采⽤整合Spring Data 的⽅式进⾏统⼀处理,通过⼤量⾃动配置,来简化我们对数据访问层的操作,
⽽如果我们要使⽤MyBatis-Plus,只需要引⼊ MyBatis-Plus 的启动器就⾏。
概述
MyBatis-Plus (opens new window)(简称 MP)是⼀个 MyBatis (opens new window)的增强⼯具,在 MyBatis的基础上只做增强不做改变,为简化开发、提⾼效率⽽⽣。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂⽃罗 中的 1P、2P,基友搭配,效率翻倍。
特性
- ⽆侵⼊:只做增强不做改变,引⼊它不会对现有⼯程产⽣影响,如丝般顺滑
- 损耗⼩:启动即会⾃动注⼊基本 CURD,性能基本⽆损耗,直接⾯向对象操作
- 强⼤的 CRUD 操作:内置通⽤ Mapper、通⽤ Service,仅仅通过少量配置即可实现单表⼤部分 CRUD 操作,更有强⼤的条件构造器,满⾜各类使⽤需求
- ⽀持 Lambda 形式调⽤:通过 Lambda 表达式,⽅便的编写各类查询条件,⽆需再担⼼字段写错
- ⽀持主键⾃动⽣成:⽀持多达 4 种主键策略(内含分布式唯⼀ ID ⽣成器 - Sequence),可⾃由配置,完美解决主键问题
- ⽀持 ActiveRecord 模式:⽀持 ActiveRecord 形式调⽤,实体类只需继承 Model 类即可进⾏强⼤的 CRUD 操作
- ⽀持⾃定义全局通⽤操作:⽀持全局通⽤⽅法注⼊( Write once, use anywhere )
- 内置代码⽣成器:采⽤代码或者 Maven 插件可快速⽣成 Mapper 、 Model 、 Service 、 Controller 层代码,⽀持模板引擎,更有超多⾃定义配置等您来使⽤
- 内置分⻚插件:基于 MyBatis 物理分⻚,开发者⽆需关⼼具体操作,配置好插件之后,写分⻚等同于普通List 查询
- 分⻚插件⽀持多种数据库:⽀持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执⾏时间,建议开发测试时启⽤该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可⾃定义拦截规则,预防误操作
1、spring_boot_mybatic_plus
入门案例
项目结构
我们将通过⼀个简单的 Demo 来阐述 MyBatis-Plus 的强⼤功能。
引⼊依赖
<dependencies>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
<!-- mybatic_puls-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<!-- 测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
配置数据源
在 application.yml 配置⽂件中添加 MySQL 数据库的相关配置:
# DataSource Config
spring:
datasource:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test
实体类
我们以经典的 Emp 表作为实例,进⾏讲解,编写对应的实体 Emp 类。
@Data
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private String hiredate;
private Double sal;
private Double comm;
private Integer deptno;
private Integer empstate;
}
//添加注解后
@TableName("emp")
@Data
public class Emp implements Serializable {
@TableId(value = "empno", type = IdType.AUTO)
private Integer empNo; // 自动---emp_no
private String ename;
private String job;
private Integer mgr;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String hireDate;
private Double sal;
private Double comm;
//@TableField("deptno")
private Integer deptNo;
@TableLogic(delval = "0", value = "1")
private Integer state;
@TableField(exist = false)
private String dname;
}
Mapper 类
编写 mapper 包下的 EmpMapper 接⼝
@Mapper
public interface EmpMapper extends BaseMapper<Emp> {
List<Emp> find16(Emp emp);
}
业务层
编写 service 包下的 EmpService 接⼝
public interface EmpService extends IService<Emp> {
}
编写 service.impl 包下的 EmpServiceImpl 实现类
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
}
开始使⽤
添加测试类,进⾏功能测试:
@SpringBootTest
@Slf4j
public class App {
@Resource
private EmpService service;
@Test
public void find() {
List<Emp> list = service.list();
list.forEach(System.out::println);
}
}
启动类
@SpringBootApplication
public class SpringBootMybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisPlusApplication.class,args);
}
}
启动后控制台输出
问题:数据库字段和实体类中的属性对不上
解决方法:
1、通过注解
@TableName("emp")
@Data
public class Emp implements Serializable {
@TableId(value = "empno", type = IdType.AUTO)
private Integer empNo; // 自动---emp_no
private String ename;
private String job;
private Integer mgr;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String hireDate;
private Double sal;
private Double comm;
//@TableField("deptno")
private Integer deptNo;
@TableLogic(delval = "0", value = "1")
private Integer state;
@TableField(exist = false)
private String dname;
}
2、通过配置
# 关闭字段名下划线自动转驼峰标记
mybatis-plus:
configuration:
map-underscore-to-camel-case: false
# 设置日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
测试效果
总结
通过以上⼏个简单的步骤,我们就实现了 Emp 表的 CRUD 功能,甚⾄连 XML ⽂件都不⽤编写!
从以上步骤中,我们可以看到集成 MyBatis-Plus ⾮常的简单,只需要引⼊ starter ⼯程,并配置 mapper 扫描路径即可。
但 MyBatis-Plus 的强⼤远不⽌这些功能,想要详细了解 MyBatis-Plus 的强⼤功能?那就继续往下看吧!
2、MyBatis-Plus注解
介绍 Mybatis-Plus 注解包相关类详解(更多详细描述可点击查看源码注释)
传动⻔:mybatis-plus: mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com - Gitee.com
@TableName
描述:表名注解,标识实体类对应的表
使⽤位置:实体类
@Data
@TableName(value = "emp")
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private String hiredate;
private Double sal;
private Double comm;
private Integer deptno;
private Integer empstate;
}
注意: @TableName 注解可省略,如省略对应类名的表
属性列表:
关于 autoResultMap 的说明:
MP 会⾃动构建⼀个 resultMap 并注⼊到 MyBatis ⾥(⼀般⽤不上),请注意以下内容:
因为 MP 底层是 MyBatis,所以 MP 只是帮您注⼊了常⽤ CRUD 到 MyBatis ⾥,注⼊之前是动态的(根据您的
Entity 字段以及注解变化⽽变化),但是注⼊之后是静态的(等于 XML 配置中的内容)。
⽽对于 typeHandler 属性,MyBatis 只⽀持写在 2 个地⽅:
定义在 resultMap ⾥,作⽤于查询结果的封装
定义在 insert 和 update 语句的 #{property} 中的 property 后⾯(例: #
{property,typehandler=xxx.xxx.xxx} ),并且只作⽤于当前 设置值
除了以上两种直接指定 typeHandler 的形式,MyBatis 有⼀个全局扫描⾃定义 typeHandler 包的配置,原理是
根据您的 property 类型去找其对应的 typeHandler 并使⽤。
@TableId
描述:主键注解
使⽤位置:实体类主键字段
@Data
@TableName(value = "emp")
public class Emp implements Serializable {
@TableId(value = "empno",type = IdType.AUTO)
private Integer empno;
// 其他省略
}
属性列表:
type属性概述:
@TableField
描述:字段注解(⾮主键)
@Data
@TableName(value = "emp")
public class Emp implements Serializable {
@TableField(value = "ename")
private String ename;
@TableField(value = "hiredate")
private String hireDate;
// 其他省略
}
属性列表:
关于 jdbcType 和 typeHandler 以及 numericScale 的说明:
numericScale只⽣效于 update 的 sql. jdbcType和typeHandler如果不配合@TableName#autoResultMap
= true⼀起使⽤,也只⽣效于 update 的 sql. 对于typeHandler如果你的字段类型和 set 进去的类型为equals
关系,则只需要让你的typeHandler让 Mybatis 加载到即可,不需要使⽤注解。
@TableLogic
描述:表字段逻辑处理注解(逻辑删除)
3、逻辑删除
我们在实际使⽤中的删除操作,其实并没有删除表中的数据,仅仅是将表的数据隐藏掉,查询时不查询这些隐藏的数据。
分类
- 全局⽅式
- 局部⽅式
全局⽅式
在 application.yml 中进⾏配置:
mybatis-plus:
global-config:
db-config:
logic-delete-field: state # 全局逻辑删除的实体字段名
logic-delete-value: 0 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)
注意:配置好后,所有实体不需额外操作,但必须保证存在 state 字段。
启动后控制台输出
注:查询的sql语句发生变化,查询只查状态为1的,删除时会将状态改为0。
test包
@Test
public void delete() {
service.removeById(7900);
}
启动后控制台输出
局部⽅式
需要在每个所需实体类中引⼊ @TableLogic 注解。
@TableLogic(delval = "0", value = "1")
private Integer state;
启动后控制台输出
说明
只对⾃动注⼊的 sql 起效:
-
插⼊: 不作限制
-
查找: 追加 where 条件过滤掉已删除数据,如果使⽤ wrapper.entity ⽣成的 where 条件也会⾃动追加该字段
-
更新: 追加 where 条件防⽌更新到已删除数据,如果使⽤ wrapper.entity ⽣成的 where 条件也会⾃动追加该字段
-
删除: 转变为 更新
例如:
- 删除: update user set deleted=1 where id = 1 and deleted=0
- 查找: select id,name,deleted from user where deleted=0
字段类型⽀持说明:
- ⽀持所有数据类型(推荐使⽤ Integer,Boolean,LocalDateTime)
- 如果数据库字段使⽤ datetime ,逻辑未删除值和已删除值⽀持配置为字符串null,另⼀个值⽀持配置为函数来获取值如now()
附录:
- 逻辑删除是为了⽅便数据恢复和保护数据本身价值等等的⼀种⽅案,但实际就是删除。
- 如果你需要频繁查出来看就不应使⽤逻辑删除,⽽是以⼀个状态去表示。
4、⾃动填充功能
在数据表的设计中,经常需要加⼀些字段,如:创建时间,最后修改时间等,此时可以使⽤ MyBatis-Plus 来帮我们进⾏⾃动维护。
填充⽅式
⾃动填充⽅式有两种,分别是:
- 通过数据库完成⾃动填充
- 使⽤程序完成⾃动填充
注:只有添加和修改有自动填充
数据库⾃动填充
这⾥没什么好说的,就是正常书写就⾏,在添加时,有默认值的列所对应的属性不赋值,数据库会⾃动赋予默认值。
程序⾃动填充
1、添加的自动填充
// 注意!这⾥需要标记为填充字段
@TableField(fill = FieldFill.INSERT)
private String hireDate;
@TableName("emp")
@Data
public class Emp implements Serializable {
@TableId(value = "empno", type = IdType.AUTO)
private Integer empNo; // 自动---emp_no
private String ename;
private String job;
private Integer mgr;
@TableField(fill = FieldFill.INSERT)
private String hireDate;
private Double sal;
private Double comm;
//@TableField("deptno")
private Integer deptNo;
@TableLogic(delval = "0", value = "1")
private Integer state;
@TableField(exist = false)
private String dname;
}
这⾥就给 hireDate 属性设置了⾃动填充,如果该属性没有值,则⾃动填充 Null 。
测试类:
@Test
public void save() {
Emp emp = new Emp();
emp.setEname("周国豪");
emp.setJob("页面设计");
emp.setSal(9999.0);
service.save(emp);
}
效果:
改进
改进刚才的内容,我们想要在未给 hireDate 属性赋值时,系统赋予当前时间。
handler包
这时我们要引⼊⾃定义实现类 DateHandler
@Component
@Slf4j
public class DateHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始录入当前时间");
this.strictInsertFill(metaObject, "hireDate", this::getNowDate, String.class);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始录入当前时间");
this.strictUpdateFill(metaObject, "hireDate", this::getNowDate, String.class);
}
private String getNowDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date());
}
}
启动后控制台输出
2、修改的自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)//修改
private String hireDate;
@TableName("emp")
@Data
public class Emp implements Serializable {
@TableId(value = "empno", type = IdType.AUTO)
private Integer empNo; // 自动---emp_no
private String ename;
private String job;
private Integer mgr;
// @TableField(fill = FieldFill.INSERT)//添加
@TableField(fill = FieldFill.INSERT_UPDATE)//修改
private String hireDate;
private Double sal;
private Double comm;
//@TableField("deptno")
private Integer deptNo;
@TableLogic(delval = "0", value = "1")
private Integer state;
@TableField(exist = false)
private String dname;
}
说明
- 填充原理是直接给 entity 的属性设置值!!!
- 注解则是指定该属性在对应情况下必有值,如果⽆值则⼊库会是 null
- MetaObjectHandler 提供的默认⽅法的策略均为:如果属性有值则不覆盖,如果填充值为 null 则不填充
- 字段必须声明 TableField 注解,属性 fill 选择对应策略,该声明告知 Mybatis-Plus 需要预留注⼊ SQL字段
- 填充处理器 NowDateHandler 在 Spring Boot 中需要声明 @Component 或 @Bean 注⼊
- 要想根据注解 FieldFill.xxx 和字段名以及字段类型来区分必须使⽤⽗类的 strictInsertFill 或者strictUpdateFill ⽅法
- 不需要根据任何来区分可以使⽤⽗类的 fillStrategy ⽅法
- update(T t,Wrapper updateWrapper) 时 t 不能为空,否则⾃动填充失效
5、条件构造器
在Mybatis-Plus中提了构造条件的类 Wrapper ,它可以根据⾃⼰的意图定义我们需要的条件。
Wrapper 是⼀个抽象类,⼀般情况下我们⽤它的⼦类 QueryWrapper 来实现⾃定义条件查询。
API
AbstractWrapper
说明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的⽗类
⽤于⽣成 sql 的 where 条件, entity 属性也⽤于⽣成 sql 的 where 条件
注意: entity ⽣成的 where 条件与 使⽤各个 api ⽣成的 where 条件没有任何关联⾏为
QueryWrapper
说明:
继承⾃ AbstractWrapper ,⾃身的内部属性 entity 也⽤于⽣成 where 条件
及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() ⽅法获取
select
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
- 设置查询字段
说明:
以上⽅法分为两类.
第⼆类⽅法为:过滤查询字段(主键除外),⼊参不包含 class 的调⽤前需要 wrapper 内的 entity 属性有值!
这两类⽅法重复调⽤以最后⼀次为准
- 例: select(“id”, “name”, “age”)
- 例: select(i -> i.getProperty().startsWith(“test”))
UpdateWrapper
说明:
继承⾃ AbstractWrapper ,⾃身的内部属性 entity 也⽤于⽣成 where 条件
及 LambdaUpdateWrapper , 可以通过 new UpdateWrapper().lambda() ⽅法获取!
set
set(String column, Object val)
set(boolean condition, String column, Object val)
- SQL SET 字段
- 例: set(“name”, “⽼李头”)
- 例: set(“name”, “”) —>数据库字段值变为空字符串
- 例: set(“name”, null) —>数据库字段值变为 null
setSQL
setSql(String sql)
- 设置 SET 部分 SQL
- 例: setSql(“name = ‘⽼李头’”)
基于spring_boot_mybatic_plus
service包
public interface EmpService extends IService<Emp> {
// select * from emp where state=1 and job=?
List<Emp> find1(String job);
List<Emp> find2(String job, double sal);
List<Emp> find3(String job, double sal);
List<Emp> find4();
List<Emp> find5(String name);
List<Emp> find6(double a, double b);
List<Emp> find7(String fieldName);
Emp find8(String ename);
List<Map<String, Object>> find9(String fieldName);
List<Emp> find10(String... fields);
Emp find11();
List<Emp> find12();
List<Emp> find13(int size);
//更新
void update(Emp emp);
//分页
PageInfo<Emp> find14(int page, int size);
PageInfo<Emp> find15(int page, int size);
}
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
@Resource
private EmpMapper mapper;
// 步骤:
// 1、产生对象
// 2、设置条件
// 3、查询
@Override
public List<Emp> find1(String job) {
// 设置条件
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.eq("job", job);// =
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find2(String job, double sal) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.eq("job", job);// =
wrapper.gt("sal", sal);// >
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find3(String job, double sal) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.eq("job", job).or().gt("sal", sal);// = or >
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find4() {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.isNull("comm");//没有奖金的员工
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find5(String name) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
// wrapper.like("ename",name);//模糊
// wrapper.likeRight("ename",name);//向右模糊
wrapper.likeLeft("ename", name);//向左模糊
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find6(double a, double b) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.between("sal", a, b);//工资在[a,b]之间
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find7(String fieldName) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.orderByAsc(fieldName);//按名字升序
wrapper.orderByDesc(fieldName);//按名字降序
return mapper.selectList(wrapper);
}
@Override
public Emp find8(String ename) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.eq("eanme", ename);//按名字查询一行数据
return mapper.selectOne(wrapper);
}
@Override
public List<Map<String, Object>> find9(String fieldName) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.groupBy(fieldName);//按名字分组
wrapper.select(fieldName);//显示那些列
return mapper.selectMaps(wrapper);
}
@Override
public List<Emp> find10(String... fields) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.select(fields);//传入哪些列,显示哪些列
return mapper.selectList(wrapper);
}
@Override
public Emp find11() {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.select("count(0) as 'empno");//查询总的条数
return mapper.selectOne(wrapper);
}
@Override
public List<Emp> find12() {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
//统计各个部门的最高工资
wrapper.select("max(sal) as 'sal',job");
wrapper.groupBy("job");
return mapper.selectList(wrapper);
}
@Override
public List<Emp> find13(int size) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
wrapper.last("limit" + size);//分页查询,每页size条
return mapper.selectList(wrapper);
}
@Override
public void update(Emp emp) {
UpdateWrapper<Emp> wrapper = new UpdateWrapper<>();
//修改部分字段值
wrapper.set("ename", emp.getEname());
wrapper.set("sal", emp.getSal());
wrapper.eq("empno", emp.getEmpNo());
// mapper.update(emp,wrapper);
update(wrapper); // service
}
@Override
public PageInfo<Emp> find14(int page, int size) {
//分页查询(搭配分页依赖使用)
PageHelper.startPage(page, size);
List<Emp> list = list();
PageInfo<Emp> pageInfo = new PageInfo<>(list);
return pageInfo;
}
@Override
public PageInfo<Emp> find15(int page, int size) {
QueryWrapper<Emp> wrapper = new QueryWrapper<>();
//对已知字段查询的结果后进行分页
wrapper.eq("job", "程序员");
PageHelper.startPage(page, size);
List<Emp> list = list(wrapper);
return new PageInfo<>(list);
}
}
步骤:
1、产生对象
2、设置条件
3、查询
test包
//用于启动一个Spring Boot应用作为测试环境。
@SpringBootTest
@Slf4j
public class App {
@Resource
private EmpService service;
@Test
public void find() {
List<Emp> list = service.list();
list.forEach(System.out::println);
}
@Test
public void delete() {
service.removeById(7900);
}
@Test
public void save() {
Emp emp = new Emp();
emp.setEname("赵佳旺");
emp.setJob("页面设计");
emp.setSal(9999.0);
service.save(emp);
}
@Test
public void find1() {
service.find1("程序员").forEach(System.out::println);
}
@Test
public void find2() {
service.find2("程序员", 2000).forEach(System.out::println);
}
@Test
public void find3() {
service.find3("程序员", 2000).forEach(System.out::println);
}
@Test
public void find4() {
service.find4().forEach(System.out::println);
}
@Test
public void find5() {
service.find5("a").forEach(System.out::println);
}
@Test
public void find6() {
service.find6(1890, 1999).forEach(System.out::println);
}
@Test
public void find7() {
service.find7("SAL").forEach(System.out::println);
}
@Test
public void find8() {
System.out.println(service.find8("董雪"));
}
@Test
public void find9() {
service.find9("job").forEach(System.out::println);
}
@Test
public void find10() {
service.find10("job").forEach(System.out::println);
}
@Test
public void find11() {
System.out.println(service.find11());
}
@Test
public void find12() {
service.find12().forEach(System.out::println);
}
@Test
public void find13() {
service.find13(10).forEach(System.out::println);
}
@Test
public void update() {
Emp emp = new Emp();
emp.setEmpNo(7966);
emp.setEname("李思前");
emp.setSal(9999.9);
emp.setMgr(7878);
service.update(emp);
}
@Test
public void find14() {
System.out.println(service.find14(1, 10));
}
@Test
public void find15() {
System.out.println(service.find15(2, 3));
}
}
6、使⽤ XML配置
有的时候对于⼀些复杂 SQL 语句,尤其是动态SQL,使⽤注解⽐较麻烦,我们可以通过 XML 来书写。
application.yml 引⼊
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml # mapper ⽂件位置
根据接⼝⽣成xml配置⽂件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.dailyblue.java.spring.boot.mapper.EmpMapper">
<select id="list" resultType="com.dailyblue.java.spring.boot.bean.Emp">
select * from emp where empstate=1
</select>
</mapper>
application.yml
# MybatisPlus
mybatis-plus:
global-config:
db-config:
column-underline: true # 驼峰形式
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
db-type: mysql
id-type: assign_id # id策略
table-prefix: t_ # 配置表的默认前缀
mapper-locations: classpath*:/mapper/**Mapper.xml # mapper ⽂件位置
type-aliases-package: com.dailyblue.java.mybatis.bean # 实体类别名
config-location: classpath*:/config.xml # config.xml ⽂件位置
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # ⽇志:打印sql 语句
map-underscore-to-camel-case: false # 驼峰⾃动转换
基于spring_boot_mybatic_plus
resources——>mapper包
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zgh.mapper.EmpMapper">
<select id="find16" parameterType="com.zgh.bean.Emp" resultType="com.zgh.bean.Emp">
select * from emp
<where>
<if test="ename!=null">
and ename like#{ename}
</if>
<if test="job!=null">
and job=#{job}
</if>
</where>
</select>
</mapper>
优化
1、application.yml
mapper-locations: classpath*:/mapper/**Mapper.xml # mapper ⽂件位置
type-aliases-package: com.zgh.bean.Emp # 实体类别名
2、EmpMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zgh.mapper.EmpMapper">
<select id="find16" parameterType="Emp" resultType="Emp">
select * from emp
<where>
<if test="ename!=null">
and ename like#{ename}
</if>
<if test="job!=null">
and job=#{job}
</if>
</where>
</select>
</mapper>
mapper包
@Mapper
public interface EmpMapper extends BaseMapper<Emp> {
List<Emp> find16(Emp emp);
}
service包
public interface EmpService extends IService<Emp> {
List<Emp> find16(Emp emp);
}
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
@Resource
private EmpMapper mapper;
@Override
public List<Emp> find16(Emp emp) {
return mapper.find16(emp);
}
}
test包
//用于启动一个Spring Boot应用作为测试环境。
@SpringBootTest
@Slf4j
public class App {
@Test
public void find16() {
Emp emp = new Emp();
emp.setJob("程序员");
service.find16(emp).forEach(System.out::println);
}
}
启动后控制台输出
十二、Spring Boot处理跨域
什么是跨域
浏览器从⼀个域名的⽹⻚去请求另⼀个域名的资源时,协议、域名(主机)、端⼝任⼀不同,都是跨域。
在前后端分离的模式下,前后端的域名是不⼀致的,此时就会发⽣跨域访问问题。
跨域出于浏览器的同源策略限制。
- 同源策略是⼀种约定,它是浏览器最核⼼也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
- 可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的⼀种实现。
- 同源策略会阻⽌⼀个域的javascript脚本和另外⼀个域的内容进⾏交互。
- 所谓同源(即指在同⼀个域)就是两个⻚⾯具有相同的协议(protocol),主机(host)和端⼝号(port)。
例如:a ⻚⾯想获取 b ⻚⾯资源,如果 a、b ⻚⾯的协议、域名、端⼝、⼦域名不同,所进⾏的访问⾏动都是跨域的,
⽽浏览器为了安全问题⼀般都限制了跨域访问,也就是不允许跨域请求资源。
注意:跨域限制访问,其实是浏览器的限制。理解这⼀点很重要!!!
解决⽅案
对于 CORS的跨域请求,主要有以下⼏种⽅式可供选择:
- 返回新的CorsFilter
- 重写 WebMvcConfigurer
- 使⽤注解 @CrossOrigin
- ⼿动设置响应头 (HttpServletResponse)
- ⾃定web filter 实现跨域
注意
- CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才⽀持,对应springBoot1.3版本以上
- 上⾯前两种⽅式属于全局 CORS 配置,后两种属于局部 CORS配置。如果使⽤了局部跨域是会覆盖全局跨域的规则,所以可以通过 @CrossOrigin 注解来进⾏细粒度更⾼的跨域资源控制。
- 其实⽆论哪种⽅案,最终⽬的都是修改响应头,向响应头中添加浏览器所要求的数据,进⽽实现跨域。
1、返回新的CorsFilter
在任意配置类,返回⼀个新的 CorsFilter 的对象 ,并添加映射路径和具体的 CORS 配置路径。
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放⾏哪些原始域
config.addAllowedOrigin("*");
//放⾏哪些请求⽅式
config.addAllowedMethod("*");
//放⾏哪些原始请求头部信息
config.addAllowedHeader("*");
// 添加最⼤存活时间 单位:秒
config.setMaxAge(1800L);
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new
UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
2、重写WebMvcConfigurer
@Configuration
public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/*/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("*");
}
}
3、使⽤注解 @CrossOrigin
这个注解可以⽤于两个地⽅:
- 类:表示该类的所有⽅法允许跨域。
- ⽅法:表示当前⽅法允许跨域。
@RestController
@CrossOrigin(origins = "*")
public class DailyblueController {
@RequestMapping("/a")
public String a() {
return "This is DailyblueController`a method!";
}
@RequestMapping("/b")
@CrossOrigin(origins = "http://localhost:8081") //指定具体ip允许跨域
public String b() {
return "This is DailyblueController`b method!";
}
@RequestMapping("/c")
public String c() {
return "This is DailyblueController`c method!";
}
}
其中 @CrossOrigin 中的2个参数:
- origins:允许可访问的域列表。
- maxAge:准备响应前的缓存持续的最⼤时间(以秒为单位)。
4、⼿动设置响应头
使⽤ HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这⾥ Origin 的值也可以设置为 “*”,表示全部放⾏。
@RequestMapping("/index")
public String index(HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin","*");
return "index";
}
5、使⽤⾃定义filter 实现跨域
可以⾃定义⼀个多滤器,对请求的数据头进⾏过来,允许其他域请求访问:
@Component
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,contenttype");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
6、spring_boot_mvc5
项目结构
1、注解方式@CrossOrigin(推荐)
controller包
@RestController
@RequestMapping("/first")
public class FirstController {
@CrossOrigin
@GetMapping("/a")
public String a() {
return "This is FirstController`a method!";
}
@GetMapping("/b")
public String b() {
return "This is FirstController`b method!";
}
}
启动类
@SpringBootApplication
public class SpringBootMVC5Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootMVC5Application.class, args);
}
}
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<button @click="loadAxiosData1">发送1</button>
<button @click="loadAxiosData2">发送2</button>
</div>
</body>
</html>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script>
new Vue({
el: '#app',
methods: {
loadAxiosData1() {
axios.get('http://localhost:8080/first/a')
.then((response)=>{
console.log(response)
})
},
loadAxiosData2() {
axios.get('http://localhost:8080/first/b')
.then((response)=>{
console.log(response)
})
},
}
})
</script>
启动项目后效果
2、返回新的 CorsFilter
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放⾏哪些原始域
config.addAllowedOrigin("*");
//放⾏哪些请求⽅式
config.addAllowedMethod("*");
//放⾏哪些原始请求头部信息
config.addAllowedHeader("*");
// 添加最⼤存活时间 单位:秒
config.setMaxAge(1800L);
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new
UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
3、重写 WebMvcConfigurer
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/*/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("*");
}
}
4、⼿动设置响应头
@RestController
@RequestMapping("/first")
public class FirstController {
//@CrossOrigin
@GetMapping("/a")
public String a() {
return "This is FirstController`a method!";
}
@GetMapping("/b")
public String b(HttpServletResponse response) {
//表头全通过
response.addHeader("Access-Control-Allow-Origin", "*");
return "This is FirstController`b method!";
}
}
5、使⽤⾃定义 filter 实现跨域
@Component
public class CorsFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,contenttype");
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
}