JavaWeb(后端)

目录

Spring-MVC 

Maven

依赖管理 

 Maven高级

HTTP协议

 SpringBootWeb 

注解

请求响应

请求 

 响应

 分层解耦

 三层架构

分层解耦

IOC & DI

文件上传

登录

登录认证

登录校验

会话技术

JWT令牌

过滤器Filter

拦截器Interceptor

异常处理

事务管理

AOP

通知类型

切入点表达式

 连接点

MYSQL

Mybatis 

XML映射文件 

Mybatis动态SQL 


MVC

MVC 就是 Model View Controller 的缩写,属于一种软件架构设计模式一种思想,把我们的项目分为控制器(Controller)、模型(Model)、视图(view)三个部分,model就是处理业务逻辑处理数据的,controller就是接受请求丢给对应的model进行处理的,view就是展示数据的也就是界面

                        

Spring-MVC 

Spring MVC(Model-View-Controller)是Spring框架中的一个模块,用于构建基于MVC设计模式的Web应用程序。Spring MVC将应用程序分为三个主要部分:

Model:负责处理数据和业务逻辑。
View:负责展示数据。
Controller:负责处理用户请求并返回响应。
Spring MVC通过一系列的注解(如@Controller、@RequestMapping、@RequestParam等)简化了Web应用程序的开发。

Maven

Apache Maven 是一个项目管理构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。

Maven的作用

依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题。

统一项目结构:提供标准、统一的项目结构。

 项目构建:标准跨平台(Linux、Windows、MacOS)的自动化项目构建方式。

 

 官网:http://maven.apache.org/

 

 引入依赖,会先查找本地仓库有没有,没有在从远程仓库查找(私服 一般是公司内部有的),没有再从中央仓库查找,再从中央仓库下载到私服,再从私服中下载到本地仓库,再从本地仓库中下载。

Maven的安装

 配置Maven环境(全局)

将你所配置的路径输入进去 

选择jdk的版本

 

 Maven坐标

2023版idea创建Maven项目 

依赖管理 

依赖:指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。

引入依赖

<dependencies>

              <dependency>...</dependency>

                <dependency>...</dependency>

</dependencies>

依赖传递 

 依赖范围

生命周期 

Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。

 

在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。 

 Maven高级

分模块设计与开发

分模块设计:将项目按照功能拆分成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享。

分模块设计需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分 

继承与聚合

 继承关系实现

 

 版本锁定

在maven中,可以在父工程的pom文件中通过 <dependencyManagement> 来统一管理依赖版本。

子工程引入依赖时,无需指定 <version> 版本号,父工程统一管理。变更依赖版本,只需在父工程中统一变更。

把所有的版本聚到一起,方便查找修改 

<dependencies> 是直接依赖,在父工程配置了依赖,子工程会直接继承下来。 <dependencyManagement> 是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本) 

聚合

聚合:将多个模块组织成一个整体,同时进行项目的构建。

聚合工程 :一个不具有业务功能的“空”工程(有且仅有一个pom文件)

作用: 快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)

聚合工程中所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关。

 私服

私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题。

私服在企业项目开发中,一个项目/公司,只需要一台即可(无需我们自己搭建,会使用即可)。

资源上传与下载 

HTTP协议

概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器服务器之间数据传输的规则

特点:

     1、基于TCP协议:面向连接,安全

     2、基于请求-响应模型的:一次请求对应一次响应

     3、HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的

     缺点:多次请求间不能共享数据

     优点:速度快。

HTTP-请求协议

请求行:请求数据第一行 (请求方式、资源路径、协议)

请求头:第二行开始,格式key:value

请求体:POST请求,存放请求参数

 

HTTP-响应协议

响应行:响应数据第一行(协议、状态码、描述

响应头:第二行开始,格式key:value

响应体:最后一部分,存放响应数据

HTTP响应格式

 

HTTP-协议解析 

浏览器内置了解析HTTP-协议的程序

客户端借助web服务器 进行操作,响应数据

Web 服务器

Web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是 "提供网上信息浏览服务" 。

web服务器

 对HTTP协议操作进行封装,简化web程序开发。

部署web项目,对外提供网上信息浏览服务

Tomcat 

一个轻量级的web服务器,支持servlet、jsp等少量javaEE规范。

也被称为web容器、servlet容器。

 SpringBootWeb 

Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率。

创建springboot工程:

创建springboot工程,并勾选web开发相关依赖。

Springboo内嵌Tomcat服务器

起步依赖:

      spring-boot-starter-web:包含了web应用开发所需要的常见依赖。

      spring-boot-starter-test:包含了单元测试所需要的常见依赖。 

内嵌Tomcat服务器

基于Springboot开发的web应用程序,内置了tomcat服务器,当启动类运行时,会自动启动内嵌的tomcat服务器。

注解

@SpringBootApplication


作用:这是一个组合注解,包括了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解。用于标识SpringBoot应用程序的入口类。

@Configuration:指示这个类是一个配置类,它定义了一个或多个@Bean方法,用于创建和配置Spring应用程序上下文中的Bean。

@EnableAutoConfiguration:启用Spring Boot的自动配置机制,它会自动添加所需的依赖项和配置,以使应用程序能够运行。

@ComponentScan:指示Spring Boot扫描当前包及其子包中的所有@Component、@Service、@Repository和@Controller注解的类,并将它们注册为Spring Bean。

@SpringBootApplication注解通常被用于Spring Boot应用程序的入口类上,用于启动Spring Boot应用程序。它可以简化Spring应用程序的配置和启动过程。

用例:

@RestController

@RestController
@RestController的作用等同于@Controller + @ResponseBody                                          @Controller注解,表明了这个类是一个控制器类                                                

@RequestMapping

@RequestMapping

@RequestParam

@RequestParam

@DateTimeFormat:完成日期参数格式转换

 @DateTimeFormat

@RequestBody

@RequestBody

@PathVariable

@PathVariable

@Component

@Autowired

@ComponentScan:@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。

@SpringBootApplication具有包扫描作用,默认扫描当前包及其子包

@Component,@Controller,@Service,@Repository

请求响应

请求(HttpServletRequest):获取请求数据

响应(HttpServletResponse):设置响应数据 

请求 

postman:常用于后端测试

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。

作用:常用于进行接口测试

简单参数

原始方式

    // 1. 简单参数
    //原始方式
    @RequestMapping("/simpleParam")
    public String simpleParam(HttpServletRequest request){
        //获取请求参数
        String name = request.getParameter("name");
        String ageStr = request.getParameter("age");

        int age = Integer.parseInt(ageStr);
        System.out.println(name+ ":" + age);
        return "OK";
    }

springboot方式 

简单参数:参数名与形参变量名相同,定义形参即可接收参数。

// springboot方式
    @RequestMapping("/simpleParam")
    public String simpleParam(String name, Integer age){
        System.out.println(name+ ":" + age);
        return "OK";
    }

简单参数:如果方法形参名称与请求参数名称不匹配,可以使用 @RequestParam 完成映射。

 @RequestMapping("/simpleParam")
    public String simpleParam(@RequestParam(name = "name", required = false) String username, Integer age){
        System.out.println(username+ ":" + age);
        return "OK";
    }

 @RequestParam中的required属性默认为true,代表该请求参数必须传递,如果不传递将报错。 如果该参数是可选的,可以将required属性设置为false。 

实体参数

简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可

public class User {
    private String name;
    private Integer age;
}
 //2. 实体参数
    @RequestMapping("/simplePojo")
    public String simplePojo(User user){
        System.out.println(user);
        return "OK";
    }

 复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

public class Address {
    private String province;
    private String city;
}
public class User {
    private String name;
    private Integer age;

    private Address address;
}
 @RequestMapping("/complexPojo")
    public String complexPojo(User user){
        System.out.println(user);
        return "OK";
    }

数组集合参数

数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

   //3. 数组集合参数
    @RequestMapping("/arrayParam")
    public String arrayParam(String[] hobby){
        System.out.println(Arrays.toString(hobby));
        return "OK";
    }

   //集合来接收
    @RequestMapping("/listParam")
    public String listParam(@RequestParam List<String> hobby){
        System.out.println(hobby);
        return "OK";
    }

数组:请求参数名与形参中数组变量名相同,可以直接使用数组封装

集合:请求参数名与形参中集合变量名相同,通过@RequestParam绑定参数关系

日期参数

 //4. 日期时间参数
    @RequestMapping("/dateParam")
    public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
        System.out.println(updateTime);
        return "OK";
    }

Json参数

JSON参数:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用 @RequestBody 标识

public class Address {
    private String province;
    private String city;
}
public class User {
    private String name;
    private Integer age;

    private Address address;
}
 //5. json参数
    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody User user){
        System.out.println(user);
        return "OK";
    }
路径参数

路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用 @PathVariable 获取路径参数

 //6. 路径参数
    @RequestMapping("/path/{id}")
    public String pathParam(@PathVariable Integer id){
        System.out.println(id);
        return "OK";
    }

 

 @RequestMapping("/path/{id}/{name}")
    public String pathParam2(@PathVariable Integer id , @PathVariable String name){
        System.out.println(id);
        System.out.println(name);
        return "OK";
    }

 响应

 

 Result类

package com.itheima.pojo;

/**
 * 统一响应结果封装类
 */
public class Result {
    private Integer code ;//1 成功 , 0 失败
    private String msg; //提示信息
    private Object data; //数据 date

    public Result() {
    }
    public Result(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    public static Result success(Object data){
        return new Result(1, "success", data);
    }
    public static Result success(){
        return new Result(1, "success", null);
    }
    public static Result error(String msg){
        return new Result(0, msg, null);
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
 @RequestMapping("/hello")
    public Result hello(){
        System.out.println("Hello World ~");
        //return new Result(1,"success","Hello World ~");
        return Result.success("Hello World ~");
    }

    @RequestMapping("/getAddr")
    public Result getAddr(){
        Address addr = new Address();
        addr.setProvince("广东");
        addr.setCity("深圳");
        return Result.success(addr);
    }

    @RequestMapping("/listAddr")
    public Result listAddr(){
        List<Address> list = new ArrayList<>();

        Address addr = new Address();
        addr.setProvince("广东");
        addr.setCity("深圳");

        Address addr2 = new Address();
        addr2.setProvince("陕西");
        addr2.setCity("西安");

        list.add(addr);
        list.add(addr2);
        return Result.success(list);

 分层解耦

 三层架构

controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。

service:业务逻辑层,处理具体事务的逻辑。

dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删改查。 

分层解耦

内聚:软件中各个功能模块内部的功能联系。

耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

软件设计原则:高内聚低耦合

控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。(@Component)

依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。(@Autowired)

Bean对象:IOC容器中创建、管理的对象,称之为bean。

IOC & DI

IOC

①. Service层 及 Dao层的实现类,交给IOC容器管理。

②. 为Controller及Service注入运行时,依赖的对象。

文件上传

文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。

文件上传这个功能需要涉及到两个部分:

1. 前端程序 2. 服务端程序

前端程序

<form action="/upload" method="post" enctype="multipart/form-data">
 姓名: <input type="text" name="username"><br>
   年龄: <input type="text" name="age"><br>
   头像: <input type="file" name="image"><br>
    <input type="submit" value="提交">
</form>

上传文件页面三要素

表单必须有file域,用于选择要上传的文件

<input type="file" name="image"/>

表单提交方式必须为POST  

通常上传的文件会比较大,所以需要使用 POST 提交方式

表单的编码类型enctype必须要设置为:multipart/form-data

普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格 式必须设置为multipart/form-data

 后端程序

首先在服务端定义这么一个controller,用来进行文件上传,然后在controller当中定义一个 方法来处理 /upload 请求

在定义的方法中接收提交过来的数据 (方法中的形参名和请求参数的名字保持一致)

用户名:String name

年龄: Integer age

文件: MultipartFile image

Spring中提供了一个API:MultipartFile,使用这个API就可以来接收到上传的文件

UploadController代码:

@Slf4j
@RestController
public class UploadController {
    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile 
image) {
        log.info("文件上传:{},{},{}",username,age,image);
        return Result.success();
   }
}

本地存储

将上传 的文件保存在服务器的本地磁盘上。

代码实现:

1. 在服务器本地磁盘上创建images目录,用来存储上传的文件(例:E盘创建images目录)

2. 使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下

MultipartFile 常见方法: String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流

 

登录

登录认证

如果没有登录校验,在未登录情况下,我们也可以直接访问部门管理、员工管理等功能。这是不正常的,需要进行登录校验。

登录校验

所谓登录校验,指的是我们在服务器端接收到浏览器发送过来的请求之后,首先我们要对请求进行 校验。先要校验一下用户登录了没有,如果用户已经登录了,就直接执行对应的业务操作就可以 了;如果用户没有登录,此时就不允许他执行相关的业务操作,直接给前端响应一个错误的结果, 最终跳转到登录页面,要求他登录成功之后,再来访问对应的数据。

HTTP协议是无状态的,下一次请求不会携带上一次请求的信息。

所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服 务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆 的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协 议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。

标记:用户登录成功之后,每一次请求中,都可以获取到该标记。

统一拦截:过滤器Filter  拦截器Interceptor

会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪方案:

                    客户端会话跟踪技术:Cookie

                    服务端会话跟踪技术:Session

                    令牌技术

Cookie

cookie 是客户端会话跟踪技术,它是存储在客户端浏览器的,我们使用 cookie 来跟踪会话,我们 就可以在浏览器第一次发起请求来请求服务器的时候,我们在服务器端来设置一个cookie。 比如第一次请求了登录接口,登录接口执行完成之后,我们就可以设置一个cookie,在 cookie 当中 我们就可以来存储用户相关的一些数据信息。比如我可以在 cookie 当中来存储当前登录用户的用户 名,用户的ID。 服务器端在给客户端在响应数据的时候,会自动的将 cookie 响应给浏览器,浏览器接收到响应回来 的 cookie 之后,会自动的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中, 都会将浏览器本地所存储的 cookie 自动地携带到服务端。接下来在服务端我们就可以获取到 cookie 的值。我们可以去判断一下这个 cookie 的值是否存 在,如果不存在这个cookie,就说明客户端之前是没有访问登录接口的;如果存在 cookie 的值,就 说明客户端之前已经登录完成了。这样我们就可以基于 cookie 在同一次会话的不同请求之间来共享 数据。

用了 3 个自动:

服务器会 自动 的将 cookie 响应给浏览器。

浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。

在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端 

Session,它是服务器端会话跟踪技术,所以它是存储在服务器端的。而 Session 的底层其实就是 Cookie 来实现的。

获取Session

如果我们现在要基于 Session 来进行会话跟踪,浏览器在第一次请求服务器的时候,我们就可 以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ,会话对象是不存 在的,这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ,它 都有一个ID(示意图中Session后面括号中的1,就表示ID),我们称之为 Session 的ID。

响应Cookie (JSESSIONID)

接下来,服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应给 浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个 Set-Cookie 响应头对 应的值是不是cookie? cookie 的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID。浏览器会自动识别这个响应头,然后自动将Cookie存储在浏览器本地。  

查找Session

接下来,在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下 来服务器拿到JSESSIONID这个 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就会 从众多的 Session 当中来找到当前请求对应的会话对象Session。

优缺点

       优点:Session是存储在服务端的,安全

      缺点: 服务器集群环境下无法直接使用Session 移动端APP(Android、IOS)中无法使用Cookie 用户可以自己禁用Cookie Cookie不能跨域

JWT令牌

JWT令牌本质就是一个 字符串。

如果通过令牌技术来跟踪会话,我们就可以在浏览器发起请求。在请求登录接口的时候,如果登录成功,我就可以生成一个令牌,令牌就是用户的合法身份凭证。接下来我在响应数据的时候,我就可以直 接将令牌响应给前端。 接下来我们在前端程序当中接收到令牌之后,就需要将这个令牌存储起来。这个存储可以存储在 cookie 当中,也可以存储在其他的存储空间(比如:localStorage)当中。 接下来,在后续的每一次请求当中,都需要将令牌携带到服务端。携带到服务端之后,接下来我们就需 要来校验令牌的有效性。如果令牌是有效的,就说明用户已经执行了登录操作,如果令牌是无效的,就 说明用户之前并未执行登录操作。 此时,如果是在同一次会话的多次请求之间,我们想共享数据,我们就可以将共享的数据存储在令牌当 中就可以了。

JWT全称:JSON Web Token

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字 签名的存在,这些信息是可靠的。  

jwt就是将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在 通信双方安全的进行信息传输了。 

JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)

第一部分:Header(头), 记录令牌类型、签名算法等。 例如: {"alg":"HS256","type":"JWT"}

第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如: {"id":"1","username":"Tom"}

第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加 入指定秘钥,通过指定签名算法计算而来。

签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在, 所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡 改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

 其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码 Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味 着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是 64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符 号,那就是等号。等号它是一个补位的符号 需要注意的是Base64是编码方式,而不是加密方式。

JWT令牌最典型的应用场景就是登录认证:

1. 在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果登录成功之后,我们需要生成 一个jwt令牌,将生成的 jwt令牌返回给前端。

2. 前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服 务端。

3. 服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接 拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处 理。

在JWT登录认证的场景中我们发现,整个流程当中涉及到两步操作:

1. 在登录成功之后,要生成令牌。

2. 每一次请求当中,要接收令牌并对令牌进行校验。 稍后我们再来学习如何来生成jwt令牌,以及如何来校验jwt令牌。

JWT生成和校验

导入依赖

<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成JWT令牌 

 public void testGenJWT()
    {
        Map<String,Object> claims=new HashMap<>();
        claims.put("id",1);
        claims.put("name","tom");
        String jwt=Jwts.builder()//builder生成
                .signWith(SignatureAlgorithm.HS256,"itheima")//签名算法 和密钥
                .setClaims(claims)//自定义内容(载荷)
                .setExpiration(new Date(System.currentTimeMillis()+3600*1000))//设置有效期为1小时
                .compact();//调用该方法就可以返回一个字符串类型的返回值
        System.out.println(jwt);
    }

JWT令牌解析 

 @Test
    public void parsejwt()
    {
       Claims claims= Jwts.parser()//parser解析
                .setSigningKey("itheima")//签名密钥
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyNDg0NjA1NH0.vUHKv48DmnhGiBU3e6o5-SgRBTwIWoM7X6WoQQQd8MU")//JWT令牌
                .getBody();
        System.out.println(claims);
    }

JwtUtils类 


public class JwtUtils {

    private static String signKey = "itheima";
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

登录校验

 @PostMapping("/login")
    public Result login(@RequestBody Emp emp)
    {
        log.info("员工登录:{}",emp);
        Emp e= empService.login(emp);
        //登录成功,生成令牌,下发令牌
        if(e!=null)
        {
            Map<String,Object> claims=new HashMap<>();
            claims.put("id",e.getId());
            claims.put("name",e.getName());
            claims.put("username",e.getUsername());
            String jwt= JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }

        return Result.error("用户名或密码错误");
    }

过滤器Filter

Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能 使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后, 才可以访问对应的资源。过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

 Filter快速入门

第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。

第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override//初始化,只会调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行了");
    }

    @Override//拦截到请求之后调用,调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了放行。。。之前的请求");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("Demo 拦截到了放行。。。之后的请求");
    }

    @Override//销毁方法,只调用一次
    public void destroy() {
        System.out.println("destory 销毁化方法执行了");
    }
}
@ServletComponentScan //开启servlet组件的支持
@SpringBootApplication
public class TliasWebManagement2Application {

    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagement2Application.class, args);
    }

}

 执行流程

拦截路径 

过滤器链 

先要执行过滤器2放行之后的逻辑,再来执行过滤器1放行之后的逻辑,最后在给浏览器响应数据。  以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的 自动排序确定的,类名排名越靠前,优先级越高。

登录校验-Filter

LoginCheckFilter
@Slf4j
@WebFilter("/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req=(HttpServletRequest)servletRequest;
        HttpServletResponse resq=(HttpServletResponse)servletResponse;
        //1、获取请求url
        String url=req.getRequestURI();
        log.info("请求的url:{}",url);

        //2、判断请求url中是否包含login,如果包含,说明是登录操作,放行
       if(url.contains("login"))
       {
           log.info("登录操作,放行...");
           filterChain.doFilter(servletRequest, servletResponse);
           return ;
       }
        //3、获取请求头中的令牌(token)
        String jwt= req.getHeader("token");
        //4、判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt))
        {
            log.info("请求头token为空,返回未登录的信息");
           Result error= Result.error("NOT_LOGIN");
            //手动转换成json格式 Controller层有@RestController注解 可以返回JSON格式
            String notLogin=  JSONObject.toJSONString(error);
            servletResponse.getWriter().write(notLogin);
            return ;

        }
        //5、解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            //jwt 解析失败
            log.info("解析令牌失败,返回未登录错误信息");
            Result error= Result.error("NOT_LOGIN");
            //手动转换成json格式 Controller层有@RestController注解 可以返回JSON格式
            String notLogin=  JSONObject.toJSONString(error);
            servletResponse.getWriter().write(notLogin);
            return ;
        }
        //6、放行
        log.info("令牌合法,放行");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}
拦截器Interceptor

拦截器是一种动态拦截方法调用的机制,类似于过滤器。

拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。

拦截器的作用: 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。

拦截器的使用步骤:1. 定义拦截器 2. 注册配置拦截器

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行。 返回true:放行   返回false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, 
HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        
        return true; //true表示放行
   }
    //目标资源方法执行后执行
    @Override
    public void postHandle(HttpServletRequest request, 
HttpServletResponse response, Object handler, ModelAndView 
modelAndView) throws Exception {
        System.out.println("postHandle ... ");
   }
    //视图渲染完毕后执行,最后执行
    @Override
    public void afterCompletion(HttpServletRequest request, 
HttpServletResponse response, Object handler, Exception ex) throws 
Exception {
        System.out.println("afterCompletion .... ");
   }
}

注册配置拦截器:实现WebMvcConfigurer接口,并重写addInterceptors方法(相当于Filter中的unit方法初始化)

@Configuration  
public class WebConfig implements WebMvcConfigurer {
    //自定义的拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
       
 registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**
");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
   }
}

拦截路径 

addPathPatterns("要拦截路径") 方法,就可以指定要拦截哪些资源

excludePathPatterns("不拦截路径") 方法,指定哪些 资源不需要拦截。

登录校验- Interceptor

登录校验拦截器 

//还是很好理解的 首先这一个拦截器的逻辑 然后你肯定要把这个拦截器在系统启用 所以需要一个注册
//定义拦截器
//快捷键 Ctrl+o
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标资源方法运行前运行,返回true,放行,放回false,不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {

        //1、获取请求url
        String url=req.getRequestURI();
        log.info("请求的url:{}",url);

        //2、判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("login"))
        {
            log.info("登录操作,放行...");

            return  true;
        }
        //3、获取请求头中的令牌(token)
        String jwt= req.getHeader("token");
        //4、判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt))
        {
            log.info("请求头token为空,返回未登录的信息");
            Result error= Result.error("NOT_LOGIN");
            //手动转换成json格式 Controller层有@RestController注解 可以返回JSON格式
            String notLogin=  JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;

        }
        //5、解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            //jwt 解析失败
            log.info("解析令牌失败,返回未登录错误信息");
            Result error= Result.error("NOT_LOGIN");
            //手动转换成json格式 Controller层有@RestController注解 可以返回JSON格式
            String notLogin=  JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        //6、放行
        log.info("令牌合法,放行");
        return true;
    }

    @Override//资源方法运行后运行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override//视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

 注册配置拦截器

@Configuration //加上之后 代表当前类是配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

执行流程

当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到 这次请求。拦截到这次请求之后,它会先执行放行前的逻辑然后再执行放行操作。而由于我们当 前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我 们所定义的controller当中的接口方法。

Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环 境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器)所有请求都会先 进行到DispatcherServlet,再将请求转给Controller。

当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住执行 preHandle() 方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放 行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller 中的方法也不会执行)。 

在controller当中的方法执行完毕之后,再回过来执行 postHandle() 这个方法以及 afterCompletion() 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放 行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据

它们之间的区别主要是两点:

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。

拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资 源。

异常处理

​​​​​​​全局异常处理器:在类上加上一个注解 @RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。

在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解 @ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的 是哪一类型的异常。

@RestControllerAdvice
public class GlobalExceptionHandler {
    //处理异常
    @ExceptionHandler(Exception.class) //指定能够处理的异常类型
    public Result ex(Exception e){
        e.printStackTrace();//打印堆栈中的异常信息
        //捕获到异常之后,响应一个标准的Result
        return Result.error("对不起,操作失败,请联系管理员");
   }
}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody 处理异常的方法返回值会转换为json后再响应给前端 

事务管理

事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数 据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败

事务的操作主要有三步:

1. 开启事务(一组操作开始前,开启事务):start transaction / begin ;

2. 提交事务(这组操作全部成功后,提交事务):commit ;

3. 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;

Transactional注解

@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交 事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作

@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功 能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在 一个事务范围内。

@Override
    @Transactional  //当前方法添加了事务管理
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;
        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
}

说明:可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的 日志信息了​​​​​​​

#spring事务管理日志
logging:
 level:
   org.springframework.jdbc.support.JdbcTransactionManager: debug

配置@Transactional注解​​​​​​​,只有出现RuntimeException(运行时异常)才会回滚 事务。

假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过 rollbackFor这个属性可以指定出现何种异常类型回滚事务。

    @Override
    @Transactional(rollbackFor=Exception.class)
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int num = id/0;
        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
   }

 propagation

事务的传播行为​​​​​​​:就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

​​​​​​​

AOP

Aspect Orienter Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)

AOP的优势: 1. 减少重复代码 2. 提高开发效率 3. 维护方便

AOP的常见的应用场景:记录系统的操作日志、权限控制、 事务管理

事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加 @Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完 毕之后提交或回滚事务

AOP快速入门

1、导入依赖:在pom.xml中导入AOP的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 编写AOP程序:针对于特定方法根据业务需要进行编程 

统计当前这个项目当中每一个业务方法的执行耗时

AOP程序:TimeAspect

@Slf4j
@Component //Bean对象
@Aspect  //AOP类
//AOP 针对特定的方法进行编程
public class TimeAspect {

    //连接点:JoinPoint 可以被AOP控制的方法
    //通知:Advice 指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
    //切入点:PiontCut,匹配连接点的条件,通知会在切入点方法执行时被应用
    //切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
    //目标对象:Tar,通知所应用的对象
    @Around("execution(* com.itheima.service.*.*(..))")//切入点表达式
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //1、记录开始时间
        long begin=System.currentTimeMillis();

        //2、调用原始方法运行
        Object result=joinPoint.proceed();

        //3、记录结束时间
        long end=System.currentTimeMillis();

        log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);
        return result;

    }

}

1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的 方法。 

2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来 的这一部分重复的逻辑,也就是共性的功能。

3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用 

切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。

4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪 个原始方法,在什么时候执行什么样的操作。 

5. 目标对象:Target,通知所应用的对象 

目标对象指的就是通知所应用的对象,我们就称之为目标对象。

Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理 技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的 增强。

通知类型

Spring中AOP的通知类型:

@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行

@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其 他通知不需要考虑目标方法执行

@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法 执行完毕,是获取不到返回值的

@Before:前置通知,此注解标注的通知方法在目标方法前被执行

@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行 @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执 行 @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

Spring提供了@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该 切入点表达式即可。

@Slf4j
@Component
//@Aspect
public class MyAspect1 {

    //切面类
    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void pt(){}

    //前置通知
    @Before("pt()")
    public void before(){
        log.info("before ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("pt()")
    public void after(){
        log.info("after ...");
    }

    //返回后通知,出现异常不通知
    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("afterReturning ...");
    }

    //异常后通知
    @AfterThrowing("pt()")
    public void afterThrowing(){
        log.info("afterThrowing ...");
    }
}

当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式, 当外部其他 切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候,具体 的语法为: 全类名.方法名(),具体形式如下:  

@Slf4j
@Component
@Aspect
public class MyAspect2 {
    //引用MyAspect1切面类中的切入点表达式
    @Before("com.itheima.aspect.MyAspect1.pt()")
    public void before(){
        log.info("MyAspect2 -> before ...");
   }
}

通知顺序:默认按照切面类的类名字母排序

目标方法前的通知方法:字母排名靠前的先执行 目标方法后的通知方法:字母排名靠前的后执行

如果我们想控制通知的执行顺序有两种方式:

1. 修改切面类的类名(这种方式非常繁琐、而且不便管理)

2. 使用Spring提供的@Order注解 

切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小 越后执行)

切入点表达式

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常

其中带?的表示可以省略的部分

访问修饰符:可省略(比如 public、protected)

包名.类名:可省略(不建议省略)

throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

@Before("execution(void 
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

可以使用通配符描述切入点

* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数, 也可以通配包、类、方法名的一部分

.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数  

切入点表达式的语法规则:

1. 方法的访问修饰符可以省略

2. 返回值可以使用 * 号代替(任意返回值类型)

3. 包名可以使用 * 号代替,代表任意包(一层包使用一个 * )

4. 使用 .. 配置包名,标识此包以及此包下的所有子包

5. 类名可以使用 * 号代替,标识任意类

6. 方法名可以使用 * 号代替,表示任意方法

7. 可以使用 * 配置参数,一个任意类型的参数

8. 可以使用 .. 配置参数,任意个任意类型的参数 

省略方法的修饰符号

execution(void 
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)

 使用 * 代替返回值类型

execution(* 
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)

使用 * 代替包名(一层包使用一个 * )

execution(* 
com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))

使用 .. 省略包名

execution(* com..DeptServiceImpl.delete(java.lang.Integer)) 

 使用 * 代替类名

execution(* com..*.delete(java.lang.Integer)

使用 * 代替方法名

execution(* com..*.*(java.lang.Integer)) 

 使用 * 代替参数

execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))

 使用 .. 省略参数

execution(* com..*.*(..))

可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

execution(* com.itheima.service.DeptService.list(..)) || 
execution(* com.itheima.service.DeptService.delete(..))

 @annotation

可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的 书写。

实现步骤:

1. 编写自定义注解

2. 在业务类要做为连接点的方法上添加自定义注解

自定义注解:MyLog

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

业务类:DeptServiceImpl

@Override
    @MyLog //自定义注解(表示:当前方法属于目标方法)
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        //模拟异常
        //int num = 10/0;
        return deptList;
   }
    @Override
    @MyLog  //自定义注解(表示:当前方法属于目标方法)
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
}

 切面类

@Slf4j
@Component
@Aspect
public class MyAspect7 {
    @Pointcut("@annotation(com.itheima.anno.MyLog)")
    private void pt(){}
   
    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> 
before ...");
   }
    
    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> 
after ...");
   }
    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);
        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);
        //获取方法执行时需要的参数
               Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));
        //执行原始方法
        Object returnValue = pjp.proceed();
        return returnValue;
   }
}

 连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法 名、方法参数等。

对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

​​​​​​​

MYSQL

数据库:DataBase(DB),是存储和管理数据的仓库

数据库管理系统:DataBase Management System  (DBMS),操纵和管理数据库的大型软件。

SQL:Structured Query Language,操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准。

mysql连接

DDL:Data Definition Language  数据定义语言,用来定义数据库对象(数据库,表,字段)

DML: Data Manipulation Language 数据操作语言,用来对数据库表中的数据进行增删改

DQL: Data Query Language 数据查询语言,用来查询数据库中表的记录

DCL: Data Control Language 数据控制语言,用来创建数据库用户、控制数据库的访问权限

DDL 

DDL(数据库操

作)

DDL(表操作)

 

DML 

添加数据(INSERT) 修改数据(UPDATE) 删除数据(DELETE)

insert into 表名 属性 values()

update 表名 set 属性=值1... [where 条件] 

修改数据:update  表名  set  字段名1 = 值1 , 字段名2 = 值2 , .... [ where  条件 ]

delete from 表名 [where 条件] 

删除数据:delete  from  表名  [ where  条件 ]

DQL

DQL英文全称是Data Query Language(数据查询语言),用来查询数据库表中的记录。

关键字:SELECT

 where与having区别:

执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。

判断条件不同:where不能对聚合函数进行判断,而having可以

分组排序:ASC:升序(默认值) DESC:降序

 

Mybatis 

@Mapper 在运行时,mybatis框架会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理

# 配置数据库的连接信息 - 四要素
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234

#配置mybatis的日志 指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

增删改 

@Mapper
public interface EmpMapper {

    //删除操作
    @Delete("delete from emp where id=#{id} ")
   public void delete(Integer id);

    //增加操作
    @Options(keyProperty = "id",useGeneratedKeys = true)//会自动将生成的主键值,赋值给emp对象的id属性
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
            "values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
    public void insert(Emp emp);

    //更新操作
    @Update("update emp set username=#{username},name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{deptId},update_time=#{updateTime} where id=#{id}")
    public void update(Emp emp);
}

查询

在配置里添加上

#开启mybatis的驼峰命名自动映射开关 a_column----->aColumn
mybatis.configuration.map-underscore-to-camel-case=true
//    @Select("select * from emp where id=#{id}")
//    public Emp getById(Integer id);

//     一、给字段起别名,让别名与实体类属性一致
    @Select("select id,username,password,name,gender,image,job,entrydate,dept_id deptId,create_time createTime,update_time updateTime from emp where id=#{id}")
    public Emp getById(Integer id);


//    二、通过@Results,@Results注解手动映射封装
    @Results(
            {
                    @Result(column = "dept_id",property = "deptId"),
                    @Result(column = "create_time",property = "createTime"),
                    @Result(column = "update_time",property = "updateTime")}
    )
    @Select("select * from emp where id=#{id}")
    public Emp getById(Integer id);

    //三、开启mybatis的驼峰命名自动映射开关----dept_id---->deptId 在环境配置中添加
        @Select("select * from emp where id=#{id}")
        public Emp getById(Integer id);

 contat连接

 @Select("select * from emp where name like '%${name}%' and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc")
        public List<Emp> list(String name, Short gender, LocalDate begin,LocalDate end);

 @Select("select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> list(@Param("name") String name,@Param("gender") Short gender, @Param("begin") LocalDate begin,@Param("end") LocalDate end);

XML映射文件 

 

 MybatisX 是一款基于 IDEA 的快速开发Mybatis的插件,为效率而生

使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。

Mybatis动态SQL 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
   XML映射文件定义规范
      1 XML文件的名称与Mapper接口名称一致,并且放置在相同的包下(同包同名)
      2 XML文件的namespace属性为Mapper接口全限定名一致
      3 XML文件中sql语句中id与Mapper接口中的方法名一致
-->
<mapper namespace="com.itheima.mapper.EmpMapper">

    <!--
        <sql>:定义可重用的SQL片段
        <include>:通过属性refid,指定包含的sql片段
    -->
    <sql id="commonSelect">
        select id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time
        from emp
    </sql>
    <update id="update2">
        update emp
        <set>
        <if test="username!=null">
            username=#{username},
        </if>
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="gender!=null">
            gender=#{gender},
        </if>
        <if test="image!=null">
            image=#{image},
        </if>
        <if test="job!=null">
            job=#{job},
        </if>
        <if test="entrydate!=null">
            entrydate=#{entrydate},
        </if>
        <if test="deptId!=null">
            dept_id=#{deptId},
        </if>
        <if test="updateTime!=null">
            update_time=#{updateTime}
        </if>
        </set>
       where id=#{id}

    </update>

    <!--2-->
    <!--resultType:单条记录所封装的类型-->
    <!--
      <if>:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL
      <where>:where元素只会在子元素有内容的情况下才会插入where字句。而且会自动去除子句的开头and或or
    -->
<select id="list" resultType="com.itheima.pojo.Emp"><!--3-->
   <include refid="commonSelect"/>
    <where>
    <if test="name != null">
        name like concat('%',#{name},'%')
    </if>
    <if test="gender !=null">
      and  gender=#{gender}
    </if>
    <if test="begin!=null and end!=null">
      and  entrydate between #{begin} and #{end}
    </if>
    </where>
     order by update_time desc

</select>
<!--批量删除员工 delete  from emp where id in (13,14,15)-->
    <!--
    collection:遍历的集合
    item:遍历出来的元素
    sepatator:分隔符
    open:遍历开始前拼接的SQL片段
    close:遍历结束后拼接的SQL片段
     -->
    <delete id="deleteById">
    delete from emp where id in
        <foreach collection="list" item="id" separator="," open="(" close=")">
         #{id}
        </foreach>
    </delete>

</mapper>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值