后端Web开发学习笔记

1. Maven

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

  • Apache软件基金会,成立于1999年7月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织

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

  • 开源项目:https://www.apache.org/index.html#projects-list

1.1 maven的作用

  • 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题
  • 统一项目结构:提供标准,统一项目结构
  • 项目构建:标准跨平台(Linux,windows,MacOS)的自动化项目构建方式

maven项目目录:

main实际项目资源
javaJava源代码目录
resources配置文件目录
test(java,resources)测试项目资源
pom.xml项目配置文件

1.1.2 仓库

用于存储资源,管理各种jar包

  • 本地仓库:自己计算机上的一个目录
  • 中央仓库:由Maven团队维护的全球唯一的,仓库地址:https://repo1.maven.org/maven2/
  • 远程仓库(私服):一般由公司团队搭建的私有仓库

1.2 IDEA集成

1.2.1 安装maven
  1. 解压apache-maven-3.5.3-bin.zip

  2. 配置本地仓库:修改conf/settings.xml中的为一个指定目录

    <localRepository>D:\maven\repository</localRepository>
    
  3. 配置阿里云私服(用来提高下载速度):修改conf/settings.xml中的标签,为其添加如下子标签:

    <mirror>
            <id>nexus-aliyun</id>
            <mirrorOf>*</mirrorOf>
            <name>Nexus aliyun</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>
    
  4. 配置环境变量:MAVEN_HOME为maven的解压目录,并将其bin目录加入PATH环境变量

1.2.2 配置Maven环境
  • 选择IDEA中File–>Settings–>Build,Execution,Deployment–>Build Tools–>Maven
  • 设置IDEA使用本地安装的Maven,并修改配置文件及本地仓库
1.2.3 IDEA创建Maven项目
  1. 创建模块,选择Maven,点击Next
  2. 填写模块名称,坐标信息,点击finish,创建完成
  3. 编写代码并运行,进行测试
1.2.4 IDEA导入Maven项目

方式一:打开IDEA,选择右侧Maven面板,点击+号,选中对应项目的pom.xml文件,双击即可

方式二:打开IDEA,选择File–>Project Structure–>Modules,点击+号,选择import Module,选中对应项目的pom.xml文件,点击apply

1.3 maven坐标

  • maven中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置
  • 使用坐标来定义项目或引入项目中需要的依赖
1.3.1 maven坐标主要组成
  • groupld:定义当前Maven项目隶属组织名称(通常是域名反写)
  • artifactld:定义当前Maven项目名称(通常是模块名称)
  • version:定义当前项目版本号

2. 依赖管理

2.1 依赖配置

  • 依赖:指在当前项目运行所需要的jar包,一个项目中可以引入多个依赖
  • 配置:
    1. 在pom.xml中编写标签
    2. 在标签中使用引入坐标
    3. 定义坐标的groupld,artifactld,version
    4. 点击刷新按钮,引入最新加入的坐标

如果引入依赖,在本地的仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖

如果不知道依赖的坐标信息,可以到https://mvnrepository.com/中搜索

2.2 依赖传递

依赖具有传递性

  • 直接依赖:在当前项目中通过依赖配置建立的依赖环境
  • 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
2.3 排除依赖

排除依赖指主动断开依赖的资源,被排出的依赖无需指定版本

<exclusion>
    <groupld>被排除的依赖</groupld>
    <artifactld>被排除的依赖</artifactld>
</exclusion>

2.4 依赖范围

依赖的jar包,默认情况下,可以在任何地方使用,可以通过…设置其作用范围

作用范围:

  • 主程序范围有效(main文件夹范围内)
  • 测试程序范围有效(test文件夹范围内)
  • 是否参与打包运行(package指令范围内)
scope值主程序测试程序打包(运行)范例
compile(默认)YYYlog4j
test-Y-junit
providedYY-servlet-api
runtime-YYjdbc驱动

2.5 生命周期

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

Maven中有3套相互独立的生命周期:

  • clean:清理工作
  • default:核心工作,如:编译,测试,打包,安装,部署等
  • site:生成报告,发布站点等

每一套生命周期包含一些阶段(phase),阶段是由顺序的,后面的阶段依赖于前面的阶段

生命周期阶段:

  • clean:移除上一次构建生成的文件
  • compile:编译项目源代码
  • test:使用合适的单元测试框架进行测试(junit)
  • package:将编译后的文件打包,如:jar,war等
  • install:安装项目到本地仓库

执行指定生命周期的两种方式:

  • 在IDEA中,右侧的maven工具栏,选中对应的生命周期,双击执行
  • 在命令行中,通过命令执行

2. HTTP

  • Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则
  • 特点:
    1. 支持客户/服务器模式。
    2. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GETHEADPOST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
    3. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
    4. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。早期这么做的原因是请求资源少,追求快。后来通过Connection: Keep-Alive实现长连接
    5. 无状态HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快

2.1 请求协议

HTTP-请求数据格式
  • 请求行:请求数据第一行(请求方式,资源路径,协议)
  • 请求头:第二行开始,格式key:value
Host请求的主机名
User-Agent浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0…Chrome/79
Accept表示浏览器能接收的资源类型
Accept-Language表示浏览器偏好的语言,服务器可以据此返回不同语言的网页
Accept-Encoding表示浏览器可以支持的压缩类型,如gzip,deflate等
Content-Type请求主体的数据类型
Content-Length请求主体的大小(单位:字节)
  • 请求体:POST请求,存放请求参数
请求方式
  1. 请求方式-GET:请求参数在请求行中,没有请求体,如:/brand/findAll?name=OPPO&status=1,GET请求大小是有限制的
  2. 请求方式-POST:请求参数在请求体中,POST请求大小是没有限制的

2.2 响应协议

HTTP-响应格式
  • 响应行:响应数据第一行(协议,状态码,描述)
  • 响应头:第二行开始,格式key:value
  • 响应体:最后一部分,存放响应数据
1xx响应中-临时状态码,表示请求已经接收,告诉客户端应该继续请求或者
2xx成功-表示请求已经被成功接收,处理已完成
3xx重定向-重定向到其他地方,让客户端再发起一次请求以完成整个处理
4xx客户端错误-处理发生错误,责任在客户端,如:请求了不存在的资源,客户端未被授权,禁止访问等
5xx服务器错误-处理发生错误,责任在服务端,如:程序抛出异常
Content-Type表示该响应内容的类型,例如text/html,application/json
Content-Length表示该响应内容的长度(字节数)
Content-Encoding表示该响应压缩算法,例如gzip
Cache-Control指示客户端应如何缓存,例如max-age=300表示最多缓存300秒
Set-Cookie告诉浏览器为当前页面所在的域设置cookie

常见的响应状态码:

状态码英文描述解释
200OK客户端请求成功,即处理成功
302Found指示所请求的资源已移动到Location响应头给定的URL,浏览器会自动重新访问到这个页面
304Not Modified告诉客户端,所请求的资源至上次取得后,服务端并未更改(隐式重定向)
400Bad Request客户端请求有语法错误,不能被服务端所理解
403Forbidden服务器收到请求,但是拒绝提供服务
404Not Found请求资源不存在,一般是URL输入有误,或网站资源被删除
405Method Not Allowed请求方式有误,比如应该用GET请求方式的资源,用来POST
428Precondition Required服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
429Too Many Requests指示用户在给定时间内发送了太多请求(限速),配合Retry-After(多长时间后可以请求)响应头一起使用
431Request Header Fields Too Large请求头太大,服务器不愿意处理请求,因为头部字段太大,请求可以在减少请求头域的大小后重新提交
500Internal Server Error服务器发生不可预期的错误,查看日志
503Service Unavailable服务器尚未准备好处理请求,服务器刚启动,还未初始化好

状态码大全:https://cloud.tencent.com/developer/chapter/13553

3. Web服务器

  • 对HTTP协议操作进行封装,简化web程序开发
  • 部署web项目,对外提供网上信息浏览服务

3.1 Tomcat

  • Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范
  • JavaEE:Java Enterprise Edition,Java企业版。指Java企业级开发的技术规范总和,包含13项技术规范:JDBC,JNDI,EJB,PMI,JSP,Servlet,XML,JMS,Java IDL,JTS,JTA,JavaMail,JAE
  • Tomcat也称为Web容器,Servlet容器,Servlet程序需要依赖Tomcat才能运行
  • 官网:https://tomcat.apache.org/

3.1.1 Tomcat基本使用

  • 启动:双击:bin\startup.bat

    • 控制台中文乱码:修改conf/logging.properties

      java.util.logging.ConsoleHandler.level = FINE
      java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
      java.util.logging.ConsoleHandler.encoding = (UTF-8)->GBK
      
  • 关闭:

    • 直接x掉运行窗口:强制关闭
    • bin\shutdown.bat:正常关闭
    • Ctrl+C:正常关闭
常见问题
  • 启动窗口一闪而过:检查JAVA_HOME环境变量是否正确配置

  • 端口号冲突:找到对应程序,将其关掉

  • 配置Tomcat端口号(conf/server.xml)

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"  />
    

    HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号

部署项目
  • 将项目放置到web apps目录下,即部署完成

3.1.2 SPringBootWeb

起步依赖
  • spring-boot-starter-web:包含了web应用开发所需要的常见依赖
  • spring-boot-starter-test:包含了单元测试所需要的常见依赖
  • 官方提供的starter:https://docs.spring.io/spring-boot/docs/2.7.4/reference/htmlsingle/#using.build-systems.starters

内嵌Tomcat服务器

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

4. 请求响应

  • 请求(HttpServletRequest):获取请求数据
  • 响应(HttpServletRequest):设置响应数据
  • BS架构:Browser/Server,浏览器/服务器架构模式,客户端指需要浏览器,应用程序的逻辑和数据都存储在服务端(维护比较方便,但是速度取决于带宽,可能影响体验)
  • CS架构:Client/Server,客户端/服务器架构模式,需要单独下载对应客户端(开发和维护比较麻烦,不同的系统需要不同的版本,但是本地体验更流畅)

4.1 简单参数

4.1.1 原始方式获取请求参数
  • Controller方法形参中声明HttpServletRequest对象
  • 调用对象的getParameter(参数名)
4.1.2 SpringBoot中接收简单参数
  • 请求参数名与方法形参变量名相同
  • 会自动进行类型转换
4.1.3 @RequestParam注解
  • 方法形参名称与请求参数名称不匹配,通过该注解完成映射
  • 该注解的required属性默认是true,代表请求参数必须传递
4.1.4 实体参数
  • 简单实体参数:请求参数名与形参对象属性名相同,定义POJO接收即可
  • 复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
4.1.5 数组集合参数

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

  • 数组:请求参数名与形参中数组变量名相同,可以直接使用数组封装
  • 集合:请求参数名与形参中集合变量名相同,通过@RequestParam绑定参数关系
4.1.6 日期参数
  • 日期参数:使用@DateTimeFormat注解完成日期参数格式转换

4.1.7 JSON参数

  • JSON参数:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用@RequestBody标识
4.1.8 路径参数
  • 路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数

4.2 响应数据

4.2.1 @ResponseBody
  • 类型:方法注解,类注解
  • 位置:Controller方法上/类上
  • 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
  • 说明:@RestController=@Controller + @ResponseBody
4.2.2 统一响应结果

Result(code,msg,data)

5. 分层解耦

  • 内聚:软件中各个功能模块内部的功能联系
  • 耦合:衡量软件中各个层/模块之间的依赖,关联程度
  • 软件设计原则:高内聚低耦合

5.1 三层架构

  • controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • service:业务逻辑层,处理具体的业务逻辑
  • dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删改查。

5.2 IOC & DI

  • 控制反转:Inversion Of Control,简称IOC,对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
  • 依赖注入:Dependency Injection,简称DI,容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
  • Bean对象:IOC容器中创建,管理的对象,称之为bean

步骤:

  • Service层及Dao层的实现类,交给IOC容器管理(添加@Component注解)

  • 为Controller及Service注入运行时,依赖的对象(在成员变量上添加@Autowired注解)

  • 运行测试

5.2.1 Bean的声明

要把某个对象交给IOC容器管理,需要在对应的类上加上如下直接之一:

注解说明位置
@Component声明bean的基础注解不属于以下三类时,用此注解
@Controller@Component的衍生注解标注在控制器上
@Service@Component的衍生注解标注在业务类上
@Repository@Component的衍生注解标注在数据访问类上(由于与mybatis整合,使用较少)
  • 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认类名首字母小写
  • 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller
5.2.2 Bean组件扫描
  • 前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描
  • @ComponentScan注解虽然没有显式配置,但实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包
5.2.3 Bean注入
  • Autowired注解,默认是按照类型进行,如果存在多个相同类型的bean,将会报错
    • 解决方案:
      1. @Primary
      2. @Autowired+@Qualifier(“bean的名称”)
      3. @Resource(name=“bean的名称”)
5.2.4 @Resource与@Autowired区别
  • @Autowired是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired默认是按照类型注入,而@Resource默认是按照名称注入

6. MyBatis

  • MyBatis是一款优秀的持久层框架,用于简化JDBC的开发
  • MyBatis是Apache的一个开源项目IBatis,2010年这个项目由apache迁移到了Google code,并且改名MyBatis,2013年11月迁移到Github
  • 官网:https://mybatis.org/mybatis-3/zh/index.html

6.1 MyBatis环境准备

  1. 准备工作(创建spring boot工程,数据库表user,实体类User)

  2. 引入MyBatis的相关依赖,配置MyBatis(数据库连接信息)(连接自己的数据库和密码)

    #驱动类名称
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    #数据库连接的url
    spring.datasource.url=jdbc:mysql://localhost:3306/db1
    #连接数据库的用户名
    spring.datasource.username=root
    #连接数据库的密码
    spring.datasource.password=123456
    
  3. 编写SQL语句(注解/XML)

    @Mapper //在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
    public interface UserMapper {
        //查询全部用户信息
        @Select("select * from user")
        public List<User> list();
    }
    
  4. 单元测试

    @SpringBootTest
    class SpringbootMybatisQuickstartApplicationTests {
        @Autowired
        private UserMapper userMapper;
    
        @Test
        public void testListUser(){
            List<User> userList = userMapper.list();
            userList.stream().forEach(user -> {
                System.out.println(user);
            });
        }
    }
    

6.2 配置SQL提示

  • 默认在mybatis中编写SQL语句是不识别的,可以做如下配置:

    右键->Show Context Actions->Inject Language or reference ->MySQL

  • 数据库名称爆红:

    • 产生原因:IDEA和数据库没有建立连接,不识别表信息
    • 解决方法:在IDEA 中配置MySQL数据库连接

6.3 JDBC

  • JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API
  • sun公司官方定义的一套操作所有关系型数据库的规范,即接口
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
  • 可以使用这套接口(JDBC)编程,真正执行的代码时驱动jar包中的实现类

6.4 数据库连接池

  • 数据库连接池是个容器,负责分配,管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

数据库连接池优势:

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏

标准接口:DataSource

  • 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口

  • 功能:获取连接

    Connection getConnection() throws SQLException;
    

常见产品:

  • C3P0
  • DBCP
  • Druid
    • Druid连接池是阿里巴巴开源的数据库连接池项目
    • 功能强大,性能优秀,是Java语言最好的数据库连接池之一
  • HiKari(springboot默认)
6.4.1 切换Druid数据库连接池

官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

<dependency>
    <groupld>com.alibaba</groupld>
    <artifactld>druid-spring-boot-starter</artifactld>
    <version>1.2.8</version>
</dependency>
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/数据库名
spring.datasource.druid.username=root
spring.datasource.druid.password=123456

6.5 版本选择

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

6.6 Lombok

  • lombok是一个实用的Java类库,能通过注解的形式自动生成构造器,getter/setter,equals,hashcode,toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率
注解作用
@Getter/@Setter为所有属性提供get/set方法
@ToString会给类自动生成易阅读的toString方法
@EqualsAndHashCode根据类所拥有的非静态字段重写equals方法和hashcode方法
@Data提供了更综合的生成代码功能(@Getter+@Setter+@ToString+@EqualsAndHashCode)
@NoArgsConstructor为实体类生成无参的构造器方法
@AllArgsConstructor为实体类生成除了static修饰的字段之外带有各参数的构造方法

Lombok的依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

lombok会在编译时,自动生成对应的Java代码,我们使用Lombok时,还需要安装一个Lombok的插件(Java自带)

6.7 Mybatis基本操作

6.7.1 环境准备
  1. 准备数据库表
  2. 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
  3. application.properties中引入数据库连接信息
  4. 创建对应的实体类 Emp(实体类属性采用驼峰命名)
  5. 准备Mapper接口 EmpMapper

1.准备数据库表

create table user(
     id int unsigned primary key auto_increment comment 'ID',
     name varchar(100) comment '姓名',
     age tinyint unsigned comment '年龄',
     gender tinyint unsigned comment '性别, 1:男, 2:女',
     phone varchar(11) comment '手机号'
) comment '用户表';

insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000');
insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001');
insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002');
insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003');
insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004');
insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');

select * from user;

-- 部门管理
create table dept(
     id int unsigned primary key auto_increment comment '主键ID',
     name varchar(10) not null unique comment '部门名称',
     create_time datetime not null comment '创建时间',
     update_time datetime not null comment '修改时间'
) comment '部门表';

insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now());

-- 员工管理
create table emp (
     id int unsigned primary key auto_increment comment 'ID',
     username varchar(20) not null unique comment '用户名',
     password varchar(32) default '123456' comment '密码',
     name varchar(10) not null comment '姓名',
     gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
     image varchar(300) comment '图像',
     job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
     entrydate date comment '入职时间',
     dept_id int unsigned comment '部门ID',
     create_time datetime not null comment '创建时间',
     update_time datetime not null comment '修改时间'
) comment '员工表';

INSERT INTO emp(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
    (1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
    (2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
    (3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
    (4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
    (5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
    (6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
    (7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
    (8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
    (9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
    (10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
    (11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
    (12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
    (13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
    (14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
    (15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
    (16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()),
    (17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());

-- 根据ID删除数据
delete  from emp where id = 17;

-- 登录
select count(*) from emp where username = 'zhangwuji' and password = '123456';


select count(*) from emp where username = 'zhangwuji' and password = '111';


select count(*) from emp where username = 'wuieuwiueiwuiew' and password = '' or '1' = '1';

3.application.properties中引入数据库连接信息:

#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/emp
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456

4.创建对应的实体类 Emp(实体类属性采用驼峰命名):

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private String gender;
    private String image;
    private Short job;
    private LocalDate entrydata;
    private Integer deptId;
    private LocalDateTime createTime;
    private LocalDateTime updatetime;
}

5.准备Mapper接口 EmpMapper:

package com.example.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface EmpMapper {
}
6.7.2 删除操作
  • SQL语句:

    delete from emp where id = 17;
    
  • 接口方法:

    @Mapper
    public interface EmpMapper {
        //根据ID删除数据
        @Delete("delete from emp where id = #{id}")
        public void delete(Integer id);
    
    }
    

    如果mapper接口方法形参只有一个普通类型的参数,#{…}里面的属性名可以随便写,如:#{id},#{value}

6.7.3 预编译SQL

优势:

  1. 性能更高
  2. 更安全(防止SQL注入)
  • SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法
6.7.3.1 参数占位符

#{…}

  • 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值
  • 使用时机:参数传递,都使用#{…}

${…}

  • 拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题
  • 使用时机:如果对表名,列表进行动态设置时使用
6.7.4 新增操作
  • SQL语句:

    -- 插入
    insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUE ('Tom','汤姆','1','1.jpg',1,'2005-01-01',1,now(),now())
    
  • 接口方法:

    //新增员工
        @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);
    

    在测试类中实现:

    @Test
        public void testInsert(){
            //构造员工对象
            Emp emp = new Emp();
            emp.setUsername("Tom");
            emp.setName("汤姆");
            emp.setImage("1.jpg");
            emp.setGender(String.valueOf((short)1));
            emp.setJob((short)1);
            emp.setEntrydate(LocalDate.of(2000,1,22));
            emp.setCreateTime(LocalDateTime.now());
            emp.setUpdateTime(LocalDateTime.now());
            emp.setDeptId(1);
    
            //执行新增员工信息操作
            empMapper.insert(emp);
        }
    
6.7.5 新增(主键返回)
  • 在数据添加成功后,需要获取插入数据库数据的主键

如何实现在插入数据之后返回所插入行的主键值?

  • 默认情况下,执行插入操作时,是不会主键值返回的。如果我们想要拿到主键值,需要在Mapper接口中的方法上添加一个Options注解,并在注解中指定属性useGeneratedKeys=true和keyProperty=“实体类属性名”

主键返回代码实现:

@Mapper
public interface EmpMapper {
    
    //会自动将生成的主键值,赋值给emp对象的id属性
    @Options(useGeneratedKeys = true,keyProperty = "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);

}
6.7.6 更新操作
  • SQL语句:

    -- 更新员工
    update emp set username = '',name='',gender='',image='',
                   job='',entrydate='',dept_id='',update_time='' where id=1;
    
  • 接口方法:

    @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);
    

    在测试类中实现:

    //更新员工
        @Test
        public void testUpdate(){
            //构造员工对象
            Emp emp = new Emp();
            emp.setId(18);
            emp.setUsername("Tom1");
            emp.setName("汤姆1");
            emp.setImage("1.jpg");
            emp.setGender(String.valueOf((short)1));
            emp.setJob((short)1);
            emp.setEntrydate(LocalDate.of(2000,1,1));
            emp.setUpdateTime(LocalDateTime.now());
            emp.setDeptId(1);
    
            //执行更新员工操作
            empMapper.update(emp);
        }
    
6.7.7 查询
6.7.7.1 根据ID查询
  • SQL语句:

    select * from emp where id = 18;
    
  • 接口方法:

    //根据ID查询员工
        @Select("select * from emp where id = #{id}")
        public Emp testGetById(Integer id);
    

    在测试类中实现:

     //根据ID查询员工
        @Test
        public void testGetById(){
            Emp emp = empMapper.testGetById(18);
            System.out.println(emp);
        }
    
6.7.7.2 数据封装
  • 实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装
  • 如果实体类属性名 和 数据库查询返回的字段名不一致,不能自动封装

解决方案:

  1. 起别名
  2. 手动结果映射
  3. 开启驼峰命名

方案一:给字段起别名,让别名与实体类属性一致

    @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,@Result注解手动映射封装

   @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的驼峰命名自动映射开关 —a_cloumn ----->aCloumn

    //根据ID查询员工
    @Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);

在application.properties中添加:

#开启mybatis的驼峰命名自动映射开关 ---a_cloumn ----->aCloumn
mybatis.configuration.map-underscore-to-camel-case=true
6.7.7.3 条件查询
  • SQL语句:

    -- 条件查询员工
    select * from emp where name like '%张%' and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ;
    
  • 接口方法:

    //条件查询员工
        @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);
    
    

    在上述接口方法中,使用了${name},则生成的不是预编译的SQL,会有性能低,不安全,存在SQL注入问题。

解决方法:使用concat()字符串拼接函数

  • SQL语句:
-- concat 字符串拼接函数
select * from emp where name like concat('%',?,'%') and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ;

  • 接口方法:
 @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(String name, Short gender, LocalDate begin,LocalDate end);

6.8 XML映射文件

1.规范:

  • XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
  • XML映射文件的namespace属性为Mapper接口全限定名一致
  • XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型一致

2.MybatisX:一款基于IDEA的快速开发Mybatis的插件,提高效率。

3.关于选择Mybatis注解或映射:

使用Mybatis的注解,主要是来完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

官方说明:https://mybatis.net.cn/getting-started.html

4.约束(可以直接去官网入门查询)

<?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">

6.9 动态SQL

  • 随着用户的输入或外部条件的变化而变化的SQL语句,称为动态SQQL

<if>:用于判断条件是否成立,使用test属性进行条件判断,如果条件为true,则拼接SQL。

<where>:where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的and或or。

<set>:动态地在行首插入SET关键字,并会删除额外的逗号。(用于update语句中)

6.9.1 条件查询

动态SQL语句:

<select id="list" resultType="com.itheima.pojo.Emp">
        select * from emp
        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>
    
        order by update_time desc
</select>

测试方法:

@Test
    public void testList(){
        //性别数据为null、开始时间和结束时间也为null
        List<Emp> list = empMapper.list("张", null, null, null);
        for(Emp emp : list){
            System.out.println(emp);
        }
    }

使用<where>标签代替SQL语句中的where关键字:

 <!--resultType:单条记录所封装的类型 -->
    <select id="list" resultType="com.example.pojo.Emp">
        select *
        from emp
            <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>
        order by update_time desc
            </where>
    </select>
6.9.2 更新
  • 动态更新员工信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新

修改Mapper接口:

@Mapper
public interface EmpMapper {
    //删除@Update注解编写的SQL语句
    //update操作的SQL语句编写在Mapper映射文件中
    public void update(Emp emp);
}

修改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.example.mapper.EmpMapper">
    <!--    动态更新员工-->
    <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>
        where id = #{id};
    </update>
 </mapper>

测试方法:

@Test
public void testUpdate2(){
        //要修改的员工信息
        Emp emp = new Emp();
        emp.setId(18);
        emp.setUsername("Tom111");
        emp.setName("汤姆111");

        emp.setUpdateTime(LocalDateTime.now());

        //调用方法,修改员工数据
        empMapper.update(emp);
}

使用<set>标签代替SQL语句中的set关键字:

<?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.example.mapper.EmpMapper">
    <!--    动态更新员工-->
    <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>
        where id = #{id};
        </set>
    </update>
</mapper>    
6.9.3 批量删除
  • SQL语句:

    delete from emp where id in(18,19);
    
  • 接口方法:

     //批量删除员工
        public void deleteByIds(List<Integer> ids);
    
  • XML映射文件:

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

可以对重复的代码片段进行抽取,将其通过<sql>标签封装到一个SQL片段,然后再通过<include>标签进行引用。

  • <sql>:定义可重用的SQL片段
  • <include>:通过属性refid,指定包含的sql片段

SQL片段: 抽取重复的代码

<sql id="commonSelect">
 	select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
</sql>

然后通过<include> 标签在原来抽取的地方进行引用。操作如下:

<select id="list" resultType="com.itheima.pojo.Emp">
    <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>

7. 开发

7.1 规范-Restful

  • REST(REpresentational State Transfer),表述性状态转换,它是一种软件架构风格

传统URL风格如下:

http://localhost:8080/user/getById?id=1     GET:查询id为1的用户
http://localhost:8080/user/saveUser         POST:新增用户
http://localhost:8080/user/updateUser       POST:修改用户
http://localhost:8080/user/deleteUser?id=1  GET:删除id为1的用户

原始的传统URL,定义比较复杂,而且将资源的访问行为将会对外暴露出来

基于REST风格URL如下:

http://localhost:8080/users/1  GET:查询id为1的用户
http://localhost:8080/users    POST:新增用户
http://localhost:8080/users    PUT:修改用户
http://localhost:8080/users/1  DELETE:删除id为1的用户

在REST风格的URL中,通过四种请求方式,来操作数据的增删改查。

  • GET : 查询
  • POST :新增
  • PUT :修改
  • DELETE :删除

基于REST风格,定义URL,URL将会更加简洁、更加规范、更加优雅。

注意事项:

  • REST是风格,是约定方式,约定不是规定,可以打破
  • 描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:users、emps、books…

7.2 开发流程

查看页面原型明确需求 --> 阅读接口文档 --> 思路分析 --> 接口开发 --> 接口测试 --> 前后端联调

8. 配置文件

8.1 参数配置化

  • 在调用工具类后,可以将参数配置在配置文件中

  • application.properties是springboot项目默认的配置文件,所以springboot程序在启动时会默认读取application.properties配置文件,可以使用一个现成的注解:@Value,获取配置文件中的数据。

  • @Value 注解通常用于外部配置的属性注入,具体用法为: @Value(“${配置文件中的key}”)

8.2 yml配置文件

#数据库连接信息
spring:
  profiles:
    active: dev
  datasource:
    driver-class-name:  com.mysql.cj.jdbc.Driver
    url:  jdbc:mysql://localhost:3306/tlias
    username:  root
    password: 123456
  #文件上传的配置
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
  #mybatis配置
  mybatis:
    configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      map-underscore-to-camel-case: true
#开启事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

  • SringBoot提供了多种属性配置方式:

    • application.properties
    • application.yml
    • application.yaml
  • 常见配置文件格式对比

    • XML:臃肿

    • properties:层级结构不清晰

    • yml格式的数据有以下特点:

      • 容易阅读
      • 容易与脚本语言交互
      • 以数据为核心,重数据轻格式

yml基本语法:

  • 大小写敏感
  • 数值前边必须有空格,作为分隔符
  • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(Idea 中会自动将Tab转换为空格)
  • 所进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略

当把application.properties换成application.yml,出现报错信息:No active profile set, falling back to 1 default profile: "default"时,

出现错误的原因为:

properties和yml的语法格式不一样,一般情况下,yml文件要指明是生产环境还是开发环境

(dev:开发环境 prod:生产环境 test:测试环境)

解决方法如下:

方法一:在Configurations中添加--spring.profiles.active=dev

在这里插入图片描述

方法二:在配置文件中添加

在这里插入图片描述

8.3 @ConfigurationProperties

可以直接将配置文件中配置项的值自动的注入到对象的属性中

  • 实现过程:

    1. 需要创建一个实现类,且实体类中的属性名和配置文件当中key的名字必须要一致

      比如:配置文件当中叫endpoints,实体类当中的属性也得叫endpoints,另外实体类当中的属性还需要提供 getter / setter方法

    2. 需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象

    3. 在实体类上添加@ConfigurationProperties注解,并通过perfect属性来指定配置参数项的前缀

@ConfigurationProperties与@Value

相同点:

  • 都是用来注入外部配置的属性的

不同点:

  • @Value注解只能一个一个的进行外部属性的注入
  • @ConfigurationProperties可以进行批量的将外部的属性配置注入到bean对象的属性中

如果要注入的属性非常的多,并且还想做到复用,就可以定义这么一个bean对象。通过 configuration properties 批量的将外部的属性配置直接注入到 bin 对象的属性当中。在其他的类当中,要想获取到注入进来的属性,直接注入 bin 对象,然后调用 get 方法,就可以获取到对应的属性值了

9. 事务管理

事务回滚:

事务回滚是指将该事务已经完成对数据库的更新操作撤销,在事务中,每个正确的原子都会被顺序执行,知道遇到错误的原子操作。

回滚:

回滚是删除由一个或多个部分完成的事务执行的更新,为保证应用程序、数据库或系统错误后还原数据库的完整性,需要使用回滚。

回滚包括程序回滚和数据回滚等类型(泛指程序更新失败,返回上一次正确状态行为)

9.1 Spring事务管理

  • 注解:@Transactional
  • 在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。
  • 位置:业务(service)层的方法上,类上,接口上
  • 作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕后,提交事务;出现异常,回滚事务

@Transactional注解书写位置:

  • 方法
    • 当前方法交给spring进行事务管理
    • 当前类中所有的方法都交由spring进行事务管理
  • 接口
    • 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

在配置文件中添加:

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

9.2 事务属性

9.2.1 回滚

rollbackFor

  • 指定回滚事物的范围
  • 默认情况下,只有出现RuntimeException(运行时异常)才回滚异常,rollbackFor属性用于控制出现何种异常类型,回滚事务
9.2.2 传播行为

propagation

  • 事务传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
属性值含义
REQUIRED【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW需要新事务,无论有无,总是创建新事务
SUPPORTS支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛异常
NEVER必须没事务,否则抛异常
  • REQUIRED :大部分情况下都是用该传播行为即可。

  • REQUIRES_NEW :当不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。

10. AOP

10.1 AOP概述

  • AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),面向切面编程就是面向特定方法编程。

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

AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)。

AOP优点:

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

10.2 AOP入门

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

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

10.3 AOP核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(含方法执行时的相关信息)
  • 通知:Advice,指重复的逻辑,即共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象

10.4 AOP进阶

10.4.1 通知类型
  1. @Around:环绕通知,此注解标注的通知方法在目标方法前,后都执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会被执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
  • @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值
10.4.2 通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

执行顺序:

  1. 不同切面类中,默认按照切面类的类名字母排序:

    • 目标方法前的通知方法:字母排名靠前的先执行
    • 目标方法后的通知方法:字母排名靠后的后执行
  2. 用@Order(数字)加在切面类上来控制顺序

    • 目标方法前的通知方法:数字小的先执行

    • 目标方法后的通知方法:数字小的后执行

10.4.3 切入点表达式
  • 切入点表达式:描述切入点方法的一种表达式
  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式;
    1. execution(…):根据方法的签名来匹配
    2. @annotation(…):根据注解匹配
execution

execution主要根据方法的返回值,包名宁,类名,方法名,方法参数等信息来匹配,语法为:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throw 异常)
  • 其中带?的表示可以省略的部分

    • 访问修饰符:可省略(如:public,protected)
    • 包名.类名:可省略(不建议省略,范围扩大可能会影响实际效果)
    • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
  • 可以使用通配符描述切入点

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

      execution(* com.* .service.*.update*(*))
      
    • ..多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

      execution(* com.dream..DeptService.*(..))
      

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

@annotation
  • @annotation切入点表达式,用于匹配标识有特定注解的方法

    1. 编写自定义注解

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

  • execution切入点表达式

    • 根据所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
    • 如果要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐
  • annotation 切入点表达式

    • 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,需要自定义一个注解,但是相对来比较灵活。需要匹配哪个方法,就在方法上加上对应的注解就可以了
10.4.4 连接点
  • 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等
    • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
    • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJointPoint的 父类型

10.5 AOP案例

10.5.1 需求分析

将案例中增、删、改相关接口的操作日志记录到数据库表中

  • 就是当访问部门管理和员工管理当中的增、删、改相关功能接口时,需要详细的操作日志,并保存在数据表中,便于后期数据追踪。

操作日志信息包含:

  • 操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

所记录的日志信息包括当前接口的操作人是谁操作的,什么时间点操作的,以及访问的是哪个类当中的哪个方法,在访问这个方法的时候传入进来的参数是什么,访问这个方法最终拿到的返回值是什么,以及整个接口方法的运行时长是多长时间。

10.5.2 步骤
  • 准备工作
    1. 引入AOP的起步依赖
    2. 导入资料中准备好的数据库表结构,并引入对应的实体类
  • 编码实现
    1. 自定义注解@Log
    2. 定义切面类,完成记录操作日志的逻辑
10.5.3 具体实现
  1. AOP起步依赖
<!--AOP起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 创建数据表
-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
  1. 实体类
//操作日志实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //主键ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}
  1. Mapper接口
@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}
  1. 自定义注解@Lo
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Target(ElementType.METHOD) //作用在方法上
public @interface Log {
}
  1. 定义切面类,完成记录操作日志的逻辑
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {
    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.example.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable{

        //操作人ID -当前登陆员工ID
        //获取请求头中的jwt令牌,解析令牌
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer operateUser = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getTarget().getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] args = joinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        long begin = System.currentTimeMillis();
        //调用原始目标方法运行
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        // 操作耗时
        Long costTime = end - begin;

        //方法返回值
        String returnValue = JSONObject.toJSONString(result);

        //记录日志
        OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
        operateLogMapper.insert(operateLog);

        log.info("AOP记录操作日志:{}",operateLog);
        return result;
    }
}
  1. 修改业务实现类,在增删改业务方法上添加@Log注解

获取request对象,从请求头中获取到jwt令牌,解析令牌获取出当前用户的id

10.5.4 效果

在这里插入图片描述

11. SpringBoot原理篇

11.1 配置

  • SpringBoot中支持三种格式的配置文件:

    • application.properties
    • application.yml
    • application.yaml
  • 三种配置文件的优先级(从高到低)为:

    1. properties配置文件
    2. yml配置文件
    3. yaml配置文件

虽然Springboot支持多种格式的配置文件,但是在项目开发时,推荐使用统一的一种格式的配置(yml)

在SpringBoot项目当中除了以上3种配置文件外,SpringBoot为了增强程序的扩展性,除了支持配置文件的配置方式以外,还支持另外两种常见的配置方式:

  • Java系统属性

    -Dserver.port=9000
    
  • 命令行参数

    --server.port=10010
    

在这里插入图片描述

11.1.1 配置优先级

优先级(从低到高):

  • application.yaml(忽略)
  • application.yml
  • application.properties
  • java系统属性(-Dxxx=xxx)
  • 命令行参数(–xxx=xxx)

11.2 Bean管理

11.2.1 Bean是什么

1、Java面向对象,对象有方法和属性,那么就需要对象实例来调用方法和属性(即实例化);

2、凡是有方法或属性的类都需要实例化,这样才能具象化去使用这些方法和属性;

3、规律:凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解;

4、Bean通过反射、代理来实现,能代表类所拥有的东西;

5、在Spring中,标识一个@符号,那么Spring就会从这里拿到一个Bean或者给出一个Bean

二、注解分为两类:

1、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;

2、一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。

总结:

1、凡是子类及带属性、方法的类都注册Bean到Spring中,交给它管理;

2、@Bean 用在方法上,告诉Spring容器,可以从下面这个方法中拿到一个Bean

11.2.2 获取Bean
  • 默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过以下三种方式:

    • 根据name获取bean:

      Object getBean(String name)
      
    • 根据类型获取bean

      <T> T getBean(Class<T> requiredType)
      
    • 根据name获取bean(带类型转换)

      <T> T getBean(String name, Class<T> requiredType)
      
11.2.3 Bean作用域
  • Spring支持五种作用域,后三种在web环境中才生效
作用域说明
singleton容器内同名称的bean只有一个实例(单例)(默认)
prototype每次使用该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例(web环境中)
session每个会话范围内会创建新的实例(web环境中)
application每个应用范围内会创建新的实例(web环境中)

可以借助Spring中的@Scope注解来进行配置作用域:

@Scope("prototype")
  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例
  • 实际开发当中,绝大部分的Bean是单例的,也就是说绝大多数的Bean不需要配置scope属性
11.2.4 第三方Bean
  • 如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Compontent及原生注解声明bean的,需要用到@Bean注解
  • 若要管理第三方bean对象,可以对这些bean进行集中分类配置,可以通过@Configuration注解声明一个配置类

注意事项:

  • 如果通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就算方法名
  • 如果第三方bean需要依赖其他bean对象,直接在bena定义方法中设置形参即可,容器会根据类型字段装配

@Component及衍生注解与@Bean注解使用场景?

  • 项目中定义的,使用@Component及其衍生注解
  • 项目中引入第三方的,使用@Bean注解

11.3 Springboot原理

11.3.1 起步依赖
  • 使用SpringBoot,不需要像繁琐的引入依赖,只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web

  • Maven的依赖传递

    • 在SpringBoot给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常见依赖(官网地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters)。

    • 比如:springboot-starter-web,这是web开发的起步依赖,在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。

起步依赖的原理就是Maven的依赖传递

11.3.2 自动配置
  • SpriingBoot的自动配置就算当spring容器启动后,一些配置类,bean对象就自动存入到了IOC容器中,不需要手动声明,简化了开发,省去了繁琐的配置操作

引入进来的第三方依赖当中的bean以及配置类为什么没有生效?

  • 在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。
  • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。
  • 当前包:com.itheima, 第三方依赖中提供的包:com.example(扫描不到)

解决问题:

  • 方案1:@ComponentScan 组件扫描
  • 方案2:@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)
@ComponentScan 组件扫描
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要扫描的包
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}
@Import 导入

导入形式主要有以下几种:

  1. 导入普通类

  2. 导入配置类

  3. 导入ImportSelector接口实现类

  4. 使用第三方依赖提供的 @EnableXxxxx注解

关于第四种:

  • 第三方依赖中提供的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig { 
}
  • 在使用时只需在启动类上加上@EnableXxxxx注解即可
@EnableHeaderConfig  //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}
自动配置原理

自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:

  • @SpringBootConfiguration
    • 声明当前类是一个配置类
  • @ComponentScan
    • 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)
  • @EnableAutoConfiguration
    • 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)
      • 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

@Conditional

@Conditional注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
  • 位置:方法、类
  • @Conditional本身是一个父注解,派生出大量的子注解:
    • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
    • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

11.4 总结

  1. web后端开发现在基本上都是基于标准的三层架构进行开发的,在三层架构当中,Controller控制器层负责接收请求响应数据,Service业务层负责具体的业务逻辑处理,而Dao数据访问层也叫持久层,就是用来处理数据访问操作的,来完成数据库当中数据的增删改查操作

  2. 如果在执行具体的业务处理之前,需要去做一些通用的业务处理,比如:要进行统一的登录校验,要进行统一的字符编码等这些操作时,就可以借助于Javaweb当中三大组件之一的过滤器Filter或者是Spring当中提供的拦截器Interceptor来实现

  3. 而为了实现三层架构层与层之间的解耦,学习了Spring框架当中的第一大核心:IOC控制反转与DI依赖注入

    所谓控制反转,指的是将对象创建的控制权由应用程序自身交给外部容器,这个容器就是我们常说的IOC容器或Spring容器。

    而DI依赖注入指的是容器为程序提供运行时所需要的资源。

  4. Filter过滤器、Cookie、 Session这些都是传统的JavaWeb提供的技术。

    JWT令牌、阿里云OSS对象存储服务,是现在企业项目中常见的一些解决方案。

    IOC控制反转、DI依赖注入、AOP面向切面编程、事务管理、全局异常处理、拦截器等,这些技术都是 Spring Framework框架当中提供的核心功能。

    Mybatis就是一个持久层的框架,是用来操作数据库的。

  5. 在Spring框架的生态中,对web程序开发提供了很好的支持,如:全局异常处理器、拦截器这些都是Spring框架中web开发模块所提供的功能,而Spring框架的web开发模块,也称为:SpringMVC

  6. SSM,就是由:SpringMVC、Spring Framework、Mybatis三块组成。

    基于传统的SSM框架进行整合开发项目会比较繁琐,而且效率也比较低,所以在现在的企业项目开发当中,基本上都是直接基于SpringBoot整合SSM进行项目开发的。
    在这里插入图片描述

12. Maven高级

12.1 分模块设计与开发

  1. 分模块设计,顾名思义指的就是在设计一个 Java 项目的时候,将一个 Java 项目拆分成多个模块进行开发
  2. 分模块设计就是将项目按照功能/结构拆分成若干个子模块,方便项目的管理维护、拓展,也方便模块键的相互调用、资源共享
  3. 分模块设计的优点:方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享
  4. 分模块设计需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分

12.2 继承与聚合

12.2.1 继承
  • 概念:继承描述的是两个工程之间的关系,与Java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
  • 作用:简化依赖配置,统一管理依赖
  • 实现:<parent></parent>

jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)

war:普通web程序打包,需要部署在外部的tomcat服务器中运行

pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理

继承关系实现:

  1. 创建maven模块tlias-parent,该工程为父工程,设置打包方式pom(默认jar)
  2. 在子工程的pom.xml文件中,配置继承关系
  3. 在父工程中配置各个过程共有的依赖(子工程会自动继承父工程的依赖)

注意:

  • 在子工程中,配置了继承关系之后,坐标中的groupId是可以是可以省略的,因为会自动继承父工程的
  • relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)
  • 若父子工程都配置了同一个依赖的不同版本,以子工程的为准
12.2.2 版本锁定
<dependencyManagement>
  • 在maven中,可以在父工程的pom文件中通过<dependencyManagement>来统一管理依赖版本
  • 子工程引入依赖时,无需指定<version>版本号,父工程统一管理,变更版本依赖,只需在父工程中统一变更
自定义属性/引用属性

在这里插入图片描述

<dependencyManagement><dependencies> 的区别:

  • <dependencies> 是直接依赖,在父工程配置了依赖,子工程会直接继承下来。
  • <dependencyManagement> 是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
12.2.3 聚合
  • 聚合:将多个模块组织成一个整体,同时进行项目的构建

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

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

  • maven中可以通过<modules>设置当前聚合工程所包含的子模块名称

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

12.3 继承与聚合对比

  • 作用

    • 聚合用于快速构建项目

    • 继承用于简化依赖配置、统一管理依赖

  • 相同点:

    • 聚合与继承的pom.xml文件打包方式均为pom,通常将两种关系制作到同一个pom文件中

    • 聚合与继承均属于设计型模块,并无实际的模块内容

  • 不同点:

    • 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些

    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

12.4 私服

  • 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题
  • 依赖查找顺序:
    • 本地仓库
    • 私服
    • 中央仓库
12.4.1 资源上传与下载

第一步配置:在maven的配置文件中配置访问私服的用户名、密码。

第二步配置:在maven的配置文件中配置连接私服的地址(url地址)。

第三步配置:在项目的pom.xml文件中配置上传资源的位置(url地址)。

配置好了上述三步之后,要上传资源到私服仓库,就执行执行maven生命周期:deploy。

私服仓库说明:

  • RELEASE:存储自己开发的RELEASE发布版本的资源。
  • SNAPSHOT:存储自己开发的SNAPSHOT发布版本的资源。
  • Central:存储的是从中央仓库下载下来的依赖。

项目版本说明:

  • RELEASE(发布版本):功能趋于稳定、当前更新停止,可以用于发行的版本,存储在私服中的RELEASE仓库中。
  • SNAPSHOT(快照版本):功能不稳定、尚处于开发中的版本,即快照版本,存储在私服的SNAPSHOT仓库中。

具体操作:

1.设置私服的访问用户名/密码(在自己maven安装目录下的conf/settings.xml中的servers中配置)

2.设置私服依赖下载的仓库组地址(在自己maven安装目录下的conf/settings.xml中的mirrors、profiles中配置)

3.IDEA的maven工程的pom文件中配置上传(发布)地址(直接在tlias-parent中配置发布地址)

配置完成之后,我们就可以在tlias-parent中执行deploy生命周期,将项目发布到私服仓库中。

13. 总结

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值