后端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 实际项目资源
java Java源代码目录
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\repositorylocalRepository>
    
  3. 配置阿里云私服(用来提高下载速度):修改conf/settings.xml中的标签,为其添加如下子标签:

    <mirror>
            <id>nexus-aliyunid>
            <mirrorOf>*mirrorOf>
            <name>Nexus aliyunname>
            <url>http://maven.aliyun.com/nexus/content/groups/publicurl>
    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(默认) Y Y Y log4j
test - Y - junit
provided Y Y - servlet-api
runtime - Y Y jdbc驱动

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

常见的响应状态码:

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


DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

6.9 动态SQL

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

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

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

:动态地在行首插入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);
        }
    }

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

 
    <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映射文件:


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

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




    
    
        update emp
        
            username   =#{username},
        name =#{name},
        gender =#{gender},
        image =#{image},
        job =#{job},
        entrydate =#{entrydate},
        dept_id =#{deptId},
        update_time =#{updateTime}
        where id = #{id};
        
    
    

6.9.3 批量删除

  • SQL语句:

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

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

       
        
        <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片段
  • :通过属性refid,指定包含的sql片段

SQL片段: 抽取重复的代码

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

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

<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

后端Web开发学习笔记_第1张图片

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

后端Web开发学习笔记_第2张图片

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.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
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起步依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
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 效果

后端Web开发学习笔记_第3张图片

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
    

后端Web开发学习笔记_第4张图片

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进行项目开发的。
    后端Web开发学习笔记_第5张图片

12. Maven高级

12.1 分模块设计与开发

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

12.2 继承与聚合

12.2.1 继承

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

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

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

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

继承关系实现:

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

注意:

  • 在子工程中,配置了继承关系之后,坐标中的groupId是可以是可以省略的,因为会自动继承父工程的
  • relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)
  • 若父子工程都配置了同一个依赖的不同版本,以子工程的为准

12.2.2 版本锁定

  • 在maven中,可以在父工程的pom文件中通过来统一管理依赖版本
  • 子工程引入依赖时,无需指定版本号,父工程统一管理,变更版本依赖,只需在父工程中统一变更
自定义属性/引用属性

后端Web开发学习笔记_第6张图片

的区别:

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

12.2.3 聚合

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

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

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

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

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

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. 总结

后端Web开发学习笔记_第7张图片

你可能感兴趣的:(spring,boot,java)