0%

一、SpringMVC简介

1.1 什么是MVC

  • MVC:软件架构思想
  • M:模型层,指工程中的bean
  • V:视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
  • C:控制层,指工程中的servlet,用于接收请求和响应数据
  • MVC工作流程:视图层发送请求到服务器,服务器请求被控制层接收,控制层调用响应的模型层处理请求,处理完之后将结果返回控制层,控制层,再根据请求处理的结果找到相应的视图层,渲染数据后最终相应给浏览器

二、一个案例

2.1 创建MAVEN工程

  • 在pom文件中设置打包方式为war包
1
<packaging>war</packaging>

2.2 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>

<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>

</dependencies>

2.3 创建webapp

  • 在mian目录下创建webapp文件夹
  • 在webapp下创建文件夹WEB-INF
  • 在项目结构窗口的modules窗口中添加web.xml配置,注意路径要在WEB-INF前添加上src/main/webapp
  • 在WEB-INF中创建templates/index.html

2.4 配置web.xml

2.4.1 默认配置方式

  • 此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为<servlet-name>-servlet.xml,例如以下配置所对应的SpringMVC的配置文件位于WEB-INF下,文件名为SpringMVC-servlet.xml
  • 不推荐,因为spring配置资源一般都是放在resources下的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--  配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理  -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
<!--
设置SpringMVC核心控制器所能处理的请求路径
/ 所匹配的请求可以是/login或.html/.css/.js等方式的请求路径
但不匹配.jsp请求路径的请求

-->
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

2.4.2 扩展配置方式(推荐)

  • 加入init-param,设置配置资源路径
  • 配置好init-param后,需要在resources中创建springMVC.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!--  配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理  -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!-- 配置SpringMVC配置文件的位置和名称,然后在reources中创建xml配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>

<!-- 将前端控制器DispatcherServlet启动时间提前到服务器启动的时候 -->
<!-- 提升第一次访问的速度 -->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<!--
设置SpringMVC核心控制器所能处理的请求路径
/ 所匹配的请求可以是/login或.html/.css/.js等方式的请求路径
但不匹配.jsp请求路径的请求

-->
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

2.5 配置springMVC.xml

2.5.1 开启自动扫描

1
2
<!-- 自动扫描包 -->
<context:component-scan base-package="com.kuang.mvc.controller"/>

2.5.2 添加Thymeleaf视图解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>

<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>

2.5.3 静态资源处理配置

  • 暂时不需要配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>

<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

2.6 控制层实现

  • 创建controller层实现类
1
2
3
4
5
6
7
8
9
10
@Controller
public class HelloController {

// 请求地址映射,实现页面跳转
@RequestMapping(value = "/")
public String index(){
return "index";
}
}

2.7 编写页面

2.7.1 添加thymeleaf命名空间

  • 在html标签内写入xmlns:th="http://www.thymeleaf.org"
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>

2.8 配置服务器

  • 使用tomcat服务器部署项目并运行
  • 注意这些选项的配置
    • 简化访问路径和调试方式

三、RequestMapping注解

3.1 简介

  • 将请求和控制器方法联系起来

3.2 注解位置

  • @RequestMapping标识一个类:设置映射请求的请求路径的初始信息

  • @RequestMapping标识一个方法:设置映射请求请求路径的具体信息

3.3 注解属性

3.3.1 value

  • @RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
    • 一个方法可以对应多个请求
1
2
<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
1
2
3
4
5
6
@RequestMapping(
value = {"/testRequestMapping", "/test"}
)
public String testRequestMapping(){
return "success";
}

3.3.2 method

  • 配置请求方式,如果浏览器请求方式与method方式不匹配,则会报错405
  • @RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求
1
2
3
4
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
<input type="submit">
</form>
1
2
3
4
5
6
7
@RequestMapping(
value = {"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
return "success";
}
  • @RequestMapping的派生注解
    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @DeleteMapping
  • 常用的请求方式:get、post、put、delete

  • 但是目前浏览器只支持get和post,若在form表单提交时,为method设置了其他请求方式的字符串(put或delete),则按照默认的请求方式get处理

  • 若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter,在RESTful部分会讲到

3.4 ant风格路径

  • RequestMapping设置的请求地址时会使用到以下匹配字符
    • ?:表示任意的单个字符
    • *:表示任意的0个或多个字符
    • **:表示任意的一层或多层目录

3.5 占位符(RESTful)

  • 原始请求路径:/deleteUser?id=1
  • RESTful风格请求地址:/deleteUser/{id}

    • 比如请求地址为/deleteUser/1,与原始请求路径方式作用一致
    • {xxx}匹配请求参数,可以使用多个,如:/deleteUser/{id}/{username}
  • 控制器获取RESTful风格请求的参数方式:

    • 需要使用注解@PathVariable,对应请求参数
  • 配置了RESTful风格请求路径,就无法使用原始请求路径进行访问了
1
2
3
4
@RequestMapping("/testRequestMapping/{id}",)
public String testRequestMapping(@PathVariable("id")Integer id){
return "success";
}

四、获取请求参数

4.1 RequestParam注解

  • 配置需要的请求参数名
  • value可以配置参数名
  • required可以设置请求参数是否必须要传递,默认为true
    • 设置为false之后,没有传这个参数,方法形参得到的值就是null
  • defaultValue表示为形参设置默认值
    • 前端需要传递参数但没有传递值,如/user?id=&username=
1
2
3
4
@RequestMapping("/testRequestMapping")
public String testRequestMapping(@RequestParam(value="id",required = false,defaultValue = 1)Integer id){
return "success";
}

4.2 RequestHeader注解

  • 同RequestParam,拥有value/required/defaultValue属性
  • 用于获取请求头信息

4.3 CookieValue注解

  • 同RequestParam,拥有value/required/defaultValue属性
  • 用于获取cookie信息

4.4 实体类获取请求参数

  • 当表单的name属性和实体类的属性一一对应时,使用实体类形参即可获取到请求信息
1
2
3
4
@RequestMapping("/testRequestMapping")
public String testRequestMapping(User user){
return "success";
}
  • 以上方式可以接收表单请求数据,但是user数据在控制台打印时会出现乱码的问题
  • get请求数据乱码可以修改tomcat配置文件server.xml的URIEncoding属性
  • post请求乱码需要在web.xml中配置过滤器进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--  配置过滤器  -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

五、域对象共享数据

5.1 Servlet四大作用域

  • page:对应pageContext对象,JSP页面容器(page对象),当前页面有效(重定向/请求转发都会使其无效)
  • request:对应HttpServletContext对象,请求对象,同一次请求有效(请求转发有效、重定向无效)
  • session:对应HttpSession对象,会话对象,同一次会话有效(请求转发/重定向有效,关闭/切换浏览器无效)
  • application:对应ServletContext对象,全局对象,全局有效(整个项目运行期间/切换浏览器都有效,关闭服务、其他项目中无效)
  • 有效的意思是说以上4个对象使用setAttribute()方法后的有效范围
  • 尽量使用最小的范围,作用域越大,系统开销越大
  • 经常改动的数据一般使用较小的作用域,不常变动的数据一般使用较大的作用域

5.2 request域对象共享数据

  • 控制器方法执行后都会统一返回ModelAndView对象,其中包含了页面view对象信息和向域对象model中共享的数据

  • 通过ServletAPI向request域共享对象

1
2
3
4
5
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope", "hello,servletAPI");
return "success";
}
  • thymeleaf/index页面
1
<a th:href="@{/success}">request域测试</a>
  • thymeleaf/success页面
1
<p th:text="${name}"></p>

5.3 使用ModelAndView向request域对象共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//返回ModelAndView提交前端控制器进行处理
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("testScope", "hello,ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
return mav;
}

5.4 使用Model向request域对象共享数据

1
2
3
4
5
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testScope", "hello,Model");
return "success";
}

5.5 使用map向request域对象共享数据

1
2
3
4
5
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testScope", "hello,Map");
return "success";
}

5.6 使用ModelMap向request域对象共享数据

1
2
3
4
5
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testScope", "hello,ModelMap");
return "success";
}

5.7 Model、ModelMap、Map的关系

  • Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的
  • BindingAwareModelMap可以实例化Model、ModelMap、Map

5.8 向session域共享数据

1
2
3
4
5
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}
  • 访问session域的共享数据
1
<p th:text="${session.name}"></p>

5.9 向application域共享数据

1
2
3
4
5
6
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}
  • 访问application域的共享数据
1
<p th:text="${application.name}"></p>

六、SpringMVC视图

6.1 介绍

  • SpringMVC中的视图是View接口,视图的作用是渲染页面,将模型Model中的数据展示给用户
  • SpringMVC中的视图种类很多,默认有转发视图InternalResourceView和重定向视图RedirectView
  • 当工程引入jstl的依赖,转发视图会自动转换为jstlView
  • 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后得到的是ThymeleafView

6.2 请求转发和重定向

  • 以下示例,客户端登录需要经过后端校验,然后进行页面跳转
区别 请求转发 重定向
地址栏是否改变 不变check.jsp 改变success.jsp
是否保留第一次请求的数据 保留 不保留
请求次数 1 2
跳转发生的位置 服务端 客户端发出的第二次跳转

6.2.1 请求转发和重定向的使用情况

  • 请求转发时,如果是一个表单提交数据(如用户登录),地址栏会保持在check
    • 这时如果用户刷新页面,就会出现用户需要重新提交表单的情况
  • 对于需要频繁的提交数据的情况,使用请求转发就又可能出现重复提交数据的情况

6.2.2 示例

(1)分析

  • 写了一个index登录表单,success和error页面,比较下请求转发和重定向的区别
  • index用户登录表单发起/check,地址栏先显示/check,后端处理请求
  • 注:请求转发和重定向都要写出对应的请求方法,比如下面的/success和/error都需要写出来,如下图:
    • 从图中可以看到,请求转发是在服务器内部发生请求的调用,/check->/error
    • 重定向方式是浏览器行为,浏览器会重新发起一次请求才能进行页面的跳转

(2)实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller
public class ShowController {
@RequestMapping("/")
public String index(){
return "index";
}

@RequestMapping("/check")
public String check(String username,String pwd){
if(username.equals("zs") && pwd.equals("123123")){
return "forward:/success";
}
else{
return "forward:/error";
}
}

@RequestMapping("/success")
public String success(){
return "success";
}

@RequestMapping("/error")
public String error(){
return "error";
}
}

6.2.3 小结

  • 如果当前请求依赖其他请求,就需要用到请求转发和重定向的方式
  • 如果只是普通的页面跳转则不需要这些操作
  • SpringMVC有三个视图解析器:ThymeleafView、InternalResourceView(请求转发解析器)、RedirectView(重定向解析器)
    • return返回值中不加任何前缀的字段会交给ThymeleafView解析
    • return返回值中加前缀forward:的字段会交给InternalResourceView解析
    • return返回值中加前缀redirect:的字段会交给RedirectView解析

6.3 ThymeleafView

  • 当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

6.4 转发视图

  • SpringMVC中默认的转发视图是InternalResourceView
  • SpringMVC中创建转发视图的情况:
    • 当控制器方法中所设置的视图名称以”forward:”为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀”forward:”去掉,剩余部分作为最终路径通过转发的方式实现跳转
  • 示例:
1
2
3
4
@RequestMapping("/testForward")
public String testForward(){
return "forward:/testHello";
}

6.5 重定向视图

  • SpringMVC中默认的重定向视图是RedirectView

  • 当控制器方法中所设置的视图名称以”redirect:”为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀”redirect:”去掉,剩余部分作为最终路径通过重定向的方式实现跳转

  • 示例:
1
2
3
4
@RequestMapping("/testRedirect")
public String testRedirect(){
return "redirect:/testHello";
}

重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径

6.6 视图控制器view-controller

  • 当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
  • 需要在springMVC.xml中添加
1
2
3
4
5
<!--
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>

当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件(springMVC.xml)中设置开启mvc注解驱动的标签:

七、RESTful

7.1 RESTFul简介

  • 全称:Representational State Transfer,表现层状态转移

7.2 RESTFul的实现

  • RESTFul风格的URL请求地址
操作 传统方式 REST风格
查询 getUserById?id=1 user/1
保存 saveUser user
删除 deleteUser?id=1 user/1
更新 updateUser user

7.3 PUT和DELETE请求处理

7.3.1 简介

  • 由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
  • SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求

7.3.2 HiddenHttpMethodFilter配置

  • 在web.xml中过滤器中注册HiddenHttpMethodFilter
1
2
3
4
5
6
7
8
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • 配置完HiddenHttpMethodFilter之后,如果是用表单进行修改,需要如下设置
  • HiddenHttpMethodFilter 处理put和delete请求的条件:
    • 当前请求的请求方式必须为post
    • 当前请求必须传输请求参数_method
1
2
3
4
5
6
7
8
9
10
11
12
<!--修改用户信息表单,method必须指定为post-->
<form th:action="@{/user}" method="post">
<!--
配置完过滤器后一定要在表单下添加_method字段,交给后端过滤器进行处理
在value中指定修改操作所确定的PUT请求
-->
<input type="hidden" name="_method" value="PUT">

用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<input type="submit" value="修改"><br/>
</form>

7.3.3 小结

  • 过滤器的执行顺序:当配置有多个过滤器时,SpringMVC会按照filter-mapping的编写顺序进行配置
  • 对于之前写的CharacterEncodingFilter,如果前面写了处理请求的其他过滤器,那么处理编码的过滤器就会失效,所以我们要先配置编码过滤器,再编写请求过滤器

7.4 RESTFul案例

7.4.2 静态资源访问

  • springMVC.xml中没有配置静态资源时,在static目录中存放的css和js等静态文件就不会被访问到,前端就不会展示静态资源效果
  • 主要原因的SpringMVC的DispatchServlet找不到静态资源文件,所以需要配置springMVC.xml开启默认DefaultServlet来访问静态资源,这样在浏览器的地址栏中就可以访问到静态资源文件了
    • tomcat的配置目录conf中也有web.xml,里面就配置了DefaultServlet
  • 必须开启静态资源的访问才能保证静态资源能够被正确访问到,在springMVC.xml中添加如下标签
1
2
3
4
5
<!--开放对静态资源的访问-->
<mvc:default-servlet-handler/>

<!--开启mvc注解驱动-->
<mvc:annotation-driven/>
  • 同时需要注意的是,mvc注解驱动也要同时开启,不然控制器请求会全部被默认的Servlet获取到
  • 配置完两个标签后,前端请求会先被DispatchServlet获取到,DispatchServlet处理不了,就会把请求交给DefaultServlet处理,DefaultServlet再处理不了,浏览器就会报404

八、HttpMessageConverter报文信息转换器

8.1 简介

  • HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文
  • HttpMessageConverter提供了两个注解和两个类型
    • 注解:@RequestBody,@ResponseBody
    • 类型:RequestEntity,ResponseEntity
  • RequestEntity可以接收整个请求报文

8.2 @RequestBody

  • @RequestBody可以获取请求体,需要控制器设置一个形参
  • @RequestBody标识形参后,形参就可以获取到请求体的信息
1
2
3
4
5
<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
1
2
3
4
5
6
7
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}

// 输出结果:requestBody:username=admin&password=123456

8.3 RequestEntity

  • RequestEntity是封装请求报文的一种类型,控制器以RequestEntity对象作为形参接收请求信息
  • RequestEntity有一些方法可以获取到请求信息
    • getHeaders():获取请求头信息
    • getBody():获取请求体信息
1
2
3
4
5
6
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}

8.4 @ResponseBody

  • @ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
  • 以下控制器方法会直接在页面打印出”success”,而不是作为success页面进行显示
  • 综上:@ResponseBody可以直接向浏览器响应数据,如果不使用该注解,则可以使用传统方式HttpServletResponse处理响应
1
2
3
4
5
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}

8.5 SpringMVC处理Json

  • SpringMVC并不能直接使用将ReponseBody注解的控制器方法的返回值直接转换为Json数据,必须依赖其他包完成Java对象到Json数据的转换

8.5.1 Jackson包

  • 导入Jackson依赖
1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
  • 在SpringMVC的核心配置文件中开启mvc注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串,注意不是Json对象
1
<mvc:annotation-driven />
  • 在处理器方法上使用@ResponseBody注解进行标识
  • 将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串
1
2
3
4
5
6
7
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1001,"admin","123456",23,"男");
}

// 浏览器响应结果:{"id":1001,"username":"admin","password":"123456","age":23,"sex":"男"}

8.6 @RestController注解

  • @RestController注解是springMVC提供的一个复合注解,整合了@Controller和@ResponseBody两个注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解

8.7 ResponseEntity

  • ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
  • 可以实现文件下载

九、文件上传和下载

9.1 文件下载

  • 使用ResponseEntity实现下载文件的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径,即项目resources目录下的文件地址
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}

9.2 文件上传

  • 文件上传要求form表单的请求方式必须为post,并且添加属性enctype=”multipart/form-data”
  • SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

9.2.1 导入依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

9.2.2 SpringMVC添加配置

  • 必须给这个bean配置id属性”multipartResolver” ,否则这个bean就访问不到
1
2
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

9.2.3 控制器方法

  • 如果不处理重名情况,当重新上传时会覆盖掉原来上传的重名的文件,这不是我们想要的情况,所以要通过UUID处理重名的情况,为文件重命名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题,获取文件扩展名,包括"."
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if(!file.exists()){
file.mkdir();
}
// File.separator为文件分隔符
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}

十、拦截器

11.1 过滤器与拦截器

11.2 拦截器配置

  • 创建拦截器类FirstInterceptor,并重写三个方法
    • preHandle()
    • postHandle()
    • afterCompletion()
1
2
3
4
5
6
7
8
9
10
11
12
13
public class FirstInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
}
  • 在springMVC配置文件中注入我们创建的拦截器类
    • 方式一和方式二都可实现拦截器的注入
    • /**:表示拦截所有请求,包括/user/add、/user
    • /*:表示拦截所有根请求下的请求,如/user、/add,不包括/user/add
1
2
3
4
5
6
7
8
<mvc:interceptors>
<mvc:interceptor>
<!--方式一,这里的bean名称就是我们配置的拦截器类-->
<ref bean="firstInterceptor"></ref>
<!--方式二-->
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
  • 或者直接在springMVC配置文件中配置拦截和放行的请求
1
2
3
4
5
6
7
8
9
10
11
12
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的请求-->
<mvc:mapping path="/**"/>

<!--放行的请求-->
<mvc:exclude-mapping path="/testRequestEntity"/>

<!--拦截器类还是要配置的-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>

11.3 拦截器方法

  • preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

  • postHandle:控制器方法执行之后执行postHandle()

  • afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()

11.4 拦截器执行顺序

  • 当我们创建了多个拦截器类,它们拦截了相同的请求,那么处理的顺序是怎么样的,需要我们了解下

11.4.1 每个拦截器的preHandle()都返回true

  • 我们在springMVC中配置拦截器的顺序就是多个拦截器执行的顺序
  • 拦截器中的preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行,如下:
  • 原理:在源码中,SpringMVC为每个拦截器提供了一个索引InterceptorIndex
    • 当拦截器拦截请求时,是顺序遍历拦截器并执行preHandle()方法,InterceptorIndex遍历一遍始终保存上一个拦截器的索引值,本质上是顺序遍历的自增的i值
    • 当执行postHandle()和AfterCompletion()时,则根据InterceptorIndex的值进行反向遍历(从大到小),因此postHandle()和AfterCompletion的执行顺序是反向的

11.4.2 某个拦截器的preHandle()返回了false

  • preHandle()返回false的拦截器之前的拦截器的preHandle()和afterComplation()都会执行,但postHandle()不会执行
  • 原理:假设有5个拦截器,第3个拦截器prehandle()返回false对请求进行拦截
    • 顺序执行到第3个拦截器的preHandle()时返回false,SpringMVC经过判断后,则直接根据InterceptorIndex去处理第3个拦截器之前的拦截器的AfterCompletion()方法,而不去执行postHandle()方法,并直接return,并不执行视图渲染

十一、异常处理

11.1 xml配置文件配置

  • 在springMVC配置文件中进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
error为指定跳转的视图名称,即出现ArithmeticException异常就会跳转到error页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域(默认)中进行共享,在页面输出th:text="${ex}"的信息时,就会在页面打印出异常信息
-->
<property name="exceptionAttribute" value="ex"></property>
</bean>

11.2 注解配置异常

  • 异常控制器,写在controller层中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//@ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {

//@ExceptionHandler用于设置所标识方法处理的异常
@ExceptionHandler(ArithmeticException.class)
//ex表示当前请求处理中出现的异常对象
public String handleArithmeticException(Exception ex, Model model){
// model中添加错误信息,方便前端显示
model.addAttribute("ex", ex);
return "error";
}

}

十二、注解配置

12.1 创建初始化类,代替web.xml

  • 创建两个配置类
    • SpringConfig
    • WebConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

/**
* 指定spring的配置类,需要创建SpringConfig类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

/**
* 指定SpringMVC的配置类,需要创建WebConfig类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

/**
* 指定DispatcherServlet的映射规则,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

/**
* 添加过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceRequestEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}
}

12.2 创建SpringConfig配置类,代替Spring配置文件

1
2
3
4
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}

12.3 创建WebConfig配置类,代替SpringMVC的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Configuration
//扫描组件
@ComponentScan("com.atguigu.mvc.controller")
//开启MVC注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

//使用默认的servlet处理静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

//配置文件上传解析器
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}

//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirstInterceptor firstInterceptor = new FirstInterceptor();
registry.addInterceptor(firstInterceptor).addPathPatterns("/**");
}

//配置视图控制

/*@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}*/

//配置异常映射
/*@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}*/

//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}

//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}


}

十三、SpringMVC执行流程

一、系统分区

1.1 分区类型

1.1.1 主分区

  • 最多只能有4个

1.1.2 扩展分区

  • 最多只能有1个
  • 主分区加扩展分区最多有4个
  • 不能写入数据,只能包含逻辑分区

1.1.3 逻辑分区

  • 可以写入数据

1.2 格式化

  • 又称逻辑格式化
  • 目的是在硬盘中写入文件系统
  • 文件系统有:FAT16、FAT32、NTFS、EXT2、EXT3、EXT4(默认)等

1.3 硬件设备名

硬件 设备文件名
IDE硬盘 /dev/hd[a-d]
SCSI/SATA/USB硬盘 /dev/sd[a-p]
光驱 /dev/cdrom或/dev/sr0
软盘 /dev/fd[0-1]
打印机(25针) /dev/lp[0-2]
打印机(USB) /dev/usb/lp[0-15]
鼠标 /dev/mouse

1.4 分区表示

  • 不管如何分区,1、2、3、4都用于主分区,第一个逻辑分区都是从5开始

1.5 挂载

1.5.1 必备分区

  • / (根分区)
  • swap分区(交换分区,内存2倍,不超过2GB)

1.5.2 推荐分区

  • /boot(启动分区,200MB,默认是sda1)

1.6 文件系统结构

  • Linux目录可以放在不同的系统分区中

1.7 小结

  • 分区:把大硬盘分为小的逻辑分区
  • 格式化:写入文件系统
  • 分区设备文件名:给每个分区定义设备文件名
  • 挂载:给每个分区分配挂载点(挂载点必须是空目录,挂载后分区才能正常使用)

二、Linux命令

2.1 文件目录

目录名称 含义
. 代表当前目录
.. 代表上一级目录
- 代表前一个工作目录
~ 代表[目前用户身份]所在的家目录

2.2 文件处理命令

2.2.1 命令列表

  • 以下命令执行权限为所有用户
命令 选项 解释
ls -ald [文件/目录] a:显示目录中全部文件(包括隐藏文件)
-l:详细信息显示
-d:查看目录属性
-i:查看i节点
显示目录文件信息
所在位置/bin/mkdir
mkdir -p -p:递归创建
创建新目录
cd
pwd 显示当前目录
所在位置/bin/pwd
rmdir 删除空目录
cp -rp [原文件/目录] [目标目录] -r:表示复制目录
-p:表示保留文件属性
复制文件或目录
所在位置/bin/cp
mv [原文件或目录] [目标目录] 剪切文件、改名
所在位置/bin/mv
rm -rf -r:表示删除目录
-f:表示强制执行
touch [文件名] 创建空文件
所在位置/bin/touch,文件名不推荐加空格
cat -n [文件名] -n:表示显示行号
显示文件内容
tac 反向显示文件内容
more 按f/空格翻页
按Enter换行
按q退出
分页显示文件内容
所在位置/bin/more
less 按PgUp向上翻页
/+搜索名称进行内容搜索(继续按n可以向下查找)
分页显示文件内容(可以向上翻页)
所在位置/usr/bin/less
head -n [文件名] -n:指定行数
显示文件前几行,默认显示前世行
所在位置/uer/bin/head
tail -nf -n:指定行数
-f:表示动态显示文件末尾内容
显示文件后几行,默认显示后10行
所在位置/usr/bin/tail
ln -s [原文件] [目标文件]
生成链接文件
所在位置/bin/ln

2.2.2 示例

  • ls -ld
  • mkdir -p /tmp/a/b
  • cd ..(返回上一目录)/cd [绝对路径/相对路径]
  • pwd
  • rmdir /tmp/a/b
  • cp -r /tmp/a/b /root(将b目录复制到root目录下)
  • mv /tmp/a/b /root/c(将b剪切到root目录并改名为c)
  • touch a.list
  • cat /etc/issue
  • more /etc/services
  • less /etc/services
  • head -n 20 /etc/services
  • tail -n 18 /etc/services
  • ln -s /etc/issue /tmp/issue.soft

2.3 权限管理命令

2.3.1 命令列表

  • 以下命令执行权限为所有用户
命令 选项 解释
chmod [{ugoa}{+-=}{rwx}] [文件或目录]或[mode=421] [文件或目录]/-R -R:表示递归修改某一目录下全部文件的权限
改变文件或目录权限
u表示所有者,r=4,w=2,x=1
所在位置/bin/chmod
chown [用户] [文件或目录] 改变文件或目录的所有者
所在位置/bin/chown
chgrp [用户组] [文件或目录] 改变文件或目录的所属组
所在位置/bin/chgrp
umask [-S] -S:表示以rwx形式显示新建文件缺省权限
显示、设置文件的缺省权限
所在位置Shell内置命令

2.3.2 示例

  • chmod u+x,o-r,g=rwx a.list
  • chmod 640 a.list
  • chown gaoy a (改变文件a的所有者为gaoy)
  • chown root:tsgroup /project/(改变project目录的所有者和所属组)
  • chgrp a b.list
  • umask -S

2.4 文件搜索命令

2.4.1 命令列表

  • 以下命令执行权限为所有用户
命令 选项 解释
find [搜索范围] [匹配条件] -name:根据文件名查找
-iname:不区分大小写查找
- size:根据文件大小查找
-user:根据所有者查找
文件搜索
所在位置/bin/find
locate [文件名] -i:不区分大小写查找
在文件资料库中查找文件,速度比find快
所在位置/usr/bin/locate
which [命令名称] 搜索命令所在目录及别名信息
所在位置/usr/bin/which
whereis [命令名称] 搜索命令所在目录及帮助文档路径
grep -inv [关键字] [文件名] 在文件中搜寻字串匹配的行并输出
所在位置/bin/grep

2.4.2 示例

  • find /etc -name init(在目录/etc中查找文件init)
  • locate inittab
  • which ls
  • whereis ls
  • grep mysql /root/install.log

2.5 帮助命令

2.5.1 命令列表

  • 以下命令执行权限为所有用户
命令 选项 解释
man [命令或配置文件] 获取帮助信息
按q退出
按空格翻页
按Enter换行
按/-[查找内容]进行查找
按n向下查找
所在位置/usr/bin/man
whatis [命令] 简单显示命令帮助信息
apropos [配置文件] 查看配置文件的信息
—help [命令] —help 查看命令常见选项
help [命令] 获取Shell内置命令的版帮助信息
所在位置Shell内置命令

2.5.2 示例

  • man ls
  • man services:查看配置文件services的帮助信息
  • whatis ls
  • apropos inittab
  • ls —help
  • help umask

2.6 用户管理命令

2.6.1 命令列表

  • useradd/userdel执行权限为root
  • 其他命令执行权限为所有用户
命令 选项 解释
useradd -udcgGs -u [UID]:手工指定用户的UID
-d [家目录]:手工指定用户的家目录
-c [用户说明]:手工指定用户的说明
-g [组名]:手工指定用户的初始组
-G [组名]:手工指定用户的附加组
-s shell:手工指定用户的登录shell,默认是/bin/bash
所在位置:/usr/sbin/useradd;
添加用户,不推荐手工指定用户的家目录
passwd -Slu [用户名]—stdin -S:查询用户密码的密码状态
-l:暂时锁定用户
-u:解锁用户
—stdin:可以通过管道符输出的数据作为用户的密码
-Slu:这些选项只能由root执行
设置用户密码,root可以更改任何人的密码,普通用户可以更改自己的密码
所在位置:/usr/bin/passwd;
只用root可以改其他用户的密码,其他用户只能直接输入passwd(不用接用户名)改自己的密码(必须符合密码复杂性原则)
usermod -ucGLU -u [UID]:修改用户的UID号
-c [用户说明]:修改用户的说明信息
-G [组名]:修改用户的附加组-L-U;
-L:临时锁定用户
-U:解锁用户锁定
chage -l:列出用户的详细密码
-d [日期]:修改密码最后一次更改日期
-m [天数]:两次密码修改间隔
-M [天数]:密码有效期
-W [天数]:密码过期前警告天数
-I [天数]:密码过后宽限天数
-E [日期]:账号失效时间
userdel -r [用户名] -r:删除用户的同时删除用户家目录
id [用户名] 查看用户ID(UID、GID等)
su [选项] [用户名] -
-c
-:选项只是用“-”代表连带用户的环境变量一起切换
-c:仅执行一次命令,而不切换用户身份
groupadd -g [GID] [组名] -g [GID]:指定组ID
groupmod [选项] [组名] -g [GID]-n [新组名] -g:修改GID
-n:修改组名
groupdel [组名] 没有初始组,此用户不能存在
gpasswd -ad [用户名] [组名] -a:把用户加入组
-d:把用户从组中删除;
把用户添加入组或从组中删除
who 查看登录用户信息
所在位置:/usr/bin/who
w 查看登录用户详细信息
所在位置:/usr/bin/w

2.6.2 示例

  • useradd yangmi
  • echo “123” | passwd —stdin lamp:给用户lamp添加密码123
  • passwd -l sc:锁定用户(该用户无法登录)
  • usermod -c “test user” lamp
  • chage -d 0 lamp:用户一登录就要修改密码
  • su root:这样切换不会改变此用户的环境变量
  • su - root:把用户的操作环境也改为root环境(推荐)
  • su - root -c “useradd user3”:不切换成root,但执行useradd命令添加user3用户
  • groupmod -n testgrp group1:把组名group1修改为testgrp
  • gpasswd -a user1 root:把user1用户加入到root组中
  • who
  • w

2.7 压缩解压命令

2.7.1 命令列表

  • 以下命令执行权限为所有用户
命令 选项 解释
gzip [文件] 压缩文件(目录不可以),压缩后文件格式为.gz,压缩后只保留压缩后的文件,不保留原文件所在位置/bin/gzip
gunzip [压缩文件] 解压缩.gz的压缩文件
tar -zcfvx [压缩后的文件名] [目录] -c:打包
-v:显示详细信息
-f:指定文件名
-z:打包同时压缩
-x:解包
打包目录,即把目录打包成一个.tar的文件,压缩后的文件格式为.tar.gz所在位置:/bin/tar
zip -r [压缩后文件名] [文件或目录] -r:压缩目录
压缩文件或目录,压缩后文件格式为.zip
所在位置/usr/bin/zip
unzip [压缩文件] 解压.zip的压缩文件
所在位置/usr/bin/unzip
bzip2 -k [文件] 压缩文件,压缩后文件格式.bz2
bunzip2 -k [压缩文件] 解压缩

2.7.2 示例

  • gunzip a.gz
  • tar -zcf Japan.tar.gz Japan
  • tar -axvf Japan.tar.gz(解包)
  • zip -r Japan.zip Japan
  • unzip test.zip
  • bzip2 -k boduo
  • tar -cjf Japan.tar.bz2 Japan(针对目录)
  • bunzip2 -k boduo.bz2
  • tar -xjf Japan.tar.bz2(针对目录)

2.8 网络命令

2.8.1 命令列表

  • 以下命令执行权限为所有用户
  • ifconfig执行权限为root,其他均为所有用户
命令 选项 解释
write <用户名> 给登录用户发信息
以Ctrl+D保存结束
所在位置:/usr/bin/write
wall [message] 发广播信息
所在位置:/usr/bin/wall
ping -c [IP地址] 测试网络连通性
所在位置:/bin/ping
ifconfig [网卡名称] [IP地址] 查看和设置网卡信息
所在位置:/sbin/ifconfig
mail [用户名] 查看发送电子邮件
所在位置/bin/mail
last 列出目前与过去登入系统的用户信息
所在位置:/usr/bin/last
lastlog -u [uid] 检查某特定用户上次登录的时间
所在位置/usr/bin/lastlog
traceroute 显示数据包到主机间的路径
所在位置:/bin/traceroute
netstat -tulrn 显示网络相关信息
所在位置:/bin/netstat
setup 配置网络
所在位置/usr/bin/setup
mount [-t 文件系统] [设备文件名] [挂载点]

2.8.2 示例

  • write linzhiling
  • wall abcdefgh
  • ping -c 3 192.168.1.156
  • ifconfig eth0 192.168.8.250
  • mail root
  • mail
  • last
  • lastlog
  • lastlog -u 502
  • traceroute www.baidu.com
  • netstat -tlun(查看本机监听的端口)
  • netstat -an(查看本机所有的网络连接)
  • netstat -rn(查看本机路由表)
  • setup
  • mount -t iso9660 /dev/sr0 /mnt/cdrom
  • umount /dev/sr0(卸载)

2.9 关机重启命令

2.9.1 命令列表

命令 选项 解释
shutdown -chr [时间] -c:取消前一个关机命令
-h:关机
-r:重启
reboot 重启
init 0 关机
init 6 重启

2.9.2 系统运行级别

(1)级别

级别 解释
0 关机
1 单用户
2 不完全多用户、不含NFS服务
3 完全多用户(默认)
4 未分配
5 图形界面
重启

(2)相关命令

命令 解释
runlevel 查看系统运行级别
logout 退出登录

2.10 Shell命令

2.10.1 命令列表

命令 选项 解释
echo -e [输出内容] -e:支持反斜线控制的字符转换;基本输出命令
history -cw [历史命令保存文件] -c:清空历史命令(不推荐清空)
-w:把缓存中的历史命令写入历史命令保护文件
~/.bash_history
查看之前输入的命令,不加w选项,刚输入的命令还在缓存中,还未写入历史命令保存文件中,默认保存1000条历史命令(可以在环境变量配置文件/etc/profile中进行修改)
alias [别名]=[‘原命令’] alias [别名]=[‘原命令’]:设定命令别名
alias:查询命令别名
临时生效,重启后无效

2.10.2 示例

  • history
  • history -w
  • alias vi=’vim’
  • alias

2.11 字符提取命令

2.11.1 cut命令

  • 选项:-df [文件名]
  • 解释:
    • -d 分隔符:按照指定分隔符分割列
    • -f 列号:提取第几列
  • 说明:默认提取以制表符分割的列

2.11.2 printf命令

  • ‘输出类型/输出格式’ 输出内容
  • 输出类型:
    • %ns:输出字符串。n是数字指代输出几个字符
    • %ni:输出整数。n是数字指代输出几个数字
    • %m.nf:输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数
  • 输出格式:

    • \a:输出警告音
    • \b:输出退格键
    • \f:清除屏幕
    • \n:换行
    • \r:回车
    • \t:水平输出退格键,即Tab键
    • \v:垂直输出退格键,即Tab键
  • 说明:利用管道符或者直接接文件名都无法利用此命令输出文件内容,print和printf一般在awk命令中使用

  • 示例
    • printf ‘%s %s %s \n’ 1 2 3 4 5 6
    • printf ‘%s’ $(cat s.txt):输出文件内容

2.11.3 awk命令

  • 选项:
    • ‘条件1 {动作1}
    • 条件2 {动作2}…’ 文件名
  • 条件:
    • 一般使用关系表达式作为条件
    • x>10
    • x>=10
    • x<=10
  • 动作:格式化输出流程控制语句
  • 说明:print只能在awk中使用,会自动换行
  • 示例:
    • awk ‘{printf $2 “\t” $6 “\n”}’ s.txt:输出文件第2列和第六列
    • df -h | awk ‘{print $1 “\t” $3}’
    • df -h | grep sda5 | awk ‘{print $5}’ | cut -d “%” -f 1:截取某一行的第5列数据
    • awk ‘BEGIN{printf “This is a txt \n”} {print $2 “\t” $5}’ s.txt:在输出文件内容前,先输出This is a txt
    • awk ‘BEGIN{FS=”:”} {print $1 “\t” $3}’ /etc/passwd:FS指定分隔符为冒号,不加BEGIN,文件的第一行就不会处理
    • awk ‘END{printf “The End \n”} {printf $2 “\t” $6 “\n”}’ s.txtEND:表示输出完成之后,再输出The End

2.11.4 sed命令

  • 选项:[-nei] ‘[动作]’ [文件名]
  • 解释:
    • -n:一般sed命令会把所有数据都输出到屏幕,如果加入此选项,则只会把经过sed命令处理的行输出到屏幕
    • -e:允许对输入数据应用多条sed命令编辑
    • -i:用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出
  • 动作:

    • a \:追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用“\”代表数据未完结
    • c \:行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用“\”代表数据未完结
    • i \:插入,在当前行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用“\”代表数据未完结
    • d:删除,删除指定的行
    • p:打印,输出指定的行
    • s:字串替换,用一个字符串替换另外一个字符串。
    • 格式为“行范围s/旧字串/新字串/g”(和vim中的替换格式类似)
  • 示例:

    • sed -n ‘2p’ s.txt:输出第二行,不加-n会把所有内容都输出
    • df -h | sed -n ‘2p’ s.txt:输出df命令内容第2行
    • sed ‘2,4d’ s.txt:将文件输出的内容的第2到4行删除,但不改变文件本身
    • sed ‘2i hello \world’ s.txt:在文件输出内容第2行插入hello world内容,并不改变文件本身
    • sed ‘4s/99/55/g’ s.txt:将文件输出内容的第4行的99改为55
    • sed -e ‘s/LiMing//g ; s/Gao//g/‘ s.txt:同时把LiMing和Gao修改为空白

三、网络配置

3.1 准备工作

  • 将虚拟机网络配置改为桥接模式,才能保证主机和虚拟机ping通

3.2 第一步

3.2.1 修改配置文件参数

  1. dhclient:动态分配IP地址
  2. vim /etc/sysconfig/network-scripts/ifcfg-ens33
  3. 修改静态IP:BOOTPROTO=static
  4. 修改自启动:ONBOOT=yes

3.3 第二步

3.3.1 向配置文件添加IP地址、子网掩码、网关、DNS

  • IPADDR=192.168.1.102
  • NETMASK=255.255.255.0
  • GATEWAY=192.168.1.1
  • DNS1=119.29.29.29
  • GATEWAY和IPADDR需要在同一网段下,否则无法上网

3.4 第三步

  • 重启网络服务:systemctl restart network.service

四、开启web服务

4.1 安装tomcat

4.2 安装nginx

4.3 开放端口

  • 在不关闭防火墙的前提下,开放浏览器访问端口
    • 开启tomcat:systemctl start tomcat.service
    • 永久开放8080端口:firewall-cmd —zone=public —add-port=8080/tcp —permanent
    • 重启防火墙:systemctl restart firewalld.service
    • 查看是否开放成功:firewall-cmd —list-ports
  • 也可以关闭防火墙对服务器进行访问:
    • 暂时关闭防火墙:systemctl stop firewalld
    • 永久关闭防火墙:systemctl disable firewalld

一、设计模式基础

1.1 GoF的23种设计模式及其功能

  1. 单例( Singleton )模式 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
  2. 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例
  3. 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生存什么产品
  4. 抽象工厂(Abstract Factory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品
  5. 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们
  6. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性
  7. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能在一起工作的那些类能一起工作
  8. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化,它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
  9. 装饰(Decorator)模式::动态的给对象增加一些职责,即增加其额外的功能
  10. 外观( Facade )模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问
  11. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用
  12. 组合( Composite )模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性
  13. 模板方法( Templat Method )模式:定义一个操作中的算法骨架 ,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤
  14. 策略( Strategy )模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户
  15. 命令( Command )模式:将一个请求封装为一个对象 ,使发出请求的责任和执行请求的责任分割开
  16. 职责链 (Chain of Responsibility )模式:把请求从链中的一个对象传到下一个对象 ,直到请求被响应为止。通过这种方式去除对象之间的耦合
  17. 状态( State )模式:允许一个对象在其内部状态发生改变时改变其行为能力
  18. 观察者( Observer 模式:多个对象间存在-对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为
  19. 中介者( Mediator )模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解
  20. 迭代器 Iterator )模式 提供一种方法来顺序访问聚合对象中的一系列数据 ,而不暴露聚合对象的内部表示
  21. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合种的每个元素提供多种访问方式,即每个元素有多个访问者对象访问
  22. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态以便以后恢复它
  23. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器

二、面向对象的设计原则

2.1 开闭原则OCP

  • 对扩展开放、对修改关闭
  • 通过“抽象约束、封装变化”来实现开闭原则
  • 抽象层设计的合理,可以基本保持软件架构的稳定
  • 当软件需求发生变化时,只需要根据需求派生一个实现类来扩展就可以了

2.2 里氏替换原则LSP

  • 子类可以扩展父类的功能,但不能改变父类原有的功能
  • 简单来说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法
  • 作用:实现开闭原则的重要方式之一、提高父类可复用性、降低类的扩展出错的可能性
  • 如果出现因重写父类方法而导致出错的情况,应该设计个更一般的类,重新组织类的继承关系

2.3 依赖倒置原则DIP

  • 要面向接口编程,不要面向实现编程
  • 抽象层相对稳定,实现类较为多变
  • 接口/抽象类的目的是制定好规范和契约,不去涉及任何具体的操作
  • 具体细节的任务交给实现类完成
  • 作用:降低了类间的耦合性、提高系统的稳定性、提高代码可读性和可维护性

2.3.1 实现方法

  1. 每个类尽量提供接口或抽象类,或者两者都具备
  2. 变量的声明类型尽量是接口或者是抽象类
  3. 任何类都不应该从具体类派生
  4. 使用继承时尽量遵循里氏替换原则

2.4 单一职责原则SRP

  • 一个类应该有且仅有一个引起它变化的原因,否则应该被拆分
  • 承担太多职责的缺点:削弱类实现其他职责的能力、冗余代码
  • 作用:降低类的复杂度、提高类的可读性、提高系统可维护性、变更引起的风险降低
  • 实现方法:分析、设计、重构
  • 同样适用于方法,方法应尽可能做好一件事情

2.5 接口隔离原则ISP

  • 程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法
  • 要为类建立它们需要的专用接口,而不要视图去建立一个很庞大的接口供所有依赖它的类去调用
  • 作用:提高系统灵活性和可维护性、降低系统耦合性、提高系统内聚性、接口粒度大小要定义合理(太大太小都不好)、减少代码冗余、能够体现对象的层次
    实现方法:一个接口只服务于一个子模块或业务逻辑、为依赖接口的类定制服务、深入了解环境/业务逻辑、使接口用最少的方法实现最多的事情

2.6 迪米特法则LKP

  • 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用
  • 作用:降低类间耦合度、提高模块相对独立性、提高类的可复用性和系统的扩展性
  • 过渡使用LKP会使系统产生大量的中介类,从而增加系统的复杂性,所以使用时要权衡好
  • 实现方法:依赖者应该依赖应该依赖的对象、被依赖者只暴露应该暴露的方法
  • 注意:应创建弱耦合的类、降低类成员的访问权限、优先考虑设计不变类、降低引用次数、不暴露属性成员(提供get和set)、进行使用序列化

2.7 合成复用原则CRP

  • 又称组合/聚合复用原则(CARP)
  • 它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
  • 如果要使用继承关系,则必须严格遵循里氏代换原则
  • 合成复用原则和里氏代换原则都是开闭原则的具体实现规范
  • 类的复用包括:继承复用、合成复用

2.7.1 继承复用

  • 破坏了类的封装性,父类暴露给子类
  • 子类与父类耦合度高
  • 限制了复用的灵活性,父类继承而来的实现是静态的,在编译时已经定义、所以在运行时不可能发生变化

2.7.2 合成复用

  • 维持了类的封装性
  • 新旧类之间的耦合度低
  • 复用的灵活性高,可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象

一、安装与配置

  1. 安装gcc:yum install gcc -y
  2. 下载redis-x.x.x.tar.gz,并解压:tar -zxvf redis-x.x.x.tar.gz
  3. 进入redis解压目录,执行命令make和make install,不报错就表示安装成功,默认安装位置在/usr/local/bin目录下
  4. 开启redis后台启动,将redis解压目录下的redis.conf文件复制到其他位置,比如/etc目录下
  5. 修改redis.conf文件,找到daemonize字段,将其后的值no改为yes后,保存退出
  6. 执行命令redis-server /etc/redis.conf,即可后台开启redis
  7. 使用redis客户端访问redis服务,执行命令redis-cli进入redis客户端

二、Redis语法

2.1 数据类型

  • 字符串String
  • 列表List
  • 集合Set
  • 哈希Hash
  • 有序集合Zset

2.2 常用命令

命令 解释
ping 查看服务是否正常运行
dbsize 查看当前数据库中key的数目(返回值为integer)
select 0 切换数据库,默认使用的是db0
Redis默认操作16个数据库,序号为0~15
flushdb 删除当前操作的数据库中的内容
flushall 删除所有的库中的数据
keys pattern 查找所有符合pattern的key,pattern可以使用统配符
exists [key…] 判断key是否存在,存在key返回1,不存在返回0;
使用多个key,则执行后返回key的数量
expire [key] [seconds] 设置key的生存时间,超过时间key自动删除;单位是秒;
设置成功返回1,其他情况是0
ttl [key] 以秒为单位,返回key的剩余生存时间;
-1:表示没有设置 key 的生存时间, key 永不过期;
-2 :表示key 不存在;其他表示剩余时间
type [key] 查看key所存储的数据类型
del [key…] 删除指定的key,不存在的key进行忽略;
返回值:数字,删除的key的数量

2.3 字符串String

  • 虽然给某个key设置的值是数字,但key的类型仍然是字符串,数字是以字符串的形式存储的,增加和减小操作也是基于字符串的操作
命令 解释
set [key] [value] 将字符串值设置到key中;设置成功返回OK
get [key] 获取数据库中key的值;
返回值:key对应的value值
incr [key] 将 key 中储存的数字值加 1
如果 key 不存在,则创建 key 并初始化其值为 0 再执行incr 操作(只能对数字类型的数据操作)
decr [key] 与incr作用相反,是减1操作
append [key] [value] 字符串追加;
如果 key 存在, 则将 value 追加到 key 原来旧值的末尾
如果 key 不存在, 则将 key 设置值为 value;
返回值:追加后的字符串总长度
strlen [key] 返回 key 所储存的字符串值的长度
getrange [key] [start] [end] 获取 key 中字符串值从 start 开始 到 end 结束 的子字符串,包括 start 和 end
负数表示从字符串的末尾开始, -1 表示最后一个字符
setrange [key] [offset] [value] 用 value 覆盖(替换)key 的从offset位置开始存储的值
不存在的 key ,在offset前填充空白字符
返回值:修改后的字符串的长度
mset [key value…] 同时设置一个或多个 key-value
返回值: OK
mget [key …] 获取所有(一个或多个)给定 key 的值
包含所有 key 的列表

2.4 列表List

2.4.1 原理

  • 单键多值
  • 底层:双向链表

2.4.2 命令

命令 解释
lpush/rpush key [value…] 从左/右侧将一个或多个值 value 插入到列表 key 的表头
从左侧插入属于头插法,列表顺序为倒序
返回值:数字,新列表的长度
lpop/rpop
rpoplpush
lrange [key] [start] [stop] 获取列表 key 中指定区间内的元素,0表示第一个元素,-1为最后一个元素
返回值:指定区间的列表
lindex [key] [index] 获取列表 key 中下标为指定 index 的元素
返回值:指定下标的元素;index 不在列表范围,返回 nil
llen [key] 获取列表 key 的长度
返回值:数值,列表的长度; key 不存在返回 0
lrem [key] [count] [value] 移除列表中count个与value相等的元素
count>0从列表左侧开始移除;
count<0从列表右侧开始移除;
count=0移除表中所有与 value 相等的值
lset [key] [index] [value] 将列表 key 下标为 index 的元素的值设置为 value
返回值:设置成功返回 ok ; key 不存在或者 index 超出范围返回错误信息

2.5 集合Set

2.5.1 原理

  • 底层:hash表

2.5.2 命令

命令 解释
sadd [key] [member…] 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略,不会再加入。
返回值:加入到集合的新元素的个数。不包括被忽略的元素
smembers [key] 获取集合 key 中的所有成员元素,不存在的 key 视为空集合
sismember [key] [member] 判断 member 元素是否是集合 key 的成员返回值:member 是集合成员返回 1,其他返回 0
scard [key] 获取集合里面的元素个数
返回值:数字,key 的元素个数。 其他情况返回 0
srem [key] [member…] 删除集合 key 中的一个或多个 member 元素,不存在的元素被忽略
返回值:数字,成功删除的元素个数,不包括被忽略的元素。

2.6 有序集合Zset

2.6.1 原理

2.6.2 命令

命令 解释
zadd [key] [[score] member…] 将一个或多个 member 元素及其 score 值加入到有序集合 key 中,如果 member存在集合中,则更新值;score 可以是整数或浮点数
返回值:数字,新添加的元素个数
zrange [key] [start] [stop] [WITHSCORES] 查询有序集合,指定区间的内的元素。集合成员按 score 值从小到大来排序
WITHSCORES 选项让 score 和 value 一同返回
zrem [key] [member…] 删除有序集合 key 中的一个或多个成员,不存在的成员被忽略
返回值:被成功删除的成员数量,不包括被忽略的成员。
zcard [key] 获取有序集 key 的元素成员的个数
返回值:key 存在返回集合元素的个数, key 不存在,返回 0

三、Redis配置文件

四、Redis发布和订阅

4.1 简介

  • 发布和订阅是一种信息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息
  • Redis客户端可以订阅任意数量的频道

4.2 示例

  • 打开两个redis客户端,分别为A、B,A为发送者,B为订阅者
  • A执行命令public channel1 hello,表示向channel1频道发布信息hello
    • 命令执行后的返回值表示订阅者的数量
  • B执行命令SUBSCRIBE channel1,表示接收channel1的频道
    • 返回的是频道的信息列表

五、其他数据类型

5.1 Bitmaps

5.1.1 原理

  • 存储二进制的字符串,比如user=00001

    • 其中value为00001在Redis中依旧是以字符串的形式存储的
    • 偏移量offset从0开始算起,这里value中1的偏移量就是4
    • 想要得到以上的KV对,需要执行命令setbit user 4 1
  • Bitmaps可以用来保存用户活跃度,Set集合也可以做,但两者有区别

    • 当用户活跃量特别大时,使用Bitmaps可以极大的减少存储空间
    • 当用户活跃量比较小时,使用Set集合则比较合适

5.1.2 命令

命令 解释
setbit [key] [offset] [value] 将key的offset位置的值设置为value
getbit [key] [offset] 获取key中offset位置的值
bitcount [key] [start,end] 不指定[start,end]时,统计key中value所有被设置为1的bit个数
指定[start,end]时,这里的start和end表示的是字节下标,[1,2]表示统计第一个字节(8位)到第二个字节中1的个数,[0,-2]则表示统计从0到倒数第二个字节中1个数

5.2 HyperLogLog

5.2.1 原理

  • 计算基数(集合中不重复元素个数)的一种算法
  • 对比Set,所占内存更小,统计的范围更大,重复的元素不会被添加进去

5.2.2 命令

命令 解释
pfadd [key] [element…] 添加指定元素到HyperLogLog中
返回值:添加重复元素返回0,添加不重复元素返回1
pfcount [key…] 统计某个key的基数个数
pfmerge [destkey] [sourcekey…] 合并多个sourcekey后,将结果存放到destkey中

5.3 Geospatial

5.3.1 原理

  • 存放地理信息的数据结构,即表示经纬度的二维坐标

5.3.2 命令

命令 解释
geoadd [key] [[longitude] [latitude] [menber]…] 添加地理位置信息,(经度,纬度,名称)
示例:geoadd china 100 100 shanghai 120 120 beijin
geopos [key] [member…] 获取key中名称位member的经纬度坐标
geodist [key] [member1] [member2] [m\ km\ ft\ mi] 获取key中member1和member2的直线距离,后面可以设置单位,m为默认单位
georadius [key] [longitude] [latitude] radius m\ km\ ft\ mi 以给定的经纬度坐标为中心,找出key中包含在半径为radius的圆中的member名称列表

六、Jedis

七、Springboot整合Redis

7.1 配置环境

7.1.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.6</version>
</dependency>

<!-- 提供池化操作 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>

7.1.2 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Redis服务器地址
spring.redis.host=192.168.47.33
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认位0)
spring.redis.database=0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(复数表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

7.1.3 Redis配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Configuration
@EnableCaching // 开启缓存支持
public class RedisConfig extends CachingConfigurerSupport {

@Resource
private LettuceConnectionFactory lettuceConnectionFactory;

/**
* @description 自定义的缓存key的生成策略 若想使用这个key
* 只需要讲注解上keyGenerator的值设置为keyGenerator即可</br>
* @return 自定义策略生成的key
*/
@Override
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getDeclaringClass().getName());
Arrays.stream(params).map(Object::toString).forEach(sb::append);
return sb.toString();
}
};
}

/**
* RedisTemplate配置
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
om.enableDefaultTyping(DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

/**
* 缓存配置管理器
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

// 以锁写入的方式创建RedisCacheWriter对象
//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
// 创建默认缓存配置对象
/* 默认配置,设置缓存有效期 1小时*/
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
/* 配置test的超时时间为120s*/
RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
.withInitialCacheConfigurations(singletonMap("test", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)).disableCachingNullValues()))
.transactionAware().build();
return cacheManager;
}

}

7.2 RedisTemplate

八、事务

8.1 概念

  • Redis事务作用是串联多个命令并顺序执行
  • 一个redis客户端可以通过multi命令进入事务状态,并且事务在执行时不会被其他redis客户端打断

8.2 命令

命令 说明 备注
multi 开启事务命令,之后的命令就进入队列,而不会马上被执行 在事务生存期间,所有的 Redis 关于数据结构的命令都会入队
watch [key…] 监听某些键,当被监听的键在事务执行前被修改,则事务会被回滚 使用乐观锁
unwatch [key…] 取消监听某些键
exec 执行事务,如果被监听的键没有被修改,则采用执行命令,否则就回滚命令 在执行事务队列存储的命令前, Redis 会检测被监听的键值对有没有发生变化,如果没有则执行命令 ,否则就回滚事务
discard 回滚事务 回滚进入队列的事务命令,之后就不能再用 exec命令提交了

8.3 悲观锁/乐观锁

8.3.1 区别

  • 乐观锁和悲观锁的出现都是解决共享数据的操作问题
  • 悲观锁:每次操作共享数据时,都会给数据上锁,防止其他事务对其进行操作
    • “悲观”意思是在每次操作数据前都悲观的认为别人也会操作这个数据,所以必须加上锁,保证数据为自己所用。但这样做的缺点是效率不高
  • 乐观锁:不会像悲观锁一样每次操作前都加锁,而是让每个事务都能对共享数据进行操作,哪个事务操作的快,则更新当前版本,这样过去版本的事务操作就会因为版本不匹配而无法执行

8.3.2 示例

  • watch
  • unwatch

8.3.3 事务特性

  • 隔离性:客户端事务在执行时不能被其他客户端命令打断,没有隔离级别概念
  • 原子性:不保证原子性,因为一条命令执行失败,其他命令仍然会执行,不会执行回滚

九、秒杀案例

9.1 并发模拟

9.1.1 http-tools

  • 安装httpd-tools:yum install httpd-tools
  • httpd-tools中包含有ab命令

9.1.2 ab命令

  • ab -n 1000 -c 100 -p ~/postfile -T ‘application/x-www-form-urlencoded’ 请求地址

    • -n:表示请求次数
    • -c:表示并发数
    • -T content-type:POST/PUT 数据所使用的Content-type头信息
    • -p postfile:包含要 POST 的数据的文件,记得还要设置 -T 参数
  • 命令返回结果

    • Concurrency Level: 并发量
    • Time taken for tests: 整个测试的时间
    • Complete requests: 完成的总请求数
    • Failed requests: 失败的请求数
    • Total transferred: 响应数据的总长度(包括http头信息和消息体数据)
    • HTML transferred: 响应数据中消息体数据的总和
    • Requests per second:吞吐率(计算方式为:Complete requests / Time taken for tests,也就是 完成的总请求数 / 整个测试的时间)
    • Time per request: 用户平均请求等待时间
    • Time per request: 服务器平均请求等待时间
    • Transfer rate: 单位时间内从服务器获取的数据长度(计算方式为:Total transferred / Time taken for tests, 也就是 响应数据的总长度(包括http头信息和消息体数据)/ 整个测试的时间)

9.1 案例源码

十、持久化操作

10.1 RDB(默认方式)

  • 读时共享,写时复制

10.2 AOF

十一、主从复制

十二、集群

十三、换成

十四、分布式锁

一、Mybatis

1.1 简介

  • 优秀的持久层框架

1.2 持久层

  • 持久化:将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失

二、第一个案例

2.1 导入依赖包与资源路径

2.1.1 导入依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--    mysql驱动    -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>

<!-- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!-- junit -->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

2.1.2 配置maven资源路径

  • 在pom文件中添加以下内容,防止项目运行时,代码层的xml文件由于不在默认的resource资源目录中,导致访问失败而报错,父/子模块的pom都可以添加进去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--  配置资源导出,防止dao层中的xml无法访问而报错  -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>

<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

2.2 配置Mybatis.xml文件

  • 在resource下创建Mybatis.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--
由于hexo头信息无法复制,因此以注释的形式标注出来
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
-->
<!--核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${com.mysql.cj.jdbc.Driver}"/>
<property name="url" value="${jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8}"/>
<property name="username" value="${root}"/>
<property name="password" value="${FLzxSQC1998.Com}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
</configuration>

2.3 编写代码

2.3.1 编写工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MybatisUtils {

// 提示作用域
private static SqlSessionFactory sqlSessionFactory;

static {
try {
// 使用Mybatis第一步:获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

}

// 有了工厂类SqlSessionFactory,就可以获取到SqlSession实例了
// SqlSession完全包含了面向数据库执行sql命令所需要的方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}

2.3.1 添加实体类

  • pojo层
1
2
3
4
5
public class User {
private Integer id;
private String name;
private String pwd;
}

2.3.2 编写接口

  • 无需编写实现类,Mybatis会帮我们实现
1
2
3
public interface UserDao {
List<User> getUserList();
}

2.4 Mapper配置文件

  • dao层下创建UserMapper.xml文件
  • 注意这个mapper文件需要在mybatis-config进行配置,否则程序运行会因为找不到mapper文件而报错
1
2
3
4
5
6
7
8
9
10
11
12
<!--
<?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">
-->
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.UserDao">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user;
</select>
</mapper>

2.5 获取结果

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserDaoTest {

@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

List<User> userList = userDao.getUserList();

sqlSession.close();

}
}

三、增删改查

3.1 namespace

  • dao层命名统一为xxxMapper.xxx

3.2 Mapper配置文件解析

  • namespace:表示dao层定义的接口路径
  • id:表示接口中的方法名
  • resultType:Sql语句执行的返回值类型
  • parameterType:传入的参数类型
1
2
3
4
5
6
7
8
9
10
11
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.UserMapper">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
</select>

<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id= #{id}
</select>
</mapper>

3.3 CRUD

3.3.1 编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<mapper namespace="com.kuang.dao.UserMapper">
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id= #{id};
</select>

<!--插入用户-->
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

<!--修改用户-->
<update id="updateUser" parameterType="com.kuang.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>

<!--删除用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>

</mapper>

3.3.2 编写接口方法

  • 在接口中定义对应方法
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserMapper {
// 根据id查找用户
User getUserById(int id);

// 插入用户
int addUser(User user);

// 修改用户
void updateUser(User user);

// 删除用户
void deleteUser(User user);
}

3.3.3 测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 查询测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

User user = userMapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}

// 插入测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.addUser(new User(4,"abc","abc"));
if(res > 0){
System.out.println("插入成功")
}

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

// 修改测试
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateUser(new User(3,"abc","abc"));

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

// 删除测试
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(1);

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

四、Map和模糊查询

4.1 Map实现CRUD

4.1.1 优势

  • 相对于原来的传pojo对象的方式,map方式更为传参更为灵活

4.1.2 编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
<mapper namespace="com.kuang.dao.UserMapper"> 
<!--
插入用户,使用map对象进行插入,注意parameterType="map",传递参数为map类型
values后的字段名可以自定义如userid等,map的键名必须跟这个自定的名字保持一致
这样就可以在map中取得对应的值,完成数据的插入
但表的列名字段必须和数据库表保持一致
-->
<insert id="addUser" parameterType="map">
insert into mybatis.user(id,name,pwd) values (#{userid},#{username},#{password});
</insert>

</mapper>

4.1.3 编写接口方法

1
2
3
4
public interface UserMapper {
// Map方式插入用户
int addUser(Map<String,Object> map);
}

4.1.4 测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Map<String,Object> map = new HashMap<String,Object>();
map.put("userid",5);
map.put("username","123");
map.put("password","123");
userMapper.addUser(map);

System.out.println(user);
sqlSession.close();
}

4.2 模糊查询

4.2.1 编写sql语句

1
2
3
4
5
6
7
8
9
10
<mapper namespace="com.kuang.dao.UserMapper"> 
<!--
写成"%"#{value}"%"形式是为了防止sql注入的风险,推荐,这样只需要传递匹配字符就可以了
如果写成#{value}就必须传参"%xxx%",或者其他匹配符,否则报错,但不推荐这么写
-->
<select id="addUser" parameterType="map">
select * from mybatis.user where name like "%"#{value}"%";
</select>

</mapper>

五、环境配置

5.1 核心配置文件内容

  • 配置标签的位置顺序依次往下,不能随便写在某个位置,否则会报错
  • properties(属性)
  • setting(设置)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • databaseldProvider(数据库厂商标识)
  • mappers(映射器)

5.2 properties(属性)

  • 新建properties配置文件
1
2
3
4
5
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=FLzxSQC1998.Com

  • 在核心配置文件中引入外部配置文件,如db.properties
    • 需要注意的是,这个properties标签需要写在configuration标签下的第一个位置,因为configuration中的标签有顺序要求,否则会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--也可以在properties中配置对应的属性,但默认会先从外部的配置文件开始读取-->
<!--内部配置属性
<properties resource="db.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
-->

<properties resource="db.properties"/>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--配置时,使用${}取到properties配置文件中对应的值-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

5.3 typeAliases(类型别名)

5.3.1 自定义别名

  • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
  • 设置完简化后的别命,在使用时可以直接使用,比如在sql语句配置文件的returnType就可以直接使用别名,使配置更加简洁清晰
  • 当这样配置时,别名可以用在任何使用的地方,这种方式一般在实体类较少的情况下使用
1
2
3
4
5
6
7
8
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

5.3.2 扫描包生成别名

  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
  • 这种可以在实体类比较多的时候使用
1
2
3
4
5
6
7
<!--
设置包后,会自动为对应的类设置别命,别名即为类名
这种情况,设置的别名为首字母小写的类名,比如domain.blog.Author 的别名为 author
-->
<typeAliases>
<package name="domain.blog"/>
</typeAliases>

5.3.3 注解别名

  • 还可以使用注解设置别名
  • 在使用扫描包生成别名时,如果非要改成自定义的,可以在对应类上加上注解
1
2
3
4
@Alias("author")
public class Author {
...
}

5.3.4 基本类型的别名

  • 基本类型前加下划线:如int别名:_int
  • 包装类型首字母小写:如Integer别名:int

5.4 setting(设置)

5.5 其他配置

5.6 mappers(映射器)

  • 在核心配置文件中配置mapper配置文件所在路径
  • 方式一:使用mapper文件绑定【推荐】
1
2
3
4
5
6
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  • 方式二:使用class绑定
1
2
3
4
5
6
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
  • 使用除方式一外的其他方式时的注意点:
    • 接口和他的Mapper配置文件必须同名
    • 接口和他的Mapper配置文件必须在同一包下

六、生命周期和作用域

6.1 SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了

6.2 SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 类似数据库连接池

6.3 SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 连接到连接池的一个请求
  • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中
  • 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它
1
2
3
4
5
6
7
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}finally{
session.close();
}


6.4 获取映射实例

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}

七、ResultMap结果集映射

7.1 简介

  • 解决实体类属性名和数据库表字段名不一致的问题

7.2 结果映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
结果映射标签
property对应了实体类属性,column对应表字段
-->
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>

<!--这里的返回类型使用resultMap标注,值为resultMap标签的id-->
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>

八、日志

8.1 日志工厂

  • 如果一个数据库操作出现了异常,我们最好是能看到操作的sql语句是啥,方便我们排错
  • 因此针对以上问题,我们可以使用日志的形式输出我们数据库操作的sql语句
  • Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
    • SLF4J
    • LOG4J【掌握】
    • LOG4J2
    • JDK_LOGGING
    • COMMONS_LOGGING
    • STDOUT_LOGGING【掌握】
    • NO_LOGGING

8.2 日志配置

  • 需要在核心配置文件中进行配置
  • 配置完后直接运行即可,就可以在控制台输出日志信息
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

8.3 Log4j

8.3.1 核心配置

1
2
3
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>

8.3.2 导入Log4j

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

8.3.3 log4j配置文件

  • 在resources下创建log4j.properties配置文件
  • 这里面可以配置许多日志的输出格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
### 设置###
log4j.rootLogger = debug,stdout,D,E

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

8.3.4 Log4j的使用

  • 直接运行就可以在控制台输出日志

  • 也可以编写语句

1
2
3
4
5
6
7
8
9
public class UserDaoTest {
// 生成logger对象
static Logger logger = Logger.getLogger(String.valueOf(UserDaoTest.class));

@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
}
}

九、分页实现

9.1 Limit分页

1
SELECT * FROM user LIMIT startIndex,pageSize;

9.2 Mybatis分页

9.2.1 定义接口方法

1
2
3
4
public interface UserMapper {
List<User> getUserByLimit(Map<String,Integer> map);
}

9.2.1 mapper配置文件

1
2
3
<select id="getUserByLimit" parameterType="map" resultType="com.kuang.pojo.User">
select * from mybatis.user limit #{startIndex},#{pageSize};
</select>

9.2.3 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Map<String,Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);

List<User> users = userMapper.getUserByLimit(map);
for (User user : users){
System.out.println(user);
}

sqlSession.close();
}

十、注解开发

  • 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心
  • 本质:反射机制
  • 底层:动态代理
1
2
3
4
5
public interface UserMapper{
@Select("select * from user")
List<User> getUsers();
}

十一、Mybatis执行流程

十二、Lombok

十三、复杂查询处理

13.1 数据库环境

  • 学生表:id、name、tid(外键)
  • 教师表:id、name
  • 一个教师教多个学生:一对多
  • 多个学习选同一个老师:多对一
  • 学生类
1
2
3
4
5
6
@Data
public class Student {
private Integer id;
private String name;
private Teacher teacher;
}
  • 教师类
1
2
3
4
5
6
@Data
public class Teacher {
private Integer id;
private String name;
private List<Student> students;
}

13.1 多对一处理

13.1.1 方式一:查询嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student;
</select>

<resultMap id="StudentTeacher" type="com.kuang.pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="com.kuang.pojo.Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="com.kuang.pojo.Teacher">
select * from teacher where id = #{tid};
</select>
</mapper>

13.1.2 方式二:结果嵌套

  • 连表查询,不想方式二分成两个查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id;
</select>

<!--这里的column应该是上面select语句定义的别名-->
<resultMap id="StudentTeacher" type="com.kuang.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>

<association property="teacher" javaType="com.kuang.pojo.Teacher">
<result property="name" column="tname"/>
</association>

</resultMap>
</mapper>

13.1.3 结果

  • 以上两种方式的结果输出相同

13.2 一对多处理

13.2.1 方式一:查询嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid};
</select>
<resultMap id="TeacherStudent" type="com.kuang.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>

<collection property="students" ofType="com.kuang.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>

</resultMap>
</mapper>

13.2.2 方式二:结果嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select * from teacher where id = #{tid};
</select>

<resultMap id="TeacherStudent" type="com.kuang.pojo.Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudents"/>
</resultMap>

<select id="getStudents" resultType="com.kuang.pojo.Student">
select * from student where tid = #{id};
</select>
</mapper>

13.2.3 结果

  • 以上两种方式结果相同

13.3 小结

  • 多对一:association(关联)
  • 一对多:collection(集合),和上面都是在returnMap结果映射中使用
  • javaType:指定实体类中属性的类型
  • ofType:指定映射到List或集合中的pojo类型,泛型中约束的类型(一般在collection中会使用到)
  • 在没有为pojo类起别名时,returnType直接使用类名会报错ClassNotFound,此时必须包的全类名
  • colume属性对应的是select语句指定的导出表的列名,如果有列的别名,就写别名
  • 以上两种方式都可实现效果,视情况而定实现方式

十四、动态Sql

14.1 简介

  • 对sql语句添加条件判断,根据条件判断生成不同的sql语句

14.2 动态sql标签

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
  • sql

14.3 if

  • if表示如果查询的map中存在这个字段,就拼接if中的语句,否则不进行拼接
1
2
3
4
5
6
7
8
<select id="findActiveBlogWithTitleLike" parameterType="map"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
  • 这种语句会存在一种缺陷,它只是机械的语句拼接

  • 下面这种情况就可能拼接出:SELECT FROM BLOG WHERE AND title like #{title}或者SELECT FROM BLOG WHERE

    • 这就造成了sql语句的语法错误,无法完成查询,需要用到下面的where标签
    • where标签可以自动的去除一些字段,比如多余的AND或者符号“,”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>

14.4 choose

  • choose类似于java中的switch,选择语句,满足其中一条则拼接对应的语句
  • 如果都不满足则拼接otherwise中的语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

14.5 trim(where、set)

14.5.1 where

  • 将所有匹配条件放入到where标签中
  • 解决了if标签的机械的语句拼接问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>

14.5.2 set

  • 完成update的操作标签
  • 也是用if进行条件判断拼接sql语句
1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

14.5.3 trim

  • trim可以完成一些自定义的操作
  • 比如自定where和set标签,意思就是trim可以替换where和set标签
1
2
3
4
5
6
7
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

<trim prefix="SET" suffixOverrides=",">
...
</trim>

14.6 foreach

  • 这里是从POST表中查询需要的多条数据
  • 这里的collection指定的是我们传入的列表,这里返回的就是列表中指定的id对应的数据
  • 加入说list中的id列表为(1,2,3),那么就将满足这些id的数据全部获取到:(id=1,name=…)、(id=2,name=…)、(id=3,name=…)
1
2
3
4
5
6
7
8
9
10
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>

14.7 sql

  • 用于封装动态sql语句
  • 使用include标签引入外部sql标签的id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<sql id="if-title-author">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</sql>

<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<include refid="if-title-author"/>
</where>
</select>

十五、缓存

15.1 缓存介绍

  • 多次重复查询会消耗服务器连接数据库的资源
  • 缓存:存放一次查询的结果,下次查询就不用查数据库了
  • 使用缓存的情况:经常查询且不经常改变的数据

15.2 一级缓存

15.2.1 简介

  • 一级缓存是默认开启的
  • 只在一次SqlSession中有效,也就是SqlSession打开到关闭之间,进行查询就会存在缓存,下次查询相同数据就不会再执行一次sql语句,直接从缓存中抽取数据

15.2.2 缓存失效的情况

  • 查询不同的内容
  • 增删改操作会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存

15.3 二级缓存

15.3.1 简介

  • 二级缓存也叫全局缓存,这是由于一级缓存的作用域太低,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没有了,现在需要的是,会话关闭时,一级缓存的数据可以保存到二级缓存中
    • 新的会话查询,就可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

15.3.2 开启二级缓存

  • 在核心配置文件添加以下标签,开启全局缓存
1
2
3
4
<settings>
<!--显示开启全局缓存,默认是开启的-->
<setting name="cacheEnabled" value="true"></setting>
</settings>
  • 配置完后还需要再需要使用二级缓存的Mapper配置文件下添加cache标签
    • eviction为缓存策略
    • flushInterval为缓存刷新时间
    • size为最大数引用数
    • readOnly为是否只读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mapper namespace="xxx">
<!--在当前mapper下开启二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--也可以直接使用cache标签-->
<cache/>

<select useCache="true">
...
</select>

</mapper>
  • 使用cache标签,不添加缓存策略时,需要将实体类实现序列化,如下
1
2
3
public class User implements Serializable{
...
}

15.3.3 小结

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中
    • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中

15.4 缓存原理

  • 每个SqlSession从开启到关闭都会启动一个一级缓存
  • SqlSession关闭后一级缓存会升级为二级缓存
  • 用户进行查询时会先看二级缓存是否存在查询的内容,再去一级缓存,缓存中没有要查找的对象时,就执行查询数据库的操作

15.5 自定义缓存

15.5.1 ehcache

  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

15.5.2 引入依赖

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

15.5.3 cache配置

1
2
3
<mapper namespace="xxx">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>

15.5.4 ehcache配置文件

十六、多表查询

一、数据准备

1.1 概述

  • id最好设置为数值型,方便后面的查找工作,需要通过id值查找其在staticNodes中的下标值
1
2
3
4
5
6
7
8
9
10
11
12
13
// 设置点和边的数据
var staticNodes = [
{id:1, label:"高玥", group:1, pid:0, subids:[2,3]},
{id:2, label:"篮球", group:2, pid:1, subids:[4]},
{id:3, label:"羽毛球", group:2, pid:1},
{id:4, label:"技巧", group:3,pid:2},
]

var staticEdges = [
{id:1,label:"兴趣", from: 1,to: 2},
{id:2,label:"兴趣", from: 1,to: 3},
{id:3,label:"有", from: 2,to: 4},
]

1.2 数据格式

1.2.1 格式要求

  • 一般vis要显示network,需要以下格式
    • node:id、label、group
    • edge:id、label、from、to
  • 为了实现节点展开收缩就需要在nodes中再添加两个字段:
    • pid:父节点id
    • subids:子节点id列表

1.2.2 后端实现方案(首页展示):

  • 需要先给数据分层,添加level标签,表示层级关系,只需要标注第一层即可,因为其他层会因点击事件而进行展开
  • nodes节点中的pid和subids需要进行判断,再进行添加
    • 设置pid:根据关系进行判断(根据to,查找from),对于单向关系,比如(我,有,篮球),那么篮球的pid就设置为“我”的id值,注意:目前只考虑节点的父节点只有一个的情况,如果pid有多个就需要设置列表
    • 设置subids:根据关系进行判断(根据from,查找to),如存在一组关系(我,有,篮球)、(我,年龄,18),那么“我”的subids即表示为“篮球”和“18”对应id组成的列表
    • 设置group:group是为了对节点进行区分
      • 前端实现:在进行节点展开时,会为nodes添加节点数据,这里就可以为节点添加上相同group,可以取一个随机整数
      • 后端实现:对于关系中from标签相同的一组关系,为其to标签对应的节点添加上相同group值
  • 为vis注入数据时,先注入第一层的数据,即先显示第一层的节点关系

二、vis.js绘制network

2.1 将数据注入到vis中

1
2
var nodes = new vis.DataSet(staticNodes);
var edges = new vis.DataSet(staticEdges);

2.2 配置network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 创建一个图的容器
var container = document.getElementById('mynetwork');

var data = {
nodes: nodes,
edges: edges
};

var options = {
nodes:{
shape: "dot",
// scaling: {
// customScalingFunction: function(min, max, total, value){
// return value / total;
// },
// min:5,
// max:150,
// },
},
edges: {
width: 0.15,
//physics: false,
smooth: {
type: "continuous",
},
},

interaction: {
hover: true,
//hoverConnectedEdges: true
//hideEdgesOnDrag: true,
},

physics: {
//enabled: false,
repulsion:{
nodeDistance: 1,
springLength: 1,
springConstant: 0.5,
centralGravity: 0.9
}
},

layout:{
//hierarchical: {direction: 'UD', sortMethod: 'hubsize'}
}
};

// 图可视化
// initialize your network!
var network = new vis.Network(container, data, options);

2.3 为节点创建点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 点击事件获取的是节点的列表,一般列表中只有一个节点id
network.on('click',function(params){
// 点击的是节点的情况(不考虑点击边的情况)
if (params.nodes.length != 0) {
var clickNodeId = params.nodes[0];
//console.log(findArrayIndexById(staticNodes,clickNodeId));
//removeSubNodes(clickNodeId);
//removeByRecur(clickNodeId)
//console.log(nodes.get(1));

// 逻辑分离,如果点击的节点存在子节点,则递归收缩节点
// 否则进行节点展开
if(getSubNodes(clickNodeId).length!=0)
removeNodes(clickNodeId);
else
addNodes(clickNodeId);

}
});

三、展开收缩逻辑

  • 具体的逻辑实现如下:
    • 第一步:getSubNodes函数获取到点击节点的下级节点id列表subNodes
    • 第二步:判断subNodes是否为空,如果不为空执行第三步,否则执行第四步
    • 第三步:如果不为空:遍历subNodes中的id值,在staticNodes中找到其对应的节点并在nodes/edges中进行递归remove(收缩操作)
    • 第四步:如果为空:判断点击的节点是否存在下级节点列表subids,如果存在,执行第五步(展开操作)
    • 第五步:遍历subids下级id列表,在staticNodes中查找到对应的节点,添加到nodes和edges中,实现节点的展开操作
  • 收缩时,如果点击节点的下一级节点中包含下下一级节点,则进行递归查找并收缩,实现方法为removeNodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 根据id值查找其对应的下标
function findArrayIndexById(arr, id){
var res;
$.each(arr, (index,item)=>{
if(item['id'] == id){
res = index;
}
});
return res;
}

// 节点收缩
function removeNodes(clickNodeId){
// 获取到的是下级节点列表
var subNodes = getSubNodes(clickNodeId);

//如果获取的下级节点列表长度 不为 0,表示节点没有收缩,则点击后进行收缩
if(subNodes.length != 0){
$.each(subNodes,(index,item)=>{
//消除的是注入到vis中的数据,而不是staticNodes/Edges
removeNodes(item);
nodes.remove({id:item});
edges.remove({from:clickNodeId,to:item});

});
}
}

//节点展开
function addNodes(clickNodeId){
$.each(staticNodes[findArrayIndexById(staticNodes,clickNodeId)]['subids'],(index,item)=>{
nodes.add(staticNodes[findArrayIndexById(staticNodes,item)]);
edges.add({from: clickNodeId,to: item});
});
}

// 获取点击节点的下级节点列表,不包含与其相连的父节点id
function getSubNodes(clickNodeId){
// 获取与该节点所连接的所有节点的id(只要是相连的都会被算进来)
var returnNodes = [];
var connectedNodes = network.getConnectedNodes(clickNodeId);//获取所有连接节点

// 对节点id列表进行遍历,index表示列表下标,item表示对应的id值
$.each(connectedNodes, (index,item)=>{
//这里的clickNodeId和staticNodes数组中的下标只是简单的-1操作
//因为staticNodes中id都是顺序的,如果不是顺序的就需要遍历才能找到其下标值
//clickNodeId是点击的那个节点的id,item是连接节点的id
//如果这个连接的节点是点击的节点父节点,则不考虑进去!!!
if(item != staticNodes[findArrayIndexById(staticNodes,clickNodeId)]['pid']){
returnNodes.push(item);
}
});
return returnNodes;
}

四、实现效果

一、MySql的语法规范

  • 不区分大小写,但建议关键字大写,表名、列名小写
  • 每条命令最好用分号结尾

二、注释

  • #注释文字(单行注释)
  • — 注释文字(单行注释)
  • /* 注释文字 */(多行注释)

三、查询

1
2
3
4
5
6
7
# 分组查询推荐尽可能使用分组前筛选,即在group by前使用where
SELECT [字段名,...]
FROM [表名,...]
WHERE [条件]
GROUP BY [字段名]
HAVING [条件]
ORDER BY ASC/DESC

3.1 连接查询

3.1.1 内连接

1
2
3
4
5
6
7
8
9
10
11
12
/*
这属于sql99语法,作用等同于不加inner join时的查询
不加inner join属于隐式连接,用inner join时,inner可以省略
等值连接在WHERE子句中判断中写
*/
SELECT [字段名,...]
FROM1 别名1
inner join2 别名2 on 连接条件
WHERE [条件]
GROUP BY [字段名]
HAVING [条件]
ORDER BY ASC/DESC

3.1.2 外连接

1
2
3
4
5
6
7
SELECT  [字段名,...]
FROM1 别名1
left/right/full join2 别名2 on 连接条件
WHERE [条件]
GROUP BY [字段名]
HAVING [条件]
ORDER BY ASC/DESC

3.1.3 内连接与外连接的区别

内连接:内连接匹配的是两个表符合连接条件的交集,不存在匹配的内容为NULL的情况

外连接:外连接分为左外连接与右外连接,左连接表示左表中记录全部查询出来,右表只显示符合条件的数据,没有匹配到右表的数据,则右表数据显示为NULL;右连接则反之。以以下示例做下说明:

  • 以下是a表
a_id a_name
1 路飞
2 索隆
3 香吉士
  • 以下是b表
b_id b_name
2 索隆
3 香吉士
4 乔巴
  • 内连接语句:select * from a a inner join a a on a.a_id = b.b_id
a_id a_name b_id b_name
2 索隆 2 索隆
3 香吉士 3 香吉士
  • 左外连接语句:select * from A A left outer join B B on a.a_id = b.b_id
a_id a_name b_id b_name
1 路飞 NULL NULL
2 索隆 2 索隆
3 香吉士 3 香吉士

3.1.4 交叉连接(笛卡尔积)

1
2
3
4
5
6
7
SELECT  [字段名,...]
FROM1 别名1
cross join2 别名2
WHERE [条件]
GROUP BY [字段名]
HAVING [条件]
ORDER BY ASC/DESC

3.1.5 子查询

3.1.6 分页查询

  • offset要显示条目的起始索引(起始索引从0开始)
  • size要显示的条目个数
  • 网页的分页显示数据需要对数据库进行分页查询
1
2
3
4
5
6
7
8
SELECT  [字段名,...]
FROM1 别名1
cross join2 别名2
WHERE [条件]
GROUP BY [字段名]
HAVING [条件]
ORDER BY ASC/DESC
LIMIT offset,size;

3.1.7 联合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
适用于要查询的结果来自多个表,且多个表没有直接的连接关系,但查询的信息一致时可以使用联合查询,类似于数学的并集
*/
查询语句1
union
查询语句2

# 示例
SELECT id,cname,csex FROM t_ca
UNION
SELECT t_id,tName,tGender FROM WHERE tGender='male';

# 加ALL表示会包含重复的内容
SELECT id,cname,csex FROM t_ca
UNION ALL
SELECT t_id,tName,tGender FROM WHERE tGender='male';

3.2 示例

3.2.1 查询前五条员工信息

1
2
3
SELECT * FROM employees LIMIT 0,5;  
#或者
SELECT * FROM employees LIMIT 5;

3.2.2 分页查询规律

  • page表示第几页,(page-1)*size表示每一页的起始查询位置
1
2
3
SELECT 查询列表
FROM
LIMIT (page-1)*size,size;

四、增删改

4.1 插入数据

1
2
3
4
5
6
INSERT INTO 表名 VALUES(元组值);
INSERT INTO 表名(列名序列) VALUES(元组值);
INSERT INTO 表名(列名序列) VALUES(元组值),(元组值),...;
INSERT INTO 表名 SET 列名=值,列名=值,... #用的少
INSERT INTO 表名(列名序列) SELECT 查询语句;
INSERT INTO 表名(列名序列) TABLE 表名2;

4.2 删除数据

1
DELETE FROM 表名 WHERE 条件表达式

4.3 修改数据

1
2
UPDATE 表名 SET 列名=值表达式,列名=值表达式,... WHERE 条件表达式
UPDATE 表名 SET ROW=元组 WHERE 条件表达式

五、视图

5.1 视图的创建

1
CREATE VIEW 视图名(列名序列) AS SELECT查询语句

5.2 视图的撤销

1
DROP VIEW 视图名

六、Mysql数据类型

6.1 数值型

类型 大小 范围(有符号) 范围(无符号)
TINYINT 1 byte (-128,127) (0,255)
SMALLINT 2 bytes (-32 768,32 767) (0,65 535)
MEDIUMINT 3 bytes (-8 388 608,8 388 607) (0,16 777 215)
INT或INTEGER 4 bytes (-2 147 483 648,2 147 483 647) (0,4 294 967 295)
BIGINT 8 bytes
FLOAT 4 bytes
DOUBLE 8 bytes
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2

6.2 日期型

6.3 字符型

一、音的概念

1.1 音名/唱名

  • 音名是对音的一种标记,而唱名则是我们平时哼出来的do/re/mi/fa/sol/la/ti
  • 音名像是对音的一种抽象,而唱名则是具体的对音的表现,也就是说任何一个音名都可以表示为1,然后一次推导出其他音名,当然我这里说的是自然大调的情况,后面在做详细解释
音名 C D E F G A B
唱名 1 2 3 4 5 6 7

1.2 十二平均律

  • 按照乐理从古至今的发展,人们发现了十二平均律这样一套声音的规律
  • 在物理学中,声音是由振动产生的,而物体不同震动的频率会给人不同的听觉效果,国际上规定440HZ作为标准音,即钢琴上的第49号键(A键)的震动频率
  • 根据振动给人听感上的不同,人们以每隔半音(规定的频率大小)的形式来规定音与音的距离关系(音程),因此就有了十二平均律的概念
  • 比如对于1、2、3、4、5、6、7、1来说,它们就满足“全全半全全全半”的音程关系

1.3 音程/音数

  • 音程:表示音与音之间的距离,主要说的还是振动频率的关系
  • 音数:衡量音程的一种单位,记录音与音之间的半音数
  • 具体的音程与音数关系有以下几种:
音程 音数
纯一度 $0$
小二度 $\tfrac{1}{2}$
大二度 $1$
小三度 $1\tfrac{1}{2}$
大三度 $2$
纯四度 $2\tfrac{1}{2}$
增四度 $3$
纯五度 $3\tfrac{1}{2}$
减五度 $3$
小六度 $4$
大六度 $4 \tfrac{1}{2}$
小七度 $5$
大七度 $5\tfrac{1}{2}$
纯八度 $6$
  • 以C大调为例,各音阶度数关系如下:

二、调式

2.1 大调式

2.1.1 自然大调

  • 满足“全全半全全全半”的关系
  • $1、2、3、4、5、6、7、\dot{1}$

2.1.2 和声大调

  • $1、2、3、4、5、{}^b6、7、\dot{1}$

2.1.3 旋律大调

  • $1、2、3、4、5、{}^b6、{}^b7、1$

2.2 小调式

2.2.1 自然小调

  • $6、7、\dot{1}、\dot{2}、\dot{3}、\dot{4}、\dot{5}、\dot{6}$

2.2.2 和声小调

  • $6、7、\dot{1}、\dot{2}、\dot{3}、\dot{4}、{}^井\dot{5}、\dot{6}$

2.2.3 旋律小调

  • $6、7、\dot{1}、\dot{2}、\dot{3}、{}^井\dot{4}、{}^井\dot{5}、\dot{6}$

2.3 自然大调

  • 以下对自然大调进行着重讲解,因为自然大调在流行音乐中较为常见
  • 在吉他谱中,会在琴谱左上角标注C=1,这就规定了这首歌的调式为C调
    • 同理,如果标注G=1,则这首歌的调式为G调
    • 根据半音的规律,我们就可以在自然大调中推导出十二种调式(十二平均律)
    • 不同调式的给人的感觉是不同的,音名的构成也是不同的,下表就列出了不同调式的音的构成
音名
唱名 1、2、3、4、5、6、7、1
C C、D、E、F、G、A、B、C
#C #C、#D、#E、#F、#G、#A、#B、#C
D D、E、#F、G、A、B、#C、D
bE bE、F、G、bA、bB、C、D、bE
E E、#F、#G、A、B、#C、#D、E
F F、G、A、bB、C、D、E、F
bG bG、bA、bB、bC、bD、bE、F、bG
G G、A、B、C、D、E、#F、G
bA bA、bB、C、bD、bE、F、G、bA
A A、B、#C、D、E、#F、#G、A
bB bB、C、D、bE、F、G、A、bB
B B、#C、#D、E、#F、#G、#A、B

2.4 关系大小调

大调音阶的前三个音是大三度关系、小调音阶的前三个音是小三度关系

以自然大小调为例

  • C大调的关系小调为a小调
  • D大调的关系小调为b小调
  • F大调的关系小调为d小调
  • G大调的关系小调为e小调

推导也比较简单,比如C大调往前推一个小三度就是A,那么关系小调就是a小调

2.5 其他

  • 五声音阶:C G D A E,这五个音都相距纯五度,由此构成了五声音阶,顺序来看应该是1 2 3 5 6,这也是中国古代常用的音高顺序:宫 商 角 徵(zhǐ) 羽
  • 布鲁斯音阶:1、b3、4、b5、5、b7或者6、1、2、b3、3、5,音程间隔为:小三、大二、小二、小二、小三
  • Dorian Mode(多利亚调式——自然大调2级音阶):1、2、b3、4、5、6、b7
  • Phrygian Mode(弗利几亚调式——自然大调3级音阶):1、b2、b3、4、5、b6、b7
  • Lydian Mode(利底亚调式——自然大调4级音阶):1、2、3、#4、5、6、7。
  • Mixolydian Mode(米索利第亚调式——自然大调5级音阶):1、2、3、4、5、6、b7
  • Aeolian Mode(爱奥里亚调式———自然小调式):1、2、b3、4、5、b6、b7
  • Locrian Mode(洛克利亚调式——自然大调7级音阶):1、b2、b3、4、b5、b6、b7

三、和弦

3.1 和弦分类

3.1.1 三度和弦

  • 三和弦(三个音)
  • 五和弦(四个音)
  • 九和弦(五个音)
  • 十一和弦(六个音)
  • 十三和弦(七个音)

3.1.2 非三度和弦

  • 挂留和弦
  • 强力和弦

3.2 三和弦

3.2.1 大三和弦

  • 大三度+小三度
  • 如C调的C:1-3-5、F:4-6-1、G:5-7-2

3.2.2 小三和弦

  • 小三度+大三度
  • 如C调的Dm:2-4-6、Em:3-5-7、Am:6-1-3

3.2.3 增三和弦

  • 大三度+大三度
  • Caug:1-3-#5

3.2.4 减三和弦

  • 小三度+小三度
  • Cdim:1-b3-b5

3.3 七和弦

3.3.1 大七和弦

  • 大三和弦+根音的大7度音
  • Cmaj7:1-3-5-7
  • Fmaj7:4-6-1-3

3.3.2 大小七和弦(属七和弦)

  • 大三和弦+根音的小7度音
  • C7:1-3-5-b7
  • G7:5-7-2-4

3.3.3 小小七和弦

  • 小三和弦+根音的小7度音
  • Dm7:2-4-6-1
  • Em7:3-5-7-2
  • Am7:6-1-3-5

3.3.4 减小七和弦

  • 减三+根音7度音
  • Bdim7:7-2-4-6

3.3.5 减减七和弦

  • 减三+根音减7度音
  • Bm-5:7-2-4-b6

3.3.6 调内七和弦

以C大调为例,七和弦包括Cmaj7、Dm7、Em7、Fmaj7、G7、Am7

很明显,在某一大调调式中,只存在一个属七和弦,即五级属七和弦G7,这也是用来判断歌曲调式的一种方式

3.4 九和弦

3.4.1 属九和弦

  • 在属七和弦上加上根音的大九度音
  • C9:1-3-5-b7-2

3.4.2 Add和弦

  • 三和弦基础上再添加的音符,后面的数字代表距离根音的度数

  • Cadd9:1-3-5-2(根音的大9度音)

3.5 挂留和弦

  • Csus2:1-2-5(三度音转为二度音)
  • Csus4:1-4-5(三度音转为四度音)

3.6 和弦转位

以C和弦为例,$1-3-5$它的两个转位和弦分别为$3-5-\dot{1}$和$5-\dot{1}-\dot{3}$

其中C和弦的do始终为根音,对于不同的转为其最低音是不同的,比如$3-5-\dot{1}$的最低音是mi,$5-\dot{1}-\dot{3}$最低音是sol

$3-5-\dot{1}$转位和弦表示为:$C_6$或者$C/E$,其中由于最低音到最高音最低音到根音的度数都是6,所以表示为$C_6$

$5-\dot{1}-\dot{3}$转位和弦表示为:$C_6^4$或者$C/G$,其中$C_6^4$中的上标4表示最低音到根音的度数,下标6表示最低音到最高音的度数

对于七和弦,应该用最低音到7音的度数和最低音到根音的度数来表示转位和弦

四、五线谱

4.1 概述

4.2 音符

4.2.1 音符分类

  • 全音符
  • 二分音符
  • 四分音符
  • 八分音符
  • 十六分音符
  • 三十二分音符
  • 休止符
  • 浮点音符:延长当前音符时值的一半

4.2.2 音符关系

$全音符=2二分音符=4四分音符=8八分音符=16十六分音符$

4.3 节拍

4.3.1 概念

  • bpm:每分钟节拍数
  • 常见的节拍(从后往前读):
    • 4/4:表示以四分音符为一拍,每小节四拍;强弱规律:强、弱、次强、弱
    • 3/8:表示以八分音符为一拍,每小节三拍;强弱规律:强、弱、弱
    • 6/8:强弱规律:强、弱、弱、次强、弱、弱

4.3.2 常见节奏

  • 常见音符:全音符、二分音符、四分音符、八分音符、十六分音符、三十二分音符
  • 常见节奏组组合:

4.4 十二大小调五线谱

4.5 首调记谱法

  • C调:$1,2,3,4,5,6,7,\dot{1}$
  • D调:$2,3,#4,5,6,7,#\dot{1},\dot{2}$
  • E调:$3,#4,#5,6,7,#\dot{1},#\dot{2},\dot{3}$
  • F调:$4,5,6,b7,\dot{1},\dot{2},\dot{3},\dot{4}$
  • G调:$5,6,7,\dot{1},\dot{2},\dot{3},#\dot{4},\dot{5}$
  • A调:$6,7,#\dot1,\dot2,\dot3,#\dot4,#\dot5,\dot6$

五、乐器

5.1 打击乐器

5.1.1 架子鼓Drum

  • 嗵(tōng)鼓(tom drum):顾名思义,发咚咚声
  • 踩镲(chǎ)(hi-hat):擦擦声,声音没那么响亮
  • 军鼓(snare drum):哒哒声,较其他鼓要响亮些
  • 脚鼓(bass drum/kick):低音大鼓,又叫底鼓,从正面看的一个大鼓,脚踩发声
  • 节奏镲(ride cymbal):啪啪声,最响亮的声

  • 常用节奏示例:

    • 动(底鼓)、次(踩镲)、打(军鼓)、次 (踩镲)、动(底鼓 )、动(底鼓)、打( 军鼓) 次(踩镲)。

    • 动(底鼓 ) 、次(踩镲)、打(军鼓)、次( 踩镲)、 动( 底鼓)、 次(踩镲)、动(底鼓)、打(军鼓)、 次(踩镲)。

一、双栏信息

二、元素阴影效果

1
box-shadow: offset-x offset-y blur spread color inset;
  • offset-x:必需,取值正负都可。offset-x水平阴影的位置。
  • offset-y:必需,取值正负都可。offset-y垂直阴影的位置。
  • blur:可选,只能取正值。blur-radius阴影模糊半径,0即无模糊效果,值越大阴影边缘越模糊。
  • spread:可选,取值正负都可。spread代表阴影的周长向四周扩展的尺寸,正值,阴影扩大,负值阴影缩小。
  • color:可选。阴影的颜色。如果不设置,浏览器会取默认颜色,通常是黑色,但各浏览器默认颜色有差异,建议不要省略。可以是rgb(250,0,0),也可以是有透明值的rgba(250,0,0,0.5)。
  • inset:可选。关键字,将外部投影(默认outset)改为内部投影。inset 阴影在背景之上,内容之下。

三、元素访问效果

1
2
3
4
5
6
<!--
ele.hover:鼠标在元素上方时,要显示的效果
ele.link:未被访问的元素,要显示的效果
ele.active:鼠标按住不动时,要显示的效果
ele.visited:元素访问过后,要显示的效果
-->