暂时未有相关云产品技术能力~
文章是观看王道计算机组成原理所写中央处理器CPU1.CPU基本功能CPU:中央处理器CPU由运算器和控制器组成用计算机解决某个问题时要为它编写程序程序是一个指令序列,它告诉计算机要执行什么操作,在什么地方来找到用来操作的数据一旦把程序加载到内存储器,就可以由计算机来自动完成取出指令和执行指令的任务CPU其他功能:处理中断:①控制程序的输入和结果的输出,就是说控制主机和IO设备的交换信息,比如说我们程序在运行中,要给一个输入从可以让程序继续运行,不然的话,程序就会卡住不动,这里就运用到了处理中断②总线管理,运行过程中的异常情况和特殊情况的请求处理2.工作流程先来回顾一下控制器的组成,后面的知识需要用到第一步取指根据程序计数器PC指定的主存地址,从主存(或缓存)读取指令,放入指令寄存器IR 思考:程序开始执行以后,就会根据指令的关系来执行程序,那么第一条指令是谁给的呢?就比如说电脑上我们安装了QQ,当我们想要运行的时候,双击就可以了, 由于我们安装QQ的时候,是安装在硬盘上的,双击的时候,操作系统会把程序从硬盘调到内存中,调到内存的地方不是固定的,因为内存是动态管理的,也就是说一个程序开始的第一条指令存放的位置是由操作系统掌控的我们知道取出指令以后,肯定是想要进行相应的操作,但是我们肯定要先知道要做什么,也就是指令代码的含义,才能进行操作,所以下一步是译码。第二步译码根据IR中的指令,结合指令系统规范,分解指令的操作码、地址码等部分操作码——指明做哪种操作地址码——指明如何得到操作数、如何保存结果以及如何形成后续指令地址等第三步执行第四步写回将计算结果保存到主存(或缓存)第五步中断响应外部请求接下来就是这几个步骤的循环下面这张图就是CPU的工作流程3.CPU的基本组成1.运算部件2.寄存器组3.控制部件顾名思义,运算部件是用来做运算的,那么控制部件肯定是用来控制的控制部件主要是用来负责指令译码,并且发出完成指令功能所需要的各种操作的控制信号,比如说告诉各个部件,哪一些部件需要进行操作,具体要进行什么操作我们知道每一个人看待问题的角度是不一样的,因此不同的人看待同一种事物的感受也是不一样的,同样的,计算机的工作过程从不同的角度来看也是不一样的。从用户的角度:计算机的工作过程就是指令序列的连续执行从内部的实现机制:计算机的工作过程是控制命令下的信息传输,是控制流和信息流综合的过程对于控制部件来说,最重要的是控制命令(微命令)产生部件很明显谁先执行,肯定是有先后顺序的,分阶段的,应该要有一个时间信号,这里就由时序系统来控制的4.时序系统同步:做一件事有明确的时间规定,从什么时候开始,到什么时候结束5.CPU内部数据通路结构CPU内部单总线方式:把所有寄存器的输入端和输出端都连接到一条公共的通路上,同一个时刻只能有一个部件传输数据如果直接用导线连接,相当于多个寄存器同时并且一直向ALU传输数据---->每个输入端获得的是累加的信号,它无法分辨现在是谁给它传输数据通用寄存器有四个,R0到R3,用来存放操作数(源操作数,目的操作数,中间结果)和各种地址信息。如果执行一次加法运算时,选择两个操作数相加(放在两个寄存器),所得的结构送回其中一个寄存器(R2),那么R2原有的内容就会被替换4.指令周期及其数据流我们知道指令存在不同周期,我们可以使用触发器来判断指令处于哪一个周期指令的地址放在PC中,CPU和存储器之际的交流是依靠MAR和MDR5.数据通路5.1CPU内部单总线方式数据通路:数据在功能部件之间的传送路径数据通路的基本结构:①CPU内部单总线方式②CPU内部多总线方式③专用数据通路方式单总线方式:每一个部件都和总线相连,但是部件之间不相连每一个部件和总线之间的连接方式由一个可以控制信号的通断的控制信号控制练习5.2专用数据通路6. 控制器的功能和工作原理6.1控制器的结构和功能控制器是计算机系统的指挥中心,控制器的主要功能有下面三个:从主存中取出一条指令,并指出下一条指令在主存中的位置对指令进行译码或测试,产生相应的操作控制信号,以便启动规定的操作指挥并控制CPU,主存,输入和输出设备之间的数据流动方向CU的设计:硬布线(组合逻辑电路+触发器)微程序6.2硬布线控制器设计步骤①分析每个阶段的微操作序列②选择CPU的控制方式③安排微操作时序④电路设计6.2.1 CPU控制方式6.2.2 安排微操作时序的原则 没有相关性的操作可以安排在同一个节拍完成 6.2.3 组合逻辑设计设计步骤列出操作时间表写出微操作命令的最简表达式画出逻辑图
一、Sprig MVC简介1.1介绍Spring MVC是Spring Framework提供的Web组件,全称是Spring Web MVC,是目前主流的实现MVC设计模式的框架,提供前端路由映射、视图解析等功能Java Web开发者必须要掌握的技术框架1.2MVC是什么MVC是一种软件架构思想,把软件按照模型,视图,控制器来划分 Model:模型层,指工程中的JavaBean,用来处理数据 JavaBean分成两类:一类称为实体类Bean:专门用来存储业务数据,比如Student,User一类称为业务处理Bean:指Servlet或Dao对象,专门用来处理业务逻辑和数据访问View:视图层,指工程中的html,jsp等页面,作用是和用户进行交互,展示数据 Controler:控制层,指工程中的Servlet,作用是接收请求和响应浏览器流程:用户通过视图层发送请求到服务器,在服务器中请求被Controller接收Controller调用相应的Model层处理请求,处理完毕后结果返回到ControllerController再根据请求处理的结果找到对应的View视图,渲染数据后最终响应给浏览器Spring MVC对这套MVC流程进行封装,帮助开发者屏蔽底层细节,并且开放出相关接口供开发者调用,让MVC开发更简单方便二、Spring MVC实现原理2.1核心组件DispatcherServlet:前置控制器,负责调度其他组件的执行,可以降低不同组件之间的耦合性,是整个Spring MVC的核心模块Handler:处理器,完成具体的业务逻辑,相当于ServletHandlerMapping:DispatcherServlet是通过 HandlerMapping把请求映射到不同的HandlerHandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口完成HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果有额外拦截处理,可以添加拦截器进行设置)HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单的数据验证、数据类型转换、把表单数据封装到POJO等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的HandlerModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServletViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端2.2工作流程客户端请求被DispatcherServlet接收根据HandlerMapping映射到Handler生成Handler和HandlerInterceptorHandler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServletDispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理返回一个ModelAndView对象给DispatcherServletDispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,把逻辑视图解析成物理视图ViewResolver返回一个View进行视图渲染(把模型填充到视图中)DispatcherServlet把渲染后的视图响应给客户端三、第一个Spring MVC创建maven改成工程,pom.xml加入Spring MVC的依赖pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>SpringMVC</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>SpringMVC Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.19</version> </dependency> </dependencies> </project> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.19</version> </dependency>在web.xml中配置Spring MVC的DispatcherServlet首先在项目中创建java和resources的目录在resources目录中添加springmvc.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>然后在web.xml 配置Spring MVC的DispatcherServlet<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!-- 配置核心控制器 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- springmvc配置文件加载路径 1)默认情况下,读取WEB-INF下面的文件 2)可以改为加载类路径下(resources目录),加上classpath: --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- DispatcherServlet对象创建时间问题 1)默认情况下,第一次访问该Servlet的创建对象,意味着在这个时间才去加载springMVC.xml 2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。 <load-on-startup>1</load-on-startup> 数值越大,对象创建优先级越低! (数值越低,越先创建) --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!--/ 匹配所有的请求;(不包括.jsp)--> <!--/* 匹配所有的请求;(包括.jsp)--> <!--*.do拦截以do结尾的请求--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 拦截请求其实就是说,只要是符合这个URL的请求都会进入到Spring MVC中去看看有没有对应的Handler./不会拦截.jsp的路径,但是会拦截.html等静态资源DispatcherServlet是Spring MVC提供的核心控制器,这个一个Servlet程序,该Servlet程序会接收所有请求核心控制器会读取一个springmvc.xml配置,加载Spring MVC的核心配置配置/代表拦截所有请求代表在项目启动时实例化DispathcerServlet,如果没有配置,则在第一次访问Servlet时进行实例化3.springmvc.xml进行配置<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫包 --> <context:component-scan base-package="com.zyh.controller"></context:component-scan> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--给逻辑视图加上前缀和后缀 --> <!--前缀--> <property name="prefix" value="/"></property> <!--后缀--> <property name="suffix" value=".jsp"></property> </bean> </beans>创建Controller控制器Handler,在里面编写接收参数,调用业务方法,返回视图页面等逻辑@Controller public class HelloHandler { /** * 当客户端访问index请求时 * 直接自动关联到这个方法 * 执行这个方法后,会返回结果 * @return */ @RequestMapping("/index") public String index(){ System.out.println("接收到了请求"); //返回逻辑视图 逻辑视图相当于视图的别名 通过这个找到物理视图,也就是真正的视图 //这里返回的只是页面的名称,不是完整的页面访问路径 return "index"; } }@Controller注解是为了让Spring IOC容器初始化时自动扫描到该Controller类;@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是/;方法返回的结果是视图的名称index,该名称不是完整页面路径,最终会经过视图解析器解析为完整页面路径并跳转。配置Tomcat测试流程梳理DispatcherServlet接收到URL请求index,结合@RequestMapping("/index")注解把该请求交给index业务方法进行处理执行index业务方法,控制台打印日志,并且返回"index"字符串(逻辑视图).结合springmvc.xml中的视图解析器配置,找到目标资源:/index.jsp,即根目录下的index.jsp文件,把该JSP资源返回给客户端完成响应。Spring MVC搭建成功四、常用注解@RequestMappingSpring MVC通过@RequestMapping注解把URL请求和业务方法进行映射,在控制器的类定义处以及方法定义处都可以添加@RequestMapping,在类定义处添加相当于多了一层访问路径@RequestMapping常用参数 value:指定URL请求的实际地址,是@RequestMapping的默认值method:指定请求的method类型,包括GET、POST、PUT、DELETE等 @RequestMapping(value = "/index",method = RequestMethod.POST) public String index(){ System.out.println("接收到了请求"); //返回逻辑视图 逻辑视图相当于视图的别名 通过这个找到物理视图,也就是真正的视图 //注意:这里返回的只是页面名称,不是完整的页面访问路径 return "index"; }上述代码表示只有POST请求可以访问该方法,如果使用其他请求访问的话,直接抛出异常,比如GET请求params:指定request请求中必须包含的参数值,如果不包含的话,就无法调用该方法五、参数绑定5.1URL风格参数绑定params是对URL请求参数进行限制,不满足条件的URL无法访问该方法,需要在业务方法中获取URL的参数值。在业务方法定义时声明参数列表给参数列表添加@RequestParam注解进行绑定Spring MVC可以自动完成数据类型转换,该工作是由HandlerAdapter来完成的5.2RESTful风格的URL参数获取传统的URL:localhost:8080/hello/index?id=1&name=tomRESTful URL:localhost:8080/hello/index/1/tom @RequestMapping("/restful/{id}/{name}") public String restful(@PathVariable("id") Integer num, @PathVariable("name") String name){ System.out.println(num+"-"+name); return "index"; }5.3映射Cookie @RequestMapping("/cookie") public String getCookie(@CookieValue("JSESSIONID") String sessionId){ System.out.println(sessionId); return "index"; } 5.4使用POJO绑定参数Spring MVC会根据请求参数名和POJO属性名进行匹配,自动为该对象填充属性值,并且支持属性级联首先创建实体类为了方便测试,写一个addUser.jsp页面<%-- Created by IntelliJ IDEA. User: 17614 Date: 2022-07-04 Time: 21:01 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/hello/add" method="post"> <table> <tr> <td>编号:</td> <td> <input type="text" name="id"> </td> </tr> <tr> <td>姓名:</td> <td> <input type="text" name="name"> </td> </tr> <tr> <td> <input type="submit" value="提交"> </td> </tr> </table> </form> </body> </html> 然后在Handler中,编写相关方法启动Tomcat服务器结果发现出现乱码问题为了解决这个问题,我们只需要在web.xml配置文件中配置过滤器就可以了 <filter> <filter-name>encodingFilter</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> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>接下来看看属性级联是如何操作addUser.jsp<%-- Created by IntelliJ IDEA. User: 17614 Date: 2022-07-04 Time: 21:01 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/hello/add" method="post"> <table> <tr> <td>编号:</td> <td> <input type="text" name="id"> </td> </tr> <tr> <td>姓名:</td> <td> <input type="text" name="name"> </td> </tr> <tr> <td>地址编号:</td> <td> <input type="text" name="address.code"> </td> </tr> <tr> <td>地址信息:</td> <td> <input type="text" name="address.value"> </td> </tr> <tr> <td> <input type="submit" value="提交"> </td> </tr> </table> </form> </body> </html> 主体对象可以没有无参构造器,但是级联对象必须要有无参构造器5.5JSP页面的转发和重定向Spring MVC默认是通过转发的形式响应JSP,可以手动进行修改 @RequestMapping("/restful/{id}/{name}") public String restful(@PathVariable("id") Integer num, @PathVariable("name") String name){ System.out.println(num+"-"+name); return "index"; }比如,我们想把它改成重定向的话设置重定向的时候不能写逻辑视图,必须写明资源的物理路径,比如"rediect:/index.jsp"转发我们可以看到地址栏没变六、Spring MVC数据绑定数据绑定:在后台业务方法中,直接获取前端HTTP请求中的参数HTTP请求传输的参数都是String类型的,Handler业务方法中的参数是开发者指定的参数类型,比如int,Object,所以需要进行数据类型的转换Spring MVC的HandlerAdapter组件会在执行Handler业务方法之前,完成参数的绑定,开发者直接使用即可6.1基本数据类型@RequestMapping("/baseType") @ResponseBody public String baseType(int id){ return "id:"+id; } 客户端HTTP请求中必须包含id参数,否则抛出500异常,因为id不能为null 同时id的值必须为数值,而且必须为整数,否则抛出400异常6.2包装类 @RequestMapping("/packageType") @ResponseBody public String packageType(Integer id){ return "id:"+id; }如果HTPP请求中没有包含id参数,不会报错,id的值就是null,会直接返回id:null给客户端,但是如果id=a,或者id=1.2,同样会抛出400异常,因为数据类型无法转换value="id":把HTTP请求中名字为id的参数和Handler业务方法中的形参进行映射required:true表示id参数必须填,false表示非必填defaultValue="0":表示当HTTP请求中没有id参数的时候,形参的默认值是06.3数组类型@RequestMapping("/arrayType") @ResponseBody public String arrayType(String[] names){ StringBuffer buffer = new StringBuffer(); for (String str:names){ buffer.append(str).append(" "); } return "names:"+buffer.toString(); } 6.4POJO(java对象)public class User { private Integer id; private String name; private Address address; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", address=" + address + '}'; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Address { private Integer code; private String value; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return "Address{" + "code=" + code + ", value='" + value + '\'' + '}'; } }<%-- Created by IntelliJ IDEA. User: 17614 Date: 2022-07-04 Time: 21:01 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/hello/add" method="post"> <table> <tr> <td>编号:</td> <td> <input type="text" name="id"> </td> </tr> <tr> <td>姓名:</td> <td> <input type="text" name="name"> </td> </tr> <tr> <td>地址编号:</td> <td> <input type="text" name="address.code"> </td> </tr> <tr> <td>地址信息:</td> <td> <input type="text" name="address.value"> </td> </tr> <tr> <td> <input type="submit" value="提交"> </td> </tr> </table> </form> </body> </html> 我们如果希望直接把User对象返回给浏览器展示的话我们可以在springmvc.xml中添加一个消息转换器把中文乱码解决掉前后端转换的数据称为消息解决响应时乱码问题,springmvc.xml中配置转换器即可总结一下关于乱码的问题6.5ListSpring MVC不支持List类型的直接转换,需要包装成Objectpublic class UserList { private List<User> userList; public List<User> getUserList() { return userList; } public void setUserList(List<User> userList) { this.userList = userList; } } @RequestMapping("/listType") @ResponseBody public String listType(UserList userList){ StringBuffer buffer = new StringBuffer(); for (User user:userList.getUserList()){ buffer.append(user); } return "用户:"+buffer.toString(); } 为了方便测试,我们要写一个表单addList.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/hello/listType" method="post"> 用户1id:<input type="text" name="userList[0].id"><br> 用户1姓名:<input type="text" name="userList[0].name"><br> 用户2id:<input type="text" name="userList[1].id"><br> 用户2姓名:<input type="text" name="userList[1].name"><br> 用户3id:<input type="text" name="userList[2].id"><br> 用户3姓名:<input type="text" name="userList[2].name"><br> <input type="submit" value="提交"> </form> </body> </html> 接下来进行测试注意:User类一定要有无参构造,否则抛出异常6.6JSONJSON数据必须用JSON.stringfy()方法转换成字符串contentType:"application/json;charset=UTF-8"不能省略@RequestBody注解读取HTTP请求参数,通过Spring MVC提供的HttpMessageConverter接口把读取的参数转换为JSON、XML格式的数据,绑定到业务方法的形参需要使用组件结合@RequestBody注解把JSON转为JavaBean,这里使用FastJson,其优势是如果属性为空,就不会将其转为JSON@ResponseBody注解把业务方法返回的对象,通过HttpMessageConverter接口转为指定格式的数据,JSON、XML等,响应给客户端因为要用到jQuery,所以,先把相关代码写进来,因为代码很长,我这边就不展示出来了为了测试jQuery代码能不能使用,写一个json.jsp,然后在jsp中引入进来,写一个简单的代码测试一下<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script> <script type="text/javascript"> $(function (){ alert(123); }) </script> </body> </html>启动服务器如果,访问的时候,能够弹框123,说明jQuery可以使用,否则说明无法使用我们可以看到并没有弹窗,说明应该是哪里出了问题,jsp代码没有问题,问题应该是jQuery没有引进来,我们可以在浏览器用F12,检查一下发现json.jsp没有问题,问题出在jQuery上面,报500错误分析一下原因,因为我们刚开始在web.xml中,配置的是所有的请求都会被DispatcherServlet拦截映射,但是现在我们访问的是实际存在的资源,逻辑请求需要映射,但是物理请求是不需要映射的。这个时候,它会把我们的物理请求也进行映射,在配置文件加上下面的代码就可以了 <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping>解决好上面的问题后,我们就可以开始写代码了<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script> <script type="text/javascript"> $(function(){ var user = { "id":1, "name":"张三" }; $.ajax({ url:"/hello/jsonType", data:JSON.stringify(user), type:"POST", contentType:"application/json;charset=UTF-8", dataType:"text", success:function(data){ alert(data.id) alert(data.name) } }) }) </script> </body> </html> 我们发现出错了,打断点发现此时Ajax请求没有进入到我们刚刚写的方法。因为我们现在传的参数是json格式的,json格式就需要我们在后端把json格式解析成Java对象,这里我们仅仅加上@RequestBody注解是不够的,我们需要借助第三方工具把json解析成Java对象,这里用到的工具是fastjson,所以我们要在pom.xml中,把相关依赖导入进来<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency> 导入进来后,还需要在springmvc的配置文件中进行配置springmvc.xml配置<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置自动扫包 --> <context:component-scan base-package="com.zyh.controller"></context:component-scan> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--给逻辑视图加上前缀和后缀 --> <!--前缀--> <property name="prefix" value="/"></property> <!--后缀--> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven> <!--消息转换器 --> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property> </bean> <!--fastjson --> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean> </mvc:message-converters> </mvc:annotation-driven> </beans>七、Spring MVC视图层解析调用Web资源给域对象传值pagerequestsessionapplication业务数据的绑定是指把业务数据绑定给JSP域对象,业务数据的绑定是由ViewResolver来完成的,开发时,我们先添加业务数据,再交给ViewResolver来绑定,我们重点是学习如何添加业务数据,Spring MVC提供了下面几种方式来添加业务数据:MapModelModelAndView@SessionAttribue@ModelAttributeServlet的API7.1业务数据绑定到request域对象7.1.1MapSpring MVC在调用业务方法之前会先创建一个隐含对象作为业务数据的存储容器,设置业务方法的入参为Map类型,Spring MVC会把隐含对象的引用传递给入参7.1.2 ModelModel和Map类似,业务方法通过入参来完成业务数据的绑定7.1.3ModelAndView和Map,Model不同的是,ModelAndView不仅包含业务数据,同时也封装了视图信息,如果使用ModelAndView来处理业务数据,业务方法的返回值必须是ModelAndView对象业务方法中对ModelAndView进行两个操作:填充业务数据绑定视图信息第一种方式第二种方式第三种方式第四种方式第五种方式7.1.4 Servlet的APISpring MVC可以在业务方法种直接获取Servlet原生Web资源,只需要在方法定义时添加HttpServletRequest输入参数就可以,在方法体种直接使用request对象先在pom.xml导入相关依赖 <!--导入servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency>7.1.5@ModelAttributeSpring MVC还可以通过@ModelAttribute注解的方式添加业务数据,具体使用步骤如下:定义一个方法,这个方法用来填充到业务数据中的对象给该方法添加@ModelAttribute注解,不是响应请求的业务方法@ModelAttribute注解的作用,将请求参数绑定到Model对象。被@ModelAttribute注释的方法会在Controller每个方法执行前被执行(如果在一个Controller映射到多个URL时,要谨慎使用)。@ModelAttribute的作用是当Handler接收到一个客户端请求以后,不管调用哪一个业务方法,都会优先调用被@ModelAttribute注解修饰的方法,并且把其返回值作为业务数据,再到业务方法,此时业务方法只需要返回视图信息就可以了,不需要返回业务数据,即使返回业务数据,也会被@ModelAttribute注解修饰的方法返回的数据所覆盖域中的对象以key-value的形式存储,此时key默认值是业务数据所对应的类的类名首字母小写以后的结果如果getUser没有返回值,则必须手动在该方法中填充业务数据,使用Map或者Model均可。@ModelAttribute public void getUser(Model model){ User user=new User(); user.setId(1); user.setName("张三"); model.addAttribute("user",user); }7.2业务数据绑定到Session域对象7.2.1使用原生的Servlet API7.2.2@SessionAttribute@SessionAttribute这个注解不是给方法添加的,而是给类添加的@SessionAttributes除了可以通过key值绑定,也可以通过业务数据的数据类型进行绑定@Controller @SessionAttributes(type=User.class) public class ViewHandler{ ... }@SessionAttributes可以同时绑定多个业务数据@Controller @SessionAttributes(type={User.class,Address.class}) public class ViewHandler{ ... }或者@Controller @SessionAttributes(value={"user","address"}) public class ViewHandler{ ... }八、 Spring MVC自定义数据类型转换器Spring MVC默认情况下可以对基本类型进行类型转换,例如可以将String转换为Integer,Double,Float等。但是Spring MVC并不能转换日期类型(java.util.Date),如果希望把字符串参数转换为日期类型,必须自定义类型转换器1.创建DateConverter类,并且实现org.springframework.core.convert.converter.Converter接口,这样它就成为了一个自定义数据类型转换器,需要指定泛型<String,Date>,表示把String类型转为Date类型public class DateConverter implements Converter<String, Date> { private String pattern; public DateConverter(String pattern) { this.pattern = pattern; } @Override public Date convert(String s) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern); try { return simpleDateFormat.parse(s); } catch (ParseException e | java.text.ParseException e) { e.printStackTrace(); } return null; } } 2.在springmvc.xml中配置conversionService bean,这个bean是org.springframework.context.support.ConversionServiceFactoryBean的实例化对象,同时bean中必须包含一个converters属性,在其中注册所有需要使用的自定义转换器<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.zyh.converter.DateConverter"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg> </bean> </list> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>我们也可以自定义类<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/converter/student" method="post"> <input type="text" name="student"/>(1-张三-22)<br/> <input type="submit" value="提交"/> </form> </body> </html>接下来看看怎么解决中文乱码问题(搞了好久都要崩溃了)<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置自动扫包 --> <context:component-scan base-package="com.zyh.controller"></context:component-scan> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--给逻辑视图加上前缀和后缀 --> <!--前缀--> <property name="prefix" value="/"></property> <!--后缀--> <property name="suffix" value=".jsp"></property> </bean> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.zyh.converter.DateConverter"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg> </bean> <bean class="com.zyh.converter.StudentConverter"></bean> </list> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"> <!--消息转换器 --> <mvc:message-converters> <!-- 解决中文乱码问题 --> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property> </bean> <!--fastjson --> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean> </mvc:message-converters> </mvc:annotation-driven> </beans>如果我们想要在浏览器显示的是JSON格式,中文乱码需要在业务方法中通过设置response的编码方式来解决,springmvc.xml的bean不起作用,如果不需要把业务数据转换成json格式,springmvc.xml的配置可以起到中文乱码的作用九、Spring MVC和RESTful的集成9.1初识RESTfulRESTful是什么RESTful是当前比较流行的一种互联网软件架构模型,通过统一的规范来完成不同终端的数据访问和交换,REST全称是Representaional State Transfer(资源表现层状态转换)RESTful的优点:结构清晰,有统一的标准、扩展性好Resources资源指的是网络中的某一个具体文件,类型不限,可以是文本、图片、音频、视频、数据流等,是网络中真实存在的一个实体如何获取?可以通过统一资源标识符找到这个实体,URI,每一个资源都有特定的URI,通过URI可以找到一个具体的资源这里涉及到http协议的uri和url,推荐大家看这篇文章HTTP 协议详解 —— URI、HTTP protocol、HTTP headersPepresentation资源表现层,资源的具体表现形式,比如一段文字,可以使用TXT,JSON,HTML,XML等不同的形式来描述State Transfer状态转化是指客户端和服务端之间的数据交换,因为HTTP请求不能传输数据的状态,所有的状态都保存在服务端,如果客户端希望访问服务端的数据,就需要使其发生状态改变,同时这种状态转化是建立在表现层,完成转换就表示资源的表现形式发生了改变9.2RESTful的特点1.URL传参更加简洁传统形式URL: http://localhost:8080/findById?id=1RESTful风格URL: http://localhost:8080/findById/12.完成不同终端之间的资源共享,RESTful提供了一套规范,不同终端之间只要遵守这个规范,就可以实现数据交互。RESTful具体来说是四种表现形式,HTTP请求中四种请求类型(GET、POST、PUT、DELETE)分别表示四种常规操作,CRUDGET用来获取资源POST用来创建资源PUT用来修改资源DELETE用来删除资源两个终端要完成数据交互,基于RESTful的方式,增删改查操作分别需要使用不同的HTTP请求类型来访问。传统的web开发中form表单只支持GET和POST请求,如何解决呢?我们可以通过添加HiddenHttpMethodFilter过滤器,可以把POST请求转为PUT或者DELETE@Component @RequestMapping("/rest") public class RESTHandler { // @RequestMapping(value = "/find",method = RequestMethod.GET) @GetMapping("/find") @ResponseBody public String find(){ return "Hello"; } @PostMapping("/save") public void save(){ } @PutMapping("/update") public void update(){ } @DeleteMapping("/delete") public void delete(){ } }9.3HiddenHttpMethodFilter的实现原理HiddenHttpMethodFilter检测请求参数是否包含_method参数,如果包含则取出它的值,并且判断请求类型之后完成请求类型的转换,然后继续传递实现步骤:1.在form表单中添加隐藏域标签,name为_method,value为PUT或DELETE<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/rest/update" method="post"> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="提交"> </form> </body> </html> 2.在web.xml中配置HiddenHttpMethodFilter <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>接下来用一个小案例练练手9.4Spring MVC和RESTful整合这个整合没有连接数据库需求分析添加课程:成功则返回全部课程信息查询课程:通过id查询对应课程信息修改课程:成功则返回修改之后的全部课程信息删除课程:成功则返回删除之后的全部课程信息记得在pom.xml添加JSTL的依赖代码实现1.JSP添加课程:add.jsp修改课程:edit.jsp课程展示:courseList.jsp2.Course实体类public class Courese{ private Integer id; private String name; private double price; 还有对应的set,get方法,构造器 }3.CourseRepository@Repository public class CourseRepository { private Map<Integer, Course> map; public CourseRepository(){ map=new HashMap<>(); map.put(1, new Course(1,"语文",70.0)); map.put(2, new Course(2,"数学",80.0)); map.put(3, new Course(3,"英语",90.0)); } public Collection<Course> findAll(){ return map.values(); } public Course findById(Integer id){ return map.get(id); } public void saveOrUpdate(Course course){ map.put(course.getId(), course); } public void deleteById(Integer id){ map.remove(id); } } 4.CourseControllerpackage com.zyh.controller; import com.zyh.pojo.Course; import com.zyh.repository.CourseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletResponse; /** * @author zengyihong * @create 2022--07--11 16:14 */ @Controller @RequestMapping("/course") public class CourseController { @Autowired private CourseRepository courseRepository; @GetMapping("/findAll") public ModelAndView findAll() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("courseList"); modelAndView.addObject("list", courseRepository.findAll()); return modelAndView; } @DeleteMapping("/deleteById/{id}") public String deleteById(@PathVariable("id") Integer id){ courseRepository.deleteById(id); return "redirect:/course/findAll"; } @PostMapping("/save") public String save(Course course) { courseRepository.saveOrUpdate(course); /** * 添加信息后要求返回列表页面 * 重定向 */ return "redirect:/course/findAll"; } @GetMapping("/findById/{id}") public ModelAndView findById(@PathVariable("id") Integer id) { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("edit"); modelAndView.addObject("courser", courseRepository.findById(id)); return modelAndView; } @PutMapping("/update") public String update(Course course){ courseRepository.saveOrUpdate(course); return "redirect:/course/findAll"; } } JSPcourseList.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@page isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <table> <tr> <td>编号:</td> <td>名称:</td> <td>价格:</td> <td>操作:</td> </tr> <c:forEach items="${list}" var="course"> <tr> <td>${course.id}</td> <td>${course.name}</td> <td>${course.price}</td> <td> <form action="/course/deleteById/${course.id}" method="post"> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="删除"> </form> <a href="/course/findById/${course.id}">修改</a> </td> </tr> </c:forEach> </table> </body> </html> save.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/course/save" method="post"> <table> <tr> <td>课程编号:</td> <td> <input type="text" name="id"> </td> </tr> <tr> <td>课程名称:</td> <td><input type="text" name="name"></td> </tr> <tr> <td>课程价格:</td> <td> <input type="text" name="price"></td> </tr> <tr> <td> <input type="submit" value="提交"> </td> <td> <input type="reset" value="重置"> </td> </tr> </table> </form> </body> </html> edit.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@page isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <form action="/course/update" method="post"> <input type="hidden" name="_method" value="PUT"/> <table> <tr> <td>课程编号:</td> <td> <input type="text" name="id" value="${courser.id}"> </td> </tr> <tr> <td>课程名称:</td> <td> <input type="text" name="name" value="${courser.name}"> </td> </tr> <tr> <td>课程价格:</td> <td> <input type="text" name="price" value="${courser.price}"> </td> </tr> <tr> <td> <input type="submit" value="提交"> </td> </tr> </table> </form> </body> </html> 点击删除 点击修改点提交十、文件的上传下载10.1文件上传10.1.1单文件上传1.底层使用的是Apache fileupload 组件完成上传功能,Spring MVC只是对其进行了封装,简化开发,pom.xml <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>springmvc.xml为了把二进制数据解析成对象<!-- 文件的上传--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>2.JSP页面input的type属性设置为fileform表单的method设置为postform表单的enctype设置为multipart/form-data<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@page isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <form action="/file/upload" method="post" enctype="multipart/form-data"> <input type="file" name="img"/> <input type="submit" value="提交"> <!-- 加上/代表从根目录也就是8080开始找 --> </form> <img src="${src}"/> </body> </html> @Component @RequestMapping("/file") public class FileHandler { /** * 文件是以二进制流传输的 * @param img * @return */ @PostMapping("/upload") public String upload(@RequestParam("img") MultipartFile img, HttpServletRequest request){ if (img.getSize()>0){ String path = request.getSession().getServletContext().getRealPath("file"); String filename = img.getOriginalFilename(); File descFile=new File(path, filename); try { img.transferTo(descFile); request.setAttribute("src", "/file/"+filename); } catch (IOException e) { e.printStackTrace(); } } return "upload"; } } 然后选择文件提交10.1.2多文件上传<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@page isELIgnored="false" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>多文件上传</title> </head> <body> <form action="/file/uploads" method="post" enctype="multipart/form-data"> file1:<input type="file" name="imgs"/><br> file2:<input type="file" name="imgs"/><br> file3:<input type="file" name="imgs"/><br> <input type="submit" value="提交"/> </form> <c:forEach items="${pathList}" var="path"> <img src="${path}" width="300px"> </c:forEach> @PostMapping("/uploads") public String uploads(@RequestParam("imgs") MultipartFile[] imgs,HttpServletRequest request){ List<String> pathList=new ArrayList<>(); for (MultipartFile img:imgs){ if (img.getSize()>0){ String path = request.getSession().getServletContext().getRealPath("file"); String filename = img.getOriginalFilename(); File descFile=new File(path, filename); try { img.transferTo(descFile); pathList.add("/file/"+filename); } catch (IOException e) { e.printStackTrace(); } } } request.setAttribute("pathList", pathList); return "uploads"; }10.2文件下载JSP页面通过超链接点击进行下载<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件下载</title> </head> <body> <a href="/file/download?fileName=皮卡丘.jpg">皮卡丘.jpg</a> <a href="/file/download?fileName=柯南.png">柯南.png</a> <a href="/file/download?fileName=springmvc.png">springmvc.png</a> </body> </html>Handler /** * 根据文件的名字进行下载 */ @GetMapping("/download") public void download(String fileName, HttpServletRequest request, HttpServletResponse response) { if (fileName!=null){ String path = request.getSession().getServletContext().getRealPath("file"); File file=new File(path,fileName); OutputStream out=null; if (file.exists()){ //设置下载文件 response.setContentType("applicationContext/force-download"); //设置文件名 response.setHeader("Context-Disposition", "attachment;filename="+fileName); try { out=response.getOutputStream(); out.write(FileUtils.readFileToByteArray(file)); out.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if (out!=null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } }十一、拦截器11.1过滤器、监听器、拦截器的对比Servlet:处理Reequest请求和Response响应过滤器(Filter):对Request请求起到过滤作用,作用在Servlet之前,如果配置为/*可以为所有的资源(servlet、js/css静态资源等)进行过滤处理监听器(Listener):实现了javax.servlet.ServletContextListener接口的服务器端组件,它随Web应用的启动而启动,只初始化一次,然后一直监视,随Web应用的停止而销毁作用一:做初始化工作,web应用中spring容器启动ContextLoaderListener作用二:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等可以在某些动作 前后增加处理,实现监控,比如说统计在线人数,利用HttpSessionListener等拦截器(Interceptor):是Spring MVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image等的访问,只会拦截访问的控制器方法(Handler)servlet、filter、listener是配置在web.xml中,interceptor是配置在表现层框架自己的配置文件中在Handler业务逻辑执行之前拦截一次在Handler逻辑执行完但是还没有跳转页面之前拦截一次在跳转页面后拦截一次11.2拦截器基本概念Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。通过实现HandlerInterceptor接口继承HandlerInterceptor接口的实现类(如:HandlerInterceptorAdapter)来定义。11.3拦截器的实现通过实现HandlerInterceptor接口public class MyInterceptor implements HandlerInterceptor { /** * 在目标Handler(方法)执行前执行 * 返回true:执行Handler方法 * 返回false:阻止目标Handler方法执行 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("目标Handler执行前执行MyInterceptor---->preHandle方法..."); return true; } /** * 在目标Handler(方法)执行后,视图生成前执行 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("目标Handler执行后,视图执行前执行MyInterceptor---->postHandle方法..."); } /** * 在目标方法执行后,视图生成后执行 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("目标Handler执行后,视图执行后执行MyInterceptor---->afterCompletion方法..."); } } 拦截器配置1 <mvc:interceptors> <!-- 拦截器配置 --> <!-- 使用bean定义一个Interceptor 直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 --> <bean class="com.zyh.interceptor.MyInterceptor"></bean> </mvc:interceptors>拦截器配置方式2 <!-- 拦截器配置2 --> <mvc:interceptors> <!--定义在mvc:interceptor下面,可以自定义需要拦截的请求 如果有多个拦截器满足拦截处理的要求,则依据配置的先后顺序来执行 --> <mvc:interceptor> <!--通过mvc:mapping配置需要拦截的资源。支持通配符,可以配置多个 --> <mvc:mapping path="/**"/> <!-- /**表示拦截所有的请求--> <!--通过mvc:exclude-mapping配置不需要拦截的资源。支持通配符,可以配置多个 --> <mvc:exclude-mapping path="/hello/*"/> <!-- /hello/*表示放行hello路径下的请求 --> <bean class="com.zyh.interceptor.MyInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>## 11.4多个拦截器的实现 Spring MVC框架支持多个拦截器的配置,从而构成拦截器链,对客户端进行多次拦截操作过滤器配置 <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.zyh.interceptor.MyInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.zyh.interceptor.MyInterceptor2"></bean> </mvc:interceptor> </mvc:interceptors>自定义第二个过滤器public class MyInterceptor2 implements HandlerInterceptor { /** * 在目标Handler(方法)执行前执行 * 返回true:执行Handler方法 * 返回false:阻止目标Handler方法执行 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("2.目标Handler执行前执行MyInterceptor2---->preHandle方法..."); return true; } /** * 在目标Handler(方法)执行后,视图生成前执行 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("3.目标Handler执行后,视图执行前执行MyInterceptor2---->postHandle方法..."); } /** * 在目标方法执行后,视图生成后执行 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("5.目标Handler执行后,视图执行后执行MyInterceptor2---->afterCompletion方法..."); } } Handler@RequestMapping("/hello") @Controller public class HelloContro @RequestMapping("/packageType") @ResponseBody public String packageType(@RequestParam(value = "id", required = true) Integer id) { System.out.println("拦截的方法..."); return "id=" + id; } }
一、注入是什么通过Spring工厂以及配置文件,为所创建对象的成员变量赋值1.1为什么需要注入通过编码的方式为成员变量进行赋值,存在耦合?为什么我们以前都是通过set方法来为成员变量赋值,这样通过代码为成员变量赋值存在耦合注入可以解决这种问题1.2如何进行注入类的成员变量提供set方法配置Spring的配置文件 <bean id="person" class="com.zyh.basic.Person"> <!-- peroperty的name值要设置成为成员变量的属性名 value就相当于是我们给属性赋的值--> <property name="id"> <value >10</value> </property> <property name="name" > <value>tom</value> </property> </bean>1.3注入的好处解耦合1.4原理分析Spring读取配置文件,遇到bean标签 <bean id="person" class="com.zyh.basic.Person">这个时候它就会通过反射来创建对象Person person=new Person();调用的是无参构造方法,就算构造器私有也可以调用然后读取到property标签以前对应的属性值 <property name="id"> <value>10</value> </property> <property name="name"> <value>tom</value> </property> 比如说,读取到标签的name属性值为id,value标签为10这个时候,就会调用对应的set方法为这个属性赋值如果我们写的实体类中没有提供对应的set方法,那么就会报错报错信息说id不可写Invalid property 'id' of bean class [com.zyh.basic.Person]: Bean property 'id' is not writable or has an invalid setter method我们可以看出在这个时候,Spring底层是通过调用set方法来进行成员变量的赋值,这种方式称为set注入二、Set注入详解我们知道一个类里面的参数可以是不同的类型,我们可以大体分成两类Java原生类型(JDK内置类型) 比如基本数据类型,引用数据类型我们自定义的那些类型针对不同的类型的成员变量,我们在property标签中,需要嵌套其他的标签1.JDK内置类型1.1String+8种基本数据类型在property标签中使用value标签来进行赋值<property name="name"> <value>tom</value> </property>1.2数组类型private String[] emails; <property name="emails"> <list> <value>zhangsan@qq.com</value> <value>lisi@qq.com</value> <value>wangwu@qq.com</value> </list> </property>1.3Set类型private Set<String> details;<property name="details"> <set> <value>101</value> <value>102</value> <value>103</value> <value>102</value> </set> </property>注意:如果我们在配置文件的value标签中配置多个相同的值,我们在输出的时候其实那些相同的数据只会保留一个,其他的会被过滤掉1.3List类型 private List<String> address;<property name="address"> <list> <value>上海</value> <value>北京</value> <value>杭州</value> </list> </property>1.4Map类型注意:Map集合中的数据是以键值对的形式存储的,把key和value看成一个Entry(javase的内容)在这里如果我们想要用标签来进行对应的赋值的话,在map里面用entry标签entry里面用key标签代表键值根据对应类型选择对应类型的标签private Map<String,String> map;<property name="map"> <map> <!-- map中的数据以键值对的形式存储--> <entry> <key><value>10</value></key> <value>tom</value> </entry> </map> </property>1.Properties类型Perperties是特殊的map,它的键值对都是存储String类型<property name="p"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> </props> </property>2.用户自定义类型2.1方式一为成员变量提供set,get方法配置文件中进行注入(赋值)<!-- bean标签就是为了创建对象的--> <bean id="userservice" class="com.zyh.basic.UserServiceImpl"> <property name="userDao" > <bean class="com.zyh.basic.UserDAOImpl"/> </property> </bean>不过这种方法存在着一种问题,比如说现在有三个类,A,B,C,A类的成员变量中有C类型,B类的成员变量也有C类型,那么使用第一种方式的话,我们如果想要为这个属性赋值的话,我们就得在配置文件中进行两次注入,这样就会导致代码冗余,因为我们要进行的操作功能其实是一样的,其实我们是可以想办法让A和B共用C,但是在Spring中,只要写bean标签,它就会帮我们创建对象,这样就导致C类型的对象多次创建,导致资源浪费2.2方式二为成员变量提供set,get方法配置文件中进行配置 <bean id="userdao" class="com.zyh.basic.UserDAOImpl"></bean> <bean id="userservice" class="com.zyh.basic.UserServiceImpl"> <property name="userDao" > <ref bean="userdao"></ref> </property> </bean> 3.Set注入的简化写法3.1基于属性简化先来回顾一下,之前我们写set注入JDK类型注入 <property name="name"> <value>tom</value> </property>我们现在有一种简化的写法用value属性代替value标签 <property name="name" value="tom"/> 注意:value属性只能简化8大基本数据类型+String类型注入标签我们再来看一下,如果是用户自定义类型要怎么简化老规矩,先来回顾之前的注入怎么写 <bean id="userdao" class="com.zyh.basic.UserDAOImpl"></bean> <bean id="userservice" class="com.zyh.basic.UserServiceImpl"> <property name="userDao" > <ref bean="userdao"></ref> </property> </bean>我们可以用ref属性来替代\<ref bean><property name="userDAO" ref="userdao"/>3.2基于p命名空间简化下面两种方式是一样的 <bean id="person" class="com.zyh.basic.Person" p:id="10" p:name="jack"/> <bean id="person" class="com.zyh.basic.Person"> <property name="id" value="1"></property> <property name="name" value="tom"></property> </bean>下面的写法也是一样的 <bean id="userdao" class="com.zyh.basic.UserDAOImpl"></bean> <bean id="userservice" class="com.zyh.basic.UserServiceImpl"> <property name="userDao" > <ref bean="userdao"></ref> </property> </bean><bean id="userdao" class="com.zyh.basic.UserDAOImpl"></bean> <bean id="userService" class="com.zyh.basic.UserServiceImpl" p:userDao-ref="userdao"></bean>
⛱️vi和vim基本介绍1️⃣Linux系统会内置vi文本编辑器,类似于Windows下的记事本2️⃣Vim(目前我们用得比较多)具有程序编辑的能力,可以看成是Vi的增强版本,它可以主动的以字体颜色辨别语法的正确性,方便程序设计。3️⃣Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。4️⃣简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具。 连 vim 的官方网站 (http://www.vim.org) 自己也说 vim 是一个程序开发工具而不是文字处理软件。vim 键盘图(图片来自W3Cschool):⛱️vi和vim常用的三种模式1️⃣正常模式vim打开一个档案就直接进入正常模式(默认的模式)我们可以使用键盘中的上下左右来移动光标在这个模式下面,我们可以复制粘贴,也可以删除档案内容输入:(冒号)可以进入到命令行模式2️⃣编辑模式 按i,o,a,r(大小写均可)等任何一个字母都会进入到编辑模式按ESC进入到正常模式3️⃣命令行模式在这个模式中,完成读取、存盘、替换、离开vim、显示行号等。wq写入并退出w:写q:退出⛱️ 快速入门案例要求:使用vim编写一个Hello.java程序并且保存在工作中,我们主要还是以使用Xshell为主,在这里,我就通过Xshell来进行演示注意,记得 vi 后面一定要加文件名,不管该文件存在与否!然后按下回车键按下 i 进入输入模式,开始编辑文字然后我们就可以开始进行编辑了先输入ESC进入正常模式,然后再:wq,然后回车就会回到下面的界面我们可以输入ls查看一下如果我们想要修改刚刚写的Java文件的话,那就输入vim Hello.java,也就是输入刚刚编辑的文件名,我们可以通过table键进行代码补全⛱️各种模式的切换⛱️快捷键使用及其练习1️⃣拷贝当前行 yy,拷贝当前行向下的5行5yy,并粘贴(输入p)注意:小键盘输入数字默认当成是插入模式2️⃣删除当前行dd,删除当前行向下的5行5dd3️⃣在文件中查找某个单词[命令行下,/关键字,回车查找,输入n就是查找下一个]4️⃣设置文件的行号,取消文件的行号[命令行下 :set nu 和 :set nonu]5️⃣编辑/etc/profile文件,使用快捷键到该文档的最末行G和最首行gg6️⃣在一个文件中输入"hello",然后又撤销这个动作 u7️⃣编辑/etc/profile文件,并把光标移动到20行 shift+g
⛱️应用场景我们先来看一下,实际在公司开发的时候,具体的应用场景:1️⃣Linux服务器是开发小组共享的2️⃣ 正式上线的项目是运行在公网上的3️⃣ 因此程序员需要远程登录到Linux进行项目管理或者开发4️⃣远程登录客户端有Xshell6,Xftp6,我们学习使用Xshell和Xftp6,其他的远程工具类似 ⛱️远程登录Linux-Xshell7 介绍1️⃣Xsell是目前最好的远程登录到Linux操作的软件,流畅的速度并且完美解决了中文乱码的问题,是目前程序员首选的软件 2️⃣Xshell是一个强大的安全终端模拟软件,它支持SSH1,SSH2,以及Microsoft Windows平台的TELNET协议。 3️⃣Xshell可以在Windows界面下用来访问远端不同系统下的服务器,从而比较好的达到远程控制终端的目的Xshell7的下载-安装-配置-使用链接: https://pan.baidu.com/s/1hZ7f5nM4n01pwV1KhrsCRg?pwd=fi2r 提取码: fi2r 选择软件安装的位置然后点击确定,安装然后就安装成功了我们接下来就得先在虚拟机的终端通过ifconfig指令来查看一下网络的IP地址我们还得保证主机可以ping(用来检测两台主机的网络是否通畅)通Linux服务器公网我们在Windows打开终端 Win+R+cmdXshell连接虚拟机首先点击这个图标会跳出下面这个界面,然后我们点击新建点击确定,然后点击关闭然后就登录上去了这个时候我们其实就已经可以操作里面的内容了⛱️远程上传下载文件Xftp7基本介绍1️⃣是一个基于Windows平台的功能强大的SFTP、FTP文件传输软件 2️⃣使用了Xftp以后,Windows用户可以安全地在UNIX/Linux和Windows PC之间传输文件(PC端指的是电脑客户端) 软件的安装和配置(包括中文乱码的解决)然后选择安装的目录然后安装然后就等待安装就可以了双击Xftp7点击确定,出现下面的界面则点击连接我选择的是一次性接受输入用户名和密码接下来需要解决中文乱码问题文件---属性-------选项------勾选使用UTF-8编码(当前会话属性和默认绘画会话属性都设置一下)>左边的就是Windows下的文件,右边的是Linux下的文件接下来开始文件的上传我们可以看到图片就上传过来了我们在虚拟机中可以看到这张图片接下来看看怎么把Linux中的文件下载到Windows中然后Windows中就会出现相应的文件⛱️小练习1️⃣通过Xftp7软件可以连接到Linux,并且传输一个文件到/root目录(参照刚刚的演示)2️⃣使用Xshell7可以连接到Linux,并且可以重新启动Linux系统(通过reboot指令)我们一输入reboot指令,我们发现虚拟机就会重启⛱️结束语千里之行,始于足下,每天坚持下去,最终会有所收获。
虚拟机的安装学习Linux需要一个环境,我们需要创建一个虚拟机,然后在虚拟机上安装一个Centos系统来学习先安装vitual machine 16.2再安装Linux(CentOS 7.6/centOS8.1)原理示意图vmware16.2.3下载官网地址https://www.vmware.com/cn.html然后进入到下面的界面(本来是英文页面,浏览器可以选择简体中文)来到这个页面,因为我们一般是将虚拟机安装到 windows 系统上,也就是选择 VMware Workstation xx.x.x Pro for Windows 这个下载链接,如果你要安装到其它操作系统上,就得选择对应的下载链接了因为我是第一次打开这个网站下载,所以需要进行登录的点击sign up now这里我把网页翻译成中文了就会进入这个页面,它提醒你检查你的电子邮件,你需要打开你的邮箱,查找 vmware 官方给你发的邮件打开你的邮箱,点击 单击此处激活您的账户 ,开始激活然后输入验证码,就会跳转到登录页面登录后,回到刚刚页面继续下载然后我们等着下载就可以了,待会就是安装过程了为了方便管理,我们可以在D盘建立一个文件夹专门用来存放下载的软件我在D:\Program Files目录下面创建了一个VMware的文件夹,将下载好的 VMware安装包 放入该文件夹中,并创建了一个 VMware-workstation 的目录来存放即将安装的虚拟机双击 .exe 可执行文件,首先它会准备安装虚拟机需要的一些必要环境点击 下一步把我接收选上,然后点击下一步这里它会给我一个默认的安装地址,是安装到 C 盘的,你可以选择你想要安装的路径,点击 更改 后即可选择目录,别放在带有中文或者特殊字符的目录下,我就把它放到之前创建的 VMware-workstation 文件夹中,点击 下一步点击下一步点击下一步点击下一步然后点击安装,开始安装然后安装完成点击许可证输入然后我们在桌面上就可以看到下面的图标打开以后是这个样子的CentOS的安装(后面提供了网盘链接)CentOS官网然后会展示 CentOS-Linux 的镜像列表,找一个镜像去下载 CentOS我选择的是http://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/来到阿里云的开源镜像站,找到对应的 iso文件链接 点击下载以上就是 CentOS 映像下载的步骤,以下我也提供了我已经下载好的 CentOS-Linux链接:https://pan.baidu.com/s/1vTCFkdOvFE6c7xMnbgjzkg?pwd=vuh3 提取码:vuh3 我把下载好的放在Centos-mirrors这个文件夹里面# CentOS 安装到 VMware 我们先创建一个新的虚拟机配置类型选择典型,然后点击下一步客户机操作系统勾选 Linux(L) ,版本选择 iso镜像文件 对应的版本,我这里就是 CentOS 7 64位 了,再点击 下一步指定磁盘容量,默认是 20GB,勾选 将虚拟磁盘拆分成多个文件(M) ,点击 下一步虽然说,我们这里选择磁盘空间是20GB,但是它并不是说马上占用这么多的空间,而是随着文件慢慢增多,慢慢占用这部分的空间然后选择自定义硬件点击 新 CD/DVD(IDE) ,右边会出现 设备状态 和 连接 的一些配置,在 连接 配置下勾选 使用 ISO 映像文件(M),点击 游览 ,选择你之前下载好的 CentOS 7 映像文件 ,再 关闭 该硬件窗口(其他都是默认)然后点击完成这样我们就能在 VMware Workstation 的主页面看到刚刚新建好的 my-linux(虚拟机) 了选定我们创建好的虚拟机之后,点击 开启此虚拟机然后开始等待这里系统会先让我们设置下语言,选择 简体中文 ,再点击 继续然后选择我要配置分区点击完成,进入到下面的页面boot分区点击加号swap分区设备类型记得改成标准分区,文件系统选择swap根分区把剩余空闲空间,全部交给根分区即可选择标准分区文件系统要选择ext4然后点击完成,会弹出下面弹框,提示我们是否要进行改变点击接收更改 接下来就是网络名和主机名然后点击应用,点完成然后点击软件选择点击完成,然后点击开始安装点击root去设置root用户密码点击创建用户,去创建一个新的用户和密码这个时候,我们就有了两个用户,一个是root权限比较高,一个是tom,权限比较低,然后耐心等待安装安装好了以后,点击重启 然后就会跳出这个界面, 点击未接受许可证,然后点击同意许可协议然后点击完成,就会回到下面的界面然后点击右下角的完成配置在默认情况下,Linux希望我们通过用普通用户登录,我们也可以点击未列出,然后用root用户登录,输入用户名和密码登录等待一断时间后,就会出现下面这个界面,我们可以设置语言,选择汉语, 输入方式选择汉语拼音 位置服务关闭,然后点击前进点击跳过然后就可以开始使用这些是告诉我们一些使用技巧的,点击关闭还有一种方法可以测试是否连上网络,打开火狐浏览器(默认),看看网页能否打开我们也可以输入百度的网址看看 接下来设置一下分辨率 应用程序——系统工具——设置点击设备可以在这里设置想要的分辨率到这里,终于成功了CentOS安装难点 ——网络连接方式主机模式某些特殊的网络调试环境中,要求将真实环境和虚拟环境隔离开,这时可以采用主机模式,所有的虚拟系统可以相互通信,但虚拟系统和真实网络是隔离开的;虚拟系统的TCP/IP配置信息都是由虚拟网络中的DHCP服务器动态分配;虚拟系统和真实网络可以相互通信,相当于两台机器通过双绞线互连;虚拟网络是一个全封闭的网络,唯一能够访问的就是主机,不同于NAT的地方就是主机模式没有NAT服务,故虚拟网络不能连接到Internet。1、虚拟机之间可以相互通信2、虚拟机和主机之间也能相互通信3、虚拟机不能访问外网虚拟机克隆如果说我们已经安装了一台Linux操作系统,我们还想要更多的,我们不需要重新安装,只要克隆就可以了。方式1:直接拷贝一份安装好的虚拟机文件方式2:使用vmware的克隆操作注意:克隆的时候,需要先关闭Linux操作系统 点击下一页选择完整克隆出现下面的界面,就说明克隆完成了点关闭以后,我们会发现这里会出现新的虚拟机虚拟机快照如果我们在使用虚拟机系统的时候(比如Linux),当我们想要回到原先的某一个状态,也就是说我们担心可能有一些误操作导致系统异常,需要回到原来某一个正常运行的状态,vmware也提供了这样的功能,叫做快照管理应用案例安装好系统以后,先做一个快照A进入到系统。创建一个文件夹,再保存一个快照B回到系统刚刚安装好的状态,即快照A试试看,是否还能再回到快照B先开启虚拟机,然后登录新建一个叫hello的文件夹然后再拍一次快照然后创建一个叫作hello2的文件夹再拍一次快照点击A,点击转到,就可以转到快照A对应的状态我们这个时候,可以选择跳转到B这个时候,再创建一个文件夹hello2,然后拍下快照D虚拟机的迁移和删除虚拟系统安装好了,它的本质是文件(放在文件夹里面)如果虚拟系统要迁移的话,我们可以把安装好的虚拟系统这个文件夹整体拷贝或者剪切到其他位置使用如果要删除的话,我们可以用vmware进行移除,再点击菜单,从磁盘删除就可以了。或者直接手动删除虚拟系统对应的文件夹就可以了
跨域处理跨域问题演示我们先来演示一下跨域问题,后端代码参照上一篇博客如果在前端想要获取班级的数据,就应该给后端发送一个get请求但是我们可以发现出错了跨域概述跨域问题:在前后端分离开发过程中,前后端程序独立部署在不同的服务器,默认是拒绝跨域的跨域问题产生原因:在设计HTTP协议的时候,为了保证数据的安全,定义了同源同策规则,就比如我们的qq不能发消息给微信一样,所以不允许跨域访问跨域问题的解决方案:后端处理前端处理中间反向代理服务器什么是跨域,跨域解决方案讲跨域的话,那我们就得了解一下同源策略。同源策略是由Netscape提出的一个著名的安全策略,它是浏览器最核心也是最基本的安全功能,现在所有支持JavaScript的浏览器都会使用这个策略。同源是指协议、域名、端口要相同。同源策略是基于安全方面的考虑提出了的,这个策略本身是没有问题的,但是我们在实际开发中,因为各种原因又经常会有跨域的需求,传统的跨域方案是JSONP,JSONP虽然可以解决跨域,但是有一个很大的局限性,那就是它只支持GET请求,不支持其他类型的请求本篇文章要说的是CORS(Cross-origin resource sharing,跨域源资源共享),是一个W3C标准,它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是JSONP模式的现代版,在Spring框架中,对于CORS也提供了相应的解决方案。解决方法在方法上使用@CrossOrigin /** * 查询所有班级信息 */ @GetMapping("/school/cls") //所有的客户端请求都运行 //@CrossOrigin //参数:限制前端跨域的地址 @CrossOrigin(value = "http://localhost:8848") public ResultInfo queryAllCls() { ResultInfo info = new ResultInfo(); try { List<Cls> clsList = clsService.queryAllCls(); info.setCode(200); info.setOk(); info.setData(clsList); } catch (Exception e) { e.printStackTrace(); info.setError(); } return info; }自定义全局配置类这种方式不适合带请求头的方式/** * @author zengyihong * @create 2022--08--16 17:56 */ @Configuration public class WebConfig implements WebMvcConfigurer { /** * addMapping:允许哪一些资源路径被跨域 * allowedOrigins:允许哪些资源被跨域 * allowedMethods:允许哪些请求方式被跨域 * allowCredentials:是否使用缓存机制 * maxAge:跨域请求的超时时间 * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") // .allowedOrigins("http://localhost:8080","http://localhost:88848") .allowedMethods("*") // .allowedMethods("GET","POST","DELETE","PUT") .allowCredentials(false) .maxAge(3600); } } 自定义跨域过滤器package com.zyh.springboot.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @author zengyihong * @create 2022--08--16 19:04 */ @Configuration public class CustomCorsFilter { @Bean public FilterRegistrationBean corsFilter(){ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(false);//表示是否允许请求带有验证信息 config.addAllowedOrigin("*");//表示允许所有,可以设置需要的地址 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config);//CORS配置对所有接口都有效 FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(3600); return bean; } }
前后端分离开发概述相关术语 前端和后端:前端和后端是针对于技术来说的。前端: 负责页面展示相关的技术,比如html,css,js,jquery,vue,bookstrap等后端:与数据,还有业务逻辑相关,比如Java,MySQL等移动端:移动设置相关的技术,比如Android(java,kotilin),iOS(swift)全栈工程师:前端,后端,移动端全过程工程师:产品设计,代码开发,测试,运维前台和后台前台和后台是相对于使用者来说的前台:它是相对于普通用户来说,比如用户可以看到的网站后台:它是对应程序的管理人员和运维人员而言的,后台也包括前端(页面)和后端(数据和业务)前后端分离开发概述前后端分离模式概述 早期的时候,web应用中的数据,页面,渲染都在服务端完成,但是随着时代的发展,前端设备越来越多(手机,平板,桌面电脑等),后来就慢慢兴起前后端分离的思想:后端负责数据处理,前端负责数据渲染,前端静态页面调用指定api获取固定格式的数据为了实现前后端分离,必须要有一种统一的机制,方便不同的前端设备和后端进行通信接口规范RESTful API的理解RESTful是什么RESTful是当前比较流行的一种互联网软件架构模型,通过统一的规范来完成不同终端的数据访问和交换,REST全称是Representaional State Transfer(资源表现层状态转换)RESTful的优点:结构清晰,有统一的标准、扩展性好是前后端交互的规范Resources资源指的是网络中的某一个具体文件,类型不限,可以是文本、图片、音频、视频、数据流等,也可能是一个学生的信息,添加,删除,查询等都被看成是不同资源操作。如何获取?可以通过统一资源标识符找到这个实体,URI,每一个资源都有特定的URI,通过URL可以找到一个具体的资源HTTP 协议详解 —— URI、HTTP protocol、HTTP headersPepresentation资源表现层,资源的具体表现形式,比如一段文字,可以使用TXT,JSON,HTML,XML等不同的形式来描述对互联网的任何操作,都看成是对资源的操作State Transfer状态转化是指客户端和服务端之间的数据交换,因为HTTP请求不能传输数据的状态(HTTP协议是无状态请求),所有的状态都保存在服务端,如果客户端希望访问服务端的数据,就需要使其发生状态改变,同时这种状态转化是建立在表现层,完成转换就表示资源的表现形式发生了改变对于资源的操作行为的表现方式称为转换状态指的是对资源的操作行为RESTful风格的特点1.URL传参更加简洁传统形式URL: http://localhost:8080/findById?id=1RESTful风格URL: http://localhost:8080/findById/12.完成不同终端之间的资源共享,RESTful提供了一套规范,不同终端之间只要遵守这个规范,就可以实现数据交互。RESTful具体来说是四种表现形式,HTTP请求中四种请求类型(GET、POST、PUT、DELETE)分别表示四种常规操作,CRUD。通过不同的请求方式来完成不同的操作GET用来获取资源POST用来创建资源PUT用来修改资源DELETE用来删除资源两个终端要完成数据交互,基于RESTful的方式,增删改查操作分别需要使用不同的HTTP请求类型来访问。传统的web开发中form表单只支持GET和POST请求,如何解决呢?我们可以通过添加HiddenHttpMethodFilter过滤器,可以把POST请求转为PUT或者DELETERESTful风格的话,响应的数据格式要求是jsonURI规范uri是统一资源标识符路径只能有名词,要求和数据库中的表名对应建议名词使用复数在URI中使用小写字母不要在末尾使用/使用连字符(-),每个公司可能要求不一样http://api.example.com/school/students http://api.example.com/school/students/{100}请求方式GET用来获取资源POST用来创建资源PUT用来修改资源DELETE用来删除资源GET http://api.example.com/school/students 查询所有学生信息 GET http://api.example.com/school/students/{id} 查询指定id的学生信息 POST http://api.example.com/school/students 添加学生信息 PUT http://api.example.com/school/students/{id} 修改指定id的学生信息 DELETE http://api.example.com/school/students/{id} 删除指定id的学生信息过滤条件如果记录数量很多,服务器不可能返回所有信息给用户,API需要提供参数,来过滤返回的结果?limit=10 指定返回记录的数量 ?offset=10 指定返回记录的开始位置 ?page=2&pageCount=100 指定第几页,以及每页的记录数 ?orderby=name&order=asc 指定返回结果按照哪一个属性排序,以及排序顺序 ?age>=20指定筛选条件其他说明有的公司的请求方式还是使用GET和POST请求,然后通过路径来区分目的/stus/query 查询操作 /stus/delete/{sid} 根据id删除返回结果返回数据的格式必须要使用json格式,返回结果建议包含三个部分的信息,状态码,提示信息和真实数据状态码和信息常见的HTTP状态码json介绍JSON(JavaScript Object Notation, JS对象标记)是一种轻量级的数据交换格式,目前使用特别广泛。json的数据格式分成json Object(json对象)和json array(json数组)json Object数据格式:外层加大括号内层是k:v的形式(属性名:属性值的格式),属性之间用逗号间隔属性名必须是字符串比如{"id":100,"name":"tom"}数值支持的数据类型:数值,字符串,布尔,null,json对象,json数组学生信息: { "id":100, "name":"tom", "cls":{ "id":1, "name":"计算机一班" } }json array数据格式外层中括号数组元素的数据类型:数值,字符串,布尔,null,json对象,json数组所有班级信息的数组: ["计算机一班","计算机二班","软件一班", "软件二班"]在线json校验postman我们知道浏览器它只能发送GET请求,这样的话,其他的请求方式,我们是没法测试的。接下来就介绍前后端分离开发,经常用到的工具postman安装官网地址:https://www.postman.com/downloads/我们可以选择把exe文件放在指定文件家,然后双击然后可以去登录,或者跳过登录使用点击send(代码实现在后面介绍)Spring MVC的restful实现查询所有班级信息restful风格要求返回的字符串是json格式@ResponseBody可以把Java对象转化为json对象,并返回给客户端我们写一个小demo测试一下(数据是从数据库中查询出来的)实体类(班级表对应的实体类)@Data @NoArgsConstructor @AllArgsConstructor public class Cls { private Integer id; private String name; } Controller(可以直接用@RestController)@Controller @ResponseBody public class ClsController { @Autowired private ClsService clsService; /** * 查询所有班级信息 */ @GetMapping("/school/cls") public List<Cls> queryAllCls() { try { List<Cls> clsList = clsService.queryAllCls(); return clsList; } catch (Exception e) { e.printStackTrace(); } return null; } } Servicepublic interface ClsService { /** * 查询所有班级信息 * @return */ List<Cls> queryAllCls() throws Exception; } Service实现类@Service public class ClsServiceImpl implements ClsService { @Autowired private ClsMapper clsMapper; @Override public List<Cls> queryAllCls() throws Exception { return clsMapper.queryAllCls(); } } Mapper@Mapper public interface ClsMapper { /** * 查询所有班级信息 */ @Select("select * from cls") List<Cls> queryAllCls() throws Exception; }下面这张图片显示的数据和上面图片的不一样,是因为后来我把格式进行修改了,也就是对代码进行了修改,后面会进行说明单元测试@SpringBootTest class ClsMapperTest { @Autowired private ClsMapper clsMapper; @Test public void testQueryAllCls() throws Exception { List<Cls> cls = clsMapper.queryAllCls(); for (Cls c:cls){ System.out.println(c); } } } 输出内容: Cls(id=1, name=计科1班) Cls(id=2, name=计科2班) Cls(id=3, name=软工1班) Cls(id=4, name=软工2班)查询指定班级信息@PathVariable:把请求路径的变量和形参名进行绑定Controller@RestController public class ClsController { @Autowired private ClsService clsService; /** * 查询所有班级信息 */ @GetMapping("/school/cls") public List<Cls> queryAllCls() { try { List<Cls> clsList = clsService.queryAllCls(); return clsList; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 查询指定编号的班级信息 */ @GetMapping("/school/cls/{id}") public Cls queryClsById(@PathVariable("id") Integer id){ Cls cls= null; try { cls = clsService.queryClsById(id); } catch (Exception e) { e.printStackTrace(); } return cls; } } Servicepublic interface ClsService { /** * 查询所有班级信息 * @return */ List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return * @throws Exception */ Cls queryClsById(Integer id) throws Exception; } Service实现类@Service public class ClsServiceImpl implements ClsService { @Autowired private ClsMapper clsMapper; @Override public List<Cls> queryAllCls() throws Exception { return clsMapper.queryAllCls(); } @Override public Cls queryClsById(Integer id) throws Exception { return clsMapper.queryClsById(id); } } Mapper@Mapper public interface ClsMapper { /** * 查询所有班级信息 */ @Select("select * from cls") List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return */ @Select("select * from cls where id=#{id}") Cls queryClsById(@Param("id") Integer id); } 关于格式的说明:::info接口返回类型:json格式------code:状态码------message:状态信息------data:真实数据::: 我们如果想要达到这样的效果的话,有两种方法,第一种就是我们写一个实体类,第二种方法就是通过map来存数据通过定义实体类通过map根据id删除班级信息/** * 根据id来删除班级信息 * @param id * @return */ @RequestMapping(value = "/school/cls/{id}" ,method = RequestMethod.DELETE) public ResultInfo deleteById(@PathVariable("id") Integer id){ ResultInfo info = new ResultInfo(); try { clsService.deleteById(id); info.setOk(); } catch (Exception e) { e.printStackTrace(); info.setError(); } return info; }插入数据默认是接收key-value格式的数据,如果前端传入的是json格式的数据,那么数据就会接收失败,如果我们希望前端传送的数据是json格式的数据,那么,我们就需要在Controller中,在参数前面加上@RequestBody注解(把请求体中的json格式转换为对应Java实体类格式)Controller /** * 添加班级 前端需要传参数进来 * @return */ @PostMapping("/school/cls") public ResultInfo addCls(Cls cls){ ResultInfo info = new ResultInfo(); try { clsService.insert(cls); info.setOk(); info.setData(cls); } catch (Exception e) { e.printStackTrace(); info.setError(); } return info; }Servicepublic interface ClsService { /** * 查询所有班级信息 * @return */ List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return * @throws Exception */ Cls queryClsById(Integer id) throws Exception; void deleteById(Integer id) throws Exception; void insert(Cls cls) throws Exception; } Service实现类@Service public class ClsServiceImpl implements ClsService { @Autowired private ClsMapper clsMapper; @Override public List<Cls> queryAllCls() throws Exception { return clsMapper.queryAllCls(); } @Override public Cls queryClsById(Integer id) throws Exception { return clsMapper.queryClsById(id); } @Override public void deleteById(Integer id) throws Exception { clsMapper.deleteById(id); } //添加数据 @Override public void insert(Cls cls) throws Exception { clsMapper.insert(cls); } } Mapper@Mapper public interface ClsMapper { /** * 查询所有班级信息 */ @Select("select * from cls") List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return */ @Select("select * from cls where id=#{id}") Cls queryClsById(@Param("id") Integer id); /** * 根据id删除数据 * @param id */ @Delete("delete from cls where id=#{id}") void deleteById(@Param("id") Integer id); /** * 插入班级 * @param cls */ @Insert("insert into cls values(default,#{name})") @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id") void insert(Cls cls); } 通过数据库查询的前后两次对比可以很清楚的发现,插入数据成功了根据班级编号修改数据Controller @PutMapping("/school/cls/{id}") public ResultInfo updateById(@PathVariable("id") Integer id,String name){ ResultInfo info = new ResultInfo(); try { clsService.updateById(id,name); info.setOk(); info.setData(new Cls(id,name)); } catch (Exception e) { e.printStackTrace(); info.setError(); } return info; }Servicepublic interface ClsService { /** * 查询所有班级信息 * @return */ List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return * @throws Exception */ Cls queryClsById(Integer id) throws Exception; void deleteById(Integer id) throws Exception; void insert(Cls cls) throws Exception; void updateById(Integer id, String name) throws Exception; } Service实现类@Service public class ClsServiceImpl implements ClsService { @Autowired private ClsMapper clsMapper; @Override public List<Cls> queryAllCls() throws Exception { return clsMapper.queryAllCls(); } @Override public Cls queryClsById(Integer id) throws Exception { return clsMapper.queryClsById(id); } @Override public void deleteById(Integer id) throws Exception { clsMapper.deleteById(id); } @Override public void insert(Cls cls) throws Exception { clsMapper.insert(cls); } @Override public void updateById(Integer id, String name) throws Exception { clsMapper.updateById(id,name); } } Mapper@Mapper public interface ClsMapper { /** * 查询所有班级信息 */ @Select("select * from cls") List<Cls> queryAllCls() throws Exception; /** * 查询指定编号的班级信息 * @param id * @return */ @Select("select * from cls where id=#{id}") Cls queryClsById(@Param("id") Integer id); /** * 根据id删除数据 * @param id */ @Delete("delete from cls where id=#{id}") void deleteById(@Param("id") Integer id); /** * 插入班级 * @param cls */ @Insert("insert into cls values(default,#{name})") @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id") void insert(Cls cls); /** * 根据id修改数据 * @param id * @param name */ @Update("update cls set name=#{name} where id=#{id} ") void updateById(@Param("id") Integer id, @Param("name") String name); } 通过查询数据库中的数据,也可以发现数据改变了
验证码的作用验证码的作用:可以有效防止其他人对某一个特定的注册用户用特定的程序暴力破解方式进行不断的登录尝试我们其实很经常看到,登录一些网站其实是需要验证码的,比如牛客,QQ等。使用验证码是现在很多网站通行的一种方式,这个问题是由计算机生成并且评判的,但是必须只有人类才能解答,因为计算机无法解答验证码的问题,所以回答出问题的用户就可以被认为是人类。验证码一般用来防止批量注册。案例要求验证码本质:后端程序随机验证码图片的创建:---java api手动创建图片---JavaScript 前端创建图片验证码的刷新---添加JavaScript的点击事件,重新请求验证码图片前端页面准备因为涉及到jQuery,所以需要在resources/static创建目录,存放jQuery库准备login.html页面<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> <!--引入jQuery --> <script type="text/javascript" src="/js/jquery-1.8.3.min.js"></script> </head> <body> <h2>用户登录</h2> <form action="/login" method="post"> 用户名:<input type="text" name="username"><br><br> 密码 :<input type="password" name="password"><br><br> 验证码:<input id="identify-input" type="text" name="identifyCode"> <img id="identify-img" src="/identifyImage"><br><br> <input type="submit" value="登录"> </form> <!--绑定点击事件 --> <script> $("#identify-img").on('click',function (){ // 点击验证码那个图片的时候,我们输入的验证码那个框就会清空 $('#identify-input').val('') //而且我们点击验证码的时候,希望它可以改变验证码内容,其实是通过发送新请求来改变验证码内容 $('#identify-img').attr('src','/identifyImage?'+Math.random()) }) </script> </body> </html>随机验证码工具类public class IdentifyCodeUtils { //设置图片宽 private int width = 95; //设置图片高度 private int height = 25; //设置干扰线数量 private int lineSize = 40; //随机产生数字和字母组合的字符串 private String randString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private Random random = new Random(); /** * 获得字体 */ private Font getFont() { return new Font("Fixedsys", Font.CENTER_BASELINE, 18); } /** * 获得颜色 */ private Color getRandColor(int fc, int bc) { if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc - 16); int g = fc + random.nextInt(bc - fc - 14); int b = fc + random.nextInt(bc - fc - 18); return new Color(r, g, b); } /** * 获取验证码 * * @return */ public String getIdentifyCode() { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 4; i++) { char c = randString.charAt(random.nextInt(randString.length())); buffer.append(c); } return buffer.toString(); } /** * 生成随机图片 * * @param identifyCode * @return */ public BufferedImage getIdentifyImage(String identifyCode) { //BufferedImage类是具有缓冲区的Image类,Image类是用来描述图像信息的类 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); //产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作 Graphics graphics = image.getGraphics(); //图片大小 graphics.fillRect(0, 0, width, height); //字体大小 graphics.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18)); //字体颜色 graphics.setColor(getRandColor(110, 133)); //绘制干扰线 for (int i = 0; i <= lineSize; i++) { drawLine(graphics); } //绘制随机字符 drawString(graphics, identifyCode); graphics.dispose(); return image; } /** * 绘制字符串 */ private void drawString(Graphics g, String identifyCode) { for (int i = 0; i < identifyCode.length(); i++) { g.setFont(getFont()); g.setColor(new Color(random.nextInt(101), random.nextInt(111), random .nextInt(121))); g.translate(random.nextInt(3), random.nextInt(3)); g.drawString(String.valueOf(identifyCode.charAt(i)), 13 * i + 20, 18); } } /** * 响应验证码图片 * * @param identifyImg * @param response */ public void responseIdentifyImg(BufferedImage identifyImg, HttpServletResponse response) { //设置响应类型,告诉浏览器输出的内容是图片 response.setContentType("image/jpeg"); //设置响应头信息,告诉浏览器不用缓冲此内容 response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expire", 0); try { //把内存中的图片通过流动形式输出到客户端 ImageIO.write(identifyImg, "JPEG", response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } /** * 绘制干扰线 */ private void drawLine(Graphics graphics) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(13); int yl = random.nextInt(15); graphics.drawLine(x, y, x + xl, y + yl); } } 后端控制器@Controller public class UserController { @RequestMapping("/loginShow") public String loginShow(){ return "login.html"; } @PostMapping("/login") public String login(String username,String password,String identifyCode,HttpSession session){ System.out.println("用户名:"+username); System.out.println("密码:"+password); System.out.println("验证码:"+identifyCode); //从session中取出验证码 String sessionCode = (String)session.getAttribute("identifyFyCode"); if (identifyCode.equalsIgnoreCase(sessionCode)){ System.out.println("验证码正确"); //进行登录判断的逻辑大家自己写,这里就不演示了 }else{ System.out.println("验证码错误"); //重定向到登录画面 return "redirect:/loginShow"; } return ""; } /** * 给前端返回一个验证码图片 * @return */ @RequestMapping("/identifyImage") public void identifyImage(HttpServletResponse response, HttpSession session){ //创建随机验证码 IdentifyCodeUtils utils = new IdentifyCodeUtils(); String identifyCode = utils.getIdentifyCode(); //session存入验证码 session.setAttribute("identifyCode", identifyCode); //根据验证码创建图片 BufferedImage identifyImage = utils.getIdentifyImage(identifyCode); //回传给前端 utils.responseIdentifyImg(identifyImage,response); } } 测试当我们点击验证码这个图片的时候,它就会生成新验证码并且如果我们在输入框中如果有写验证码的话,当我们点击验证码图片,它就会把输入框内容清空(大家自己测试)
Thymeleaf基本介绍Spring Boot 官方推荐使用 Thymeleaf 作为其模板引擎。SpringBoot 为 Thymeleaf 提供了一系列默认配置,并且为Thymeleaf提供了视图解析器。项目中一但导入了 Thymeleaf 的依赖,相对应的自动配置 (ThymeleafAutoConfiguration) 就会自动生效,因此 Thymeleaf 可以与 Spring Boot 完美整合 。Thymeleaf模板引擎可以和html标签完美结合,便于后端渲染数据。Thymeleaf支持静态效果和动态效果,在没有动态数据的时候,会展示静态效果模板引擎是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档就是将模板文件和数据通过模板引擎生成一个HTML代码**常见的模板引擎有:jsp、freemarker、velocity、thymeleafThymeleaf默认写的位置是在templates这个目录下面Thymeleaf官网:https://www.thymeleaf.org/<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>Thymeleaf默认的视图路径是:/ resources/templates,在这个目录下面创建html并引入thymeleaf<html lang="en" xmlns:th="http://www.thymleaf.org">xmlns:th="http://www.thymleaf.org">基本语法${域属性名}:获得request域中的域属性值并显示${session.域属性名}: 获得session域中的域属性值并显示< p th:text="${name}">aaa</p>如果取得到数据的话,就会渲染成动态画面,否则就渲染成静态画面(只显示学生管理系统只显示学生管理系统这几个字)th:text文本替换<span th:text="${user.name}">Tom</span>th:if和th:unless文本替换使用th:if和th:unless属性进行条件判断,th:unlessth:unless刚好相反,只有表达式条件不成立才会显示内容<h2 th:if="${age>=18}">成年</h2> <h2 th:unless="${age>=18}">未成年</h2>th:each foreach循环<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .tb-stus{ width: 900px; margin: 0 auto; border: black 1px solid; border-collapse: collapse; } .tb-stus th,td{ padding: 10px; text-align: center; border:1px solid black; } </style> </head> <body> <h2 align="center">学生管理系统</h2> <table class="tb-stus"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>班级</th> <th>生日</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="stu:${stuList}"> <td>1</td> <td th:text="${stu.name}">aa</td> <td>22</td> <td>男</td> <td>计科1班</td> <td>2022-2-3</td> <td> <a href="#">删除</a> </td> </tr> </tbody> </table> </body> </html>IDEA使用Thymeleaf输入 th: 没有智能提示的全新解决办法th:href和@{}链接表达式<!--http://localhost:8080/stu/10 --> <a th:href="${'/stus/'+ stu.id}">编辑学生1</a> <!--http://localhost:8080/stu?id=10 --> <a th:href="@{/stus(id=${stu.id})}">编辑学生2</a> <!--http://localhost:8080/stu?id=10&name=abc --> <a th:href="@{/stus(id=${stu.id},name=${stu.name})}">编辑学生3</a>th:switch和th:case<div th:switch="${stu.role}"> <h2 th:case="banzhang">班长</h2> <h2 th:case="tuanzhishu">团支书</h2> <h2 th:case="${data}">学委</h2> <h2 th:case="*">其他</h2> </div> thymeleaf默认给变量名+Stat的状态如果没有显示设置状态变量,thymeleaf会默认给一个变量名+Stat的状态<tr th:each="stu: ${stus}"> <td th:text="${stuStat.index}"></td> <td th:text="${ stu.name}"></td> <td th:text="${ stu.age}"></td> </tr>th:id、th:value、th:checked等(和form表单相关)th:object可以定义对象属性*{}可以和th:object配合使用,可以取出对象中的属性dates.format()可以用来格式化日期格式 <form th:object="${stu}"> 编号:<input type="text" name="id" th:value="*{id}"><br> 姓名:<input type="text" name="name" th:value="*{name}"><br> 年龄:<input type="text" name="age" th:value="*{age}"><br> 性别:<input type="radio" name="gender" value="true" th:checked="*{gender}">男<br> 性别:<input type="radio" name="gender" value="true" th:checked="*{not gender}">女<br> 生日:<input type="text" name="birth" th:value="*{#dates.format(birth,'yyyy-MM-dd')}"><br> <input type="submit" value="编辑"> </form>整合Thymeleaf基本配置 创建项目的时候,记得在模板引擎中勾选Thymeleaf在pom.xml中把MySQL驱动的作用域删除然后我们这里使用druid连接池,所以需要在pom文件导入相关依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency>然后我们需要在全局配置文件application.properties中进行相关配置# 指定Mybatis的Mapper接口的xml映射文件的路径 mybatis.mapper-locations=classpath:mapper/*xml # MySQL数据库驱动 #这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源名称 spring.datasource.name=com.alibaba.druid.pool.DruidDataSource # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/school?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull # 数据库用户名和密码 spring.datasource.username=root spring.datasource.password=a87684009. # 设置日志级别 logging.level.com.zyh.springboot=debug # 开启mybatis驼峰命名规则自动转换功能 mybatis.configuration.map-underscore-to-camel-case=true 数据库准备准备好数据库中表所对应的实体类,以及三层结构@Data public class Stu { private Integer id; private String name; private Integer age; private Boolean gender; private Integer cid; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; }三层架构Mapper@Mapper public interface StuMapper { /** * 查询所有学生信息 * @return * @throws Exception */ @Select("select * from stu") List<Stu> queryAllStu() throws Exception; }Servicepublic interface StuService { /** * 查询所有学生信息 * @return */ List<Stu> queryAllStu() throws Exception; }Service的实现类@Service public class StuServiceImpl implements StuService { @Autowired private StuMapper stuMapper; @Override public List<Stu> queryAllStu() throws Exception { return stuMapper.queryAllStu(); } } thymeleaf<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>学生管理系统</h2> <h2 th:text="${name}">aaaa</h2> </body> </html>Controller@Controller @RequestMapping("/stu") public class StuController { @Autowired private StuService stuService; /** * 显示学生管理系统的画面 * @return */ @RequestMapping("/stusUi") public String stusUi(){ return "stus"; } }然后我们先准备好页面<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .tb-stus{ width: 900px; margin: 0 auto; border: black 1px solid; border-collapse: collapse; } .tb-stus th,td{ padding: 10px; text-align: center; border:1px solid black; } </style> </head> <body> <h2 align="center">学生管理系统</h2> <table class="tb-stus"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>班级</th> <th>生日</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="stu,status: ${stuList}"> <td th:text="${status.index+1}">1</td> <td th:text="${stu.name}">aa</td> <td th:text="${stu.age}">22</td> <!-- gender的属性值为1表示性别为男--> <td th:if="${stu.gender}">男</td> <td th:unless="${stu.gender}">女</td> <td th:text="${stu.cid}">计科1班</td> <td th:text="${#dates.format(stu.birth,'yyyy-MM-dd')}">2022-2-3</td> <td> <!--http://localhost:8080/stu/delete?id=10--> <a th:href="@{/stu/delete(id=${stu.id})}">删除</a> </td> </tr> </tbody> </table> </body> </html>当我们点击删除的时候,后端要根据前端传过来的id来从数据库中删除对应的数据。这里我们先按照我们之前学的时候,熟悉的方法来完成,到后面的时候,会详细讲前后端分离开发删除操作Controller(之前的方法这里没有粘贴出来,不然代码太多了)@Controller @RequestMapping("/stu") public class StuController { @Autowired private StuService stuService; /**根据id删除数据 * http://localhost:8080/stu/delete?id=10 * @return */ @RequestMapping("/delete") public String deleteById(@RequestParam("id") Integer id){ try { stuService.deleteByid(id); } catch (Exception e) { e.printStackTrace(); } System.out.println(id); return "redirect:/stu/stusUi"; } } Servicepublic interface StuService { /** * 查询所有学生信息 * @return */ List<Stu> queryAllStu() throws Exception; void deleteByid(Integer id); } Service实现类@Service public class StuServiceImpl implements StuService { @Autowired private StuMapper stuMapper; @Override public List<Stu> queryAllStu() throws Exception { return stuMapper.queryAllStu(); } /** * 根据id删除数据 * @param id */ @Override public void deleteByid(Integer id) throws Exception { stuMapper.deleteById(id); } } Mapper@Mapper public interface StuMapper { /** * 查询所有学生信息 * @return * @throws Exception */ @Select("select * from stu") List<Stu> queryAllStu() throws Exception; @Delete("delete from stu where id=#{id}") void deleteById( Integer id); } 把编号为8的数据删除编辑操作页面stus.html<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .tb-stus{ width: 900px; margin: 0 auto; border: black 1px solid; border-collapse: collapse; } .tb-stus th,td{ padding: 10px; text-align: center; border:1px solid black; } </style> </head> <body> <h2 align="center">学生管理系统</h2> <table class="tb-stus"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>班级</th> <th>生日</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="stu,status: ${stuList}"> <td th:text="${stu.id}">1</td> <td th:text="${stu.name}">aa</td> <td th:text="${stu.age}">22</td> <!-- gender的属性值为1表示性别为男--> <td th:if="${stu.gender}">男</td> <td th:unless="${stu.gender}">女</td> <td th:text="${stu.cid}">计科1班</td> <td th:text="${#dates.format(stu.birth,'yyyy-MM-dd')}">2022-2-3</td> <td> <!-- localhost:8080/stu/delete/8--> <!-- <a th:href="${'/stu/delete/'+stu.id}">删除</a>--> <!--http://localhost:8080/stu/delete?id=10--> <a th:href="@{/stu/delete(id=${stu.id})}">删除</a> <a th:href="@{/stu/edit(id=${stu.id})}">编辑</a> </td> </tr> </tbody> </table> </body> </html>页面 stu-edit.html<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>编辑画面</title> </head> <body> <h2 >编辑学生信息</h2> <form action="" method="post" th:object="${stu}"> 学号:<input type="text" name="id" th:value="*{id}" ><br><br> 姓名:<input type="text" name="name" th:value="*{name}"><br><br> 年龄:<input type="text" name="age" th:value="*{age}"><br><br> 性别:<input type="radio" name="gender" th:checked="*{gender}" >男 <input type="radio" name="gender" th:checked="*{!gender}" >女<br><br> 班级:<input type="text" name="cid" th:value="*{cid}"><br><br> 生日:<input type="text" name="birth" th:value="*{#dates.format(birth,'yyyy-MM-dd')}"><br><br> <input type="submit" value="编辑"> </form> </body> </html>Controller/** * 根据id来修改数据 * 我们首先得先根据id把数据查询出来,然后把数据展示出来 * 用户再进行编辑,用户编辑完并且提交以后,跳转到学生管理系统画面,展示所有数据 * @return */ @RequestMapping("/edit") public String edit(@RequestParam("id") Integer id,Model model){ System.out.println("id"+id); try { Stu stu=stuService.queryById(id); model.addAttribute("stu",stu); } catch (Exception e) { e.printStackTrace(); } return "stu-edit"; } Servicepublic interface StuService { /** * 查询所有学生信息 * @return */ List<Stu> queryAllStu() throws Exception; /** * 根据id来删除学生信息 * @param id * @throws Exception */ void deleteByid(Integer id) throws Exception; /** * 根据id来查询对应学生信息 * @param id * @return * @throws Exception */ Stu queryById(Integer id) throws Exception; } Service实现类@Service public class StuServiceImpl implements StuService { @Autowired private StuMapper stuMapper; @Override public List<Stu> queryAllStu() throws Exception { return stuMapper.queryAllStu(); } /** * 根据id删除数据 * @param id */ @Override public void deleteByid(Integer id) throws Exception { stuMapper.deleteById(id); } @Override public Stu queryById(Integer id) throws Exception { return stuMapper.queryById(id); } } Mapper@Mapper public interface StuMapper { /** * 查询所有学生信息 * @return * @throws Exception */ @Select("select * from stu") List<Stu> queryAllStu() throws Exception; @Delete("delete from stu where id=#{id}") void deleteById( Integer id); @Select("select * from stu where id=#{id}") Stu queryById(Integer id) throws Exception; }比如在序号为4中,点击编辑用户登录登录页面<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>用户登录</h2> <form action="/login" method="post"> 账号:<input type="text" name="username"><br><br> 密码:<input type="password" name="password"><br><br> <input type="submit" value="登录"> </form> </body> </html>因为需要判断用户是否存在,这是从数据库进行查询的,所以要准备对应的管理员表# 创建管理员表 CREATE TABLE admin( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20), `password` VARCHAR(20) ); INSERT INTO admin VALUES (DEFAULT,'aaa',111), (DEFAULT,'bbb',222), (DEFAULT,'ccc',333); # 查询测试 SELECT * FROM admin; 准备对应的实体类@Data public class Admin { private String username; private String password; } Controller@Controller @SessionAttributes(names = {"admin"}) public class AdminController { @Autowired private AdminService adminService; /** * 显示登录页面 * @return */ @RequestMapping(value = "/loginUi") public String loginUi(){ return "login"; } @RequestMapping(value = "/login",method = RequestMethod.POST) public String login(String username, String password, Model model){ try { Admin admin = adminService.login(username, password); //用户名存在说明登录成功 if (admin!=null){ //存放到session域中 model.addAttribute("admin",admin); return "redirect:/stu/stusUi"; } } catch (Exception e) { e.printStackTrace(); } return "redirect:/loginUi"; } } Servicepublic interface AdminService { Admin login(String username,String password) throws Exception; }Service对应的实现类@Service public class AdminServiceImpl implements AdminService { @Autowired private AdminMapper adminMapper; @Override public Admin login(String username, String password) throws Exception { return adminMapper.queryByUsernameAndPassword(username,password); } }Mapper@Mapper public interface AdminMapper { @Select("select * from admin where username=#{username} and password=#{password}") Admin queryByUsernameAndPassword(@Param("username") String username, @Param("password") String password); } <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .tb-stus{ width: 900px; margin: 0 auto; border: black 1px solid; border-collapse: collapse; } .tb-stus th,td{ padding: 10px; text-align: center; border:1px solid black; } </style> </head> <body> <h2 align="center">学生管理系统</h2> <h2 th:if="${session.admin!=null}" th:text="${session.admin.username}">用户名</h2> <a th:unless="${session.admin!=null}" href="/loginUi">登录</a> <a th:if="${session.admin!=null}" href="/logout">注销用户</a> <table class="tb-stus"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>班级</th> <th>生日</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="stu,status: ${stuList}"> <td th:text="${stu.id}">1</td> <td th:text="${stu.name}">aa</td> <td th:text="${stu.age}">22</td> <!-- gender的属性值为1表示性别为男--> <td th:if="${stu.gender}">男</td> <td th:unless="${stu.gender}">女</td> <td th:text="${stu.cid}">计科1班</td> <td th:text="${#dates.format(stu.birth,'yyyy-MM-dd')}">2022-2-3</td> <td> <!-- localhost:8080/stu/delete/8--> <!-- <a th:href="${'/stu/delete/'+stu.id}">删除</a>--> <!--http://localhost:8080/stu/delete?id=10--> <a th:href="@{/stu/delete(id=${stu.id})}">删除</a> <a th:href="@{/stu/edit(id=${stu.id})}">编辑</a> </td> </tr> </tbody> </table> </body> </html>用户注销注销的话,我们把session域中的用户对象取消,然后这个时候就得重新登录,应该要跳转到登录画面@RequestMapping("/logout") public String logout(HttpSession session){ session.removeAttribute("admin"); return "redirect:/loginUi"; }点击注销用户
1.静态资源和动态资源的区别静态资源:可以理解成为是固定不变的,比如前端固定的页面(html,css,js,图片等)静态资源不需要查询数据库来动态获取数据并展示,不需要程序处理,直接就可以展示了 如果用户请求的是静态资源,那么服务器会直接将静态资源发送给浏览器,浏览器中内置了静态资源的解析引擎,可以解析静态资源(浏览器只能解析静态资源!)。因为每个浏览器的解析引擎可能不一样,所以相同的网页可能在不同浏览器展示有所区别。动态资源:需要访问数据库,从数据库中读取数据并展示如果数据发生改变,我们不需要改变页面由于要访问数据库,涉及到和数据库等连接操作,所以访问速度比较慢如果用户请求的是动态资源,那么服务器会执行动态资源,转换为静态资源,再发送给浏览器(浏览器无法解析动态资源!)2.Spring Boot默认静态资源目录Spring Boot应用启动过程中,会读取加载一个静态资源文件加载路径这个属性# 默认值为 spring.resources.static-locations= classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/这一个属性的默认值代表静态资源扫描目录Spring Boot默认情况下,有五个位置可以存放静态资源比如:我们分别建立了public、resources、static目录,在目录下建立html静态页面。项目启动后,我们都可以直接访问这些页面:这里要注意优先级问题:根据前后关系确定优先级,也就是说如果classpath:/resources/目录和classpath:/public/都有一个test.html,那么根据默认的优先级,会去访问classpath:/resources/下的资源。3.欢迎页欢迎页是静态资源文件夹下的index.html文件4.静态资源加载顺序优先级springboot中的静态资源加载顺序优先级
1.基本工程1.1基本工程创建一个Spring Boot工程,添加Web依赖,Mybatis依赖以及MySQL驱动依赖创建成功以后,删除MySQL运行域scope1.2修改mybatis配置信息在application.properties中修改配置信息# 指定Mybatis的Mapper接口的xml映射文件的路径 mybatis.mapper-locations=classpath:mapper/*.xml # MySQL数据库驱动 #这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源名称 spring.datasource.name=defaultDataSource # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/bjpowernode?serverTimezone=UTC # 数据库用户名和密码 spring.datasource.username=root spring.datasource.password=123456 # 设置日志级别 logging.level.com.zyh.springboot=debug数据库(大家可以用以前创建的数据库来进行测试)1.3创建实体类及其接口@Mapper注解:作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类1.4单元测试我们可以选中mapper接口,使用快捷键alt+/,就可以创建junit单元测试2022-07-31 12:35:13.453 INFO 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Starting DeptMapperTest using Java 1.8.0_131 on LAPTOP-59BP4RH1 with PID 10228 (started by 17614 in D:\springboot\springboot09_base) 2022-07-31 12:35:13.453 DEBUG 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Running with Spring Boot v2.7.2, Spring v5.3.22 2022-07-31 12:35:13.453 INFO 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : No active profile set, falling back to 1 default profile: "default" 2022-07-31 12:35:15.183 INFO 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Started DeptMapperTest in 2.055 seconds (JVM running for 3.177) 2022-07-31 12:35:15.390 INFO 10228 --- [ main] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Starting... 2022-07-31 12:35:18.720 INFO 10228 --- [ main] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Start completed. 2022-07-31 12:35:18.720 DEBUG 10228 --- [ main] c.z.s.mapper.DeptMapper.queryAllDept : ==> Preparing: select * from dept 2022-07-31 12:35:18.751 DEBUG 10228 --- [ main] c.z.s.mapper.DeptMapper.queryAllDept : ==> Parameters: 2022-07-31 12:35:18.782 DEBUG 10228 --- [ main] c.z.s.mapper.DeptMapper.queryAllDept : <== Total: 3 2022-07-31 12:35:18.782 DEBUG 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Dept(deptno=10, dname=媒体部, loc=上海) 2022-07-31 12:35:18.782 DEBUG 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Dept(deptno=20, dname=研发部, loc=上海) 2022-07-31 12:35:18.782 DEBUG 10228 --- [ main] c.zyh.springboot.mapper.DeptMapperTest : Dept(deptno=30, dname=人事部, loc=杭州) 2022-07-31 12:35:18.798 INFO 10228 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Shutdown initiated... 2022-07-31 12:35:18.813 INFO 10228 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Shutdown completed.2.Mapper接口扫描@MapperScan作用:指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类添加位置:是在Springboot启动类上面添加。3.注解式开发3.1环境准备我们先准备数据库和表# 创建数据库 CREATE DATABASE shop; # 使用数据库 USE shop; # 创建商品类别表: type # id 主键自增 # name 类别名称 # 创建表 CREATE TABLE `type`( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(20) ); DESC TABLE `type`; # 创建商品表 product # id 主键自增 # name 商品名称 # price 要求是非负的浮点数 # sell 布尔类型 sell=0表示售卖,sell=1表示下架 #c_date 开始售卖时间,精确到年月日即可 # tid商品类型id,外键,管理type表的id CREATE TABLE product( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), price DOUBLE UNSIGNED, sell TINYINT UNSIGNED, c_date DATE, tid INT, FOREIGN KEY(tid) REFERENCES `type`(id) ); # 插入数据 INSERT INTO `type` VALUES (DEFAULT,'手机'), (DEFAULT,'电脑'), (DEFAULT,'平板'); SELECT * FROM `type`; INSERT INTO product VALUES (DEFAULT,"华为手机",5000.0,0,'2022-7-31',1), (DEFAULT,"苹果手机",9000.0,0,'2012-5-26',1), (DEFAULT,"华为电脑",8000.0,0,'2010-5-26',2), (DEFAULT,"苹果电脑",12000.0,0,'2012-3-10',2), (DEFAULT,"华为平板",2000.0,0,'2019-4-8',3), (DEFAULT,"苹果平板",6000.0,0,'2016-3-2',3);然后创建spring boot项目,修改相关配置# 指定Mybatis的Mapper接口的xml映射文件的路径 mybatis.mapper-locations=classpath:mapper/*.xml # MySQL数据库驱动 #这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源名称 spring.datasource.name=defaultDataSource # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull # 数据库用户名和密码 spring.datasource.username=root spring.datasource.password=123456 # 设置日志级别 logging.level.com.zyh.springboot=debug # 开启mybatis驼峰命名规则自动转换功能 mybatis.configuration.map-underscore-to-camel-case=true 3.2.mybatis的单表操作查询所有商品信息根据商品编号查询指定的商品信息根据商品名称模糊查询商品根据商品价格,查询指定范围的商品插入一条商品数据根据编号删除指定商品根据商品编号,修改商品价格3.3查询所有商品信息可能遇到的问题:org.springframework.dao.TransientDataAccessResourceException: Error attempting to get column 'c_date' from result set.解决方法如下:3.4根据商品编号查询指定的商品信息3.5根据商品名称模糊查询商品这里面涉及到的知识点是mybatis的内容,大家忘记的可以去复习复习,Mybatis特殊SQL的执行3.6根据商品价格,查询指定范围的商品我们也可以通过map来传参3.7插入一条商品数据3.7.1代码演示我们发现此时打印的信息中,主键为空,但是事实上,数据插入在数据库中是有主键的3.7.2 获取自增的主键3.8根据编号删除指定商品// 根据编号删除指定商品 @Delete("delete from product where id=#{id}") void deleteById(Integer id); @Test public void testDeleteById(){ productMapper.deleteById(8); }3.9根据商品编号,修改商品价格// 根据商品编号,修改商品价格 @Update("update product set price=#{price} where id=#{id}") void updateById(@Param("id") Integer id,@Param("price") Double price); @Test public void testUpdateById(){ productMapper.updateById(8, 3000.0); } 4.映射文件开发4.1基本操作大家可以先把这个插件下载下来,可以帮助我们快速开发环境配置和上面的注解开发是一样的# 指定Mybatis的Mapper接口的xml映射文件的路径 mybatis.mapper-locations=classpath:mapper/*.xml # MySQL数据库驱动 #这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源名称 spring.datasource.name=defaultDataSource # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull # 数据库用户名和密码 spring.datasource.username=root spring.datasource.password=123456 # 设置日志级别 logging.level.com.zyh.springboot=debug # 开启mybatis驼峰命名规则自动转换功能 mybatis.configuration.map-underscore-to-camel-case=true 提供对应的实体类和mapper接口mapper<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.zyh.springboot.mapper.ProductMapper"> <select id="queryById" resultType="com.zyh.springboot.bean.Product"> select * from product where id=#{id} </select> </mapper>测试4.2查询所有商品信息以及商品类别的详细信息(多对一)因为涉及到多表查询,我们这个时候需要新增加一个类Type测试代码4.3查询所有商品类别及其商品信息(一对多)测试测试结果2022-08-01 09:34:53.982 INFO 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Starting TypeMapperTest using Java 1.8.0_131 on LAPTOP-59BP4RH1 with PID 30284 (started by 17614 in D:\springboot\springboot11_mybatis_xml) 2022-08-01 09:34:53.982 DEBUG 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Running with Spring Boot v2.7.2, Spring v5.3.22 2022-08-01 09:34:53.982 INFO 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : No active profile set, falling back to 1 default profile: "default" 2022-08-01 09:34:58.015 INFO 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Started TypeMapperTest in 4.63 seconds (JVM running for 7.326) 2022-08-01 09:34:58.388 INFO 30284 --- [ main] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Starting... 2022-08-01 09:35:02.138 INFO 30284 --- [ main] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Start completed. 2022-08-01 09:35:02.166 DEBUG 30284 --- [ main] c.z.s.m.T.queryAllTypeAndProduct : ==> Preparing: select * from type; 2022-08-01 09:35:02.249 DEBUG 30284 --- [ main] c.z.s.m.T.queryAllTypeAndProduct : ==> Parameters: 2022-08-01 09:35:02.370 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Preparing: select * from product where tid=? 2022-08-01 09:35:02.372 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Parameters: 1(Integer) 2022-08-01 09:35:02.396 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : <==== Total: 3 2022-08-01 09:35:02.414 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Preparing: select * from product where tid=? 2022-08-01 09:35:02.415 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Parameters: 2(Integer) 2022-08-01 09:35:02.419 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : <==== Total: 2 2022-08-01 09:35:02.420 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Preparing: select * from product where tid=? 2022-08-01 09:35:02.421 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : ====> Parameters: 3(Integer) 2022-08-01 09:35:02.425 DEBUG 30284 --- [ main] c.z.s.mapper.ProductMapper.queryByTid : <==== Total: 2 2022-08-01 09:35:02.426 DEBUG 30284 --- [ main] c.z.s.m.T.queryAllTypeAndProduct : <== Total: 3 2022-08-01 09:35:02.430 DEBUG 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Type(id=1, name=手机, products=[Product(id=1, name=华为手机, price=5000.0, sell=false, cDate=Sun Jul 31 00:00:00 CST 2022, tid=1, type=null), Product(id=2, name=苹果手机, price=9000.0, sell=false, cDate=Sat May 26 00:00:00 CST 2012, tid=1, type=null), Product(id=8, name=小米手机, price=3000.0, sell=true, cDate=Sun Jul 31 00:00:00 CST 2022, tid=1, type=null)]) 2022-08-01 09:35:02.430 DEBUG 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Type(id=2, name=电脑, products=[Product(id=3, name=华为电脑, price=8000.0, sell=false, cDate=Wed May 26 00:00:00 CST 2010, tid=2, type=null), Product(id=4, name=苹果电脑, price=12000.0, sell=false, cDate=Sat Mar 10 00:00:00 CST 2012, tid=2, type=null)]) 2022-08-01 09:35:02.431 DEBUG 30284 --- [ main] c.zyh.springboot.mapper.TypeMapperTest : Type(id=3, name=平板, products=[Product(id=5, name=华为平板, price=2000.0, sell=false, cDate=Mon Apr 08 00:00:00 CST 2019, tid=3, type=null), Product(id=6, name=苹果平板, price=6000.0, sell=false, cDate=Wed Mar 02 00:00:00 CST 2016, tid=3, type=null)]) 2022-08-01 09:35:02.480 INFO 30284 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Shutdown initiated... 2022-08-01 09:35:02.500 INFO 30284 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Shutdown completed.5.整合Druid5.1传统获取连接的问题连接底层是socket连接,比较耗费时间,Java程序在连接数据库的时候,它的最大连接数也是有要求的,如果连接过多的话,可能数据库就崩了。 而且,效率还低。—可以自己去测试一下时间所以,为了解决这个问题,我们引入了数据库连接池。5.2数据库连接池接下来我对上面做一下解释5.3整合步骤druid是alibaba的一个开源项目,是目前比较主流的数据库连接技术5.3.1引入pom依赖 <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>5.3.2添加druid配置# 数据源名称 spring.datasource.name=com.alibaba.druid.pool.DruidDataSource5.3.3完整配置# 指定Mybatis的Mapper接口的xml映射文件的路径 mybatis.mapper-locations=classpath:mapper/*.xml # MySQL数据库驱动 #这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源名称 spring.datasource.name=com.alibaba.druid.pool.DruidDataSource # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/bjpowernode?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull # 数据库用户名和密码 spring.datasource.username=root spring.datasource.password=123456 # 设置日志级别 logging.level.com.zyh.springboot=debug # 开启mybatis驼峰命名规则自动转换功能 mybatis.configuration.map-underscore-to-camel-case=true5.3.4代码测试6.事务管理6.1事务的概念事务我们可以理解成是在一系列的操作中,这些操作要么全部成功,要么全部不成功。最经典的就是银行的转帐问题,比如说张三要转账给李四,我们是不是得保证张三账户的转出和李四账户的转入操作要么都成功,要么都失败,如果一个成功一个失败,就会导致 转入金额和转出金额不一致 的情况,为了防止这种情况,需要使用事务来处理。6.2事务的四大特性(ACID)名称描述原子性(Atomicity)事务是一个不可分割的单位,事务中包含的操作要么全部执行,要么全部不执行一致性(Consistency)事务执行前后数据的完整性必须保持一致。例如:在转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配。一致性是通过原子性来保证的隔离性(Isolation)一个事务不会影响其他事务的运行,多个事务并发要互相隔离。持久性 (Durability)在事务完成以后,该事务对数据库所作的更改将持久地保存在数据库之中,并不会被回滚。6.3事务的隔离级别1. Isolation.DEFAULT: 以连接的数据库的事务隔离级别为主Isolation.READ_UNCOMMITTED: 读未提交, 可以读取到未提交的事务, 存在脏读3. Isolation.READ_COMMITTED: 读已提交, 只能读取到已经提交的事务, 解决了脏读, 但存在不可重复读4 Isolation.REPEATABLE_READ: 可重复读, 解决了不可重复读, 但存在幻读5. Isolation.SERIALIZABLE: 串行化, 可以解决所有并发问题, 但性能低6.4事务的传播机制(图解)Spring 事务传播机制定义了多个包含了事务的方法, 相互调用时, 事务是如何在这些方法间进行传递的Spring 事务的传播机制包含以下 7 种 :1. Propagation.REQUIRED: 默认的事务传播级别, 它表示如果A当前存在事务, B则使用该事务; 如果A当前没有事务,则B创建一个新的事务. 2. Propagation.SUPPORTS: 如果A当前存在事务, 则B使用该事务; 如果A当前没有事务, 则B以非事务的方式继续运行. 3. Propagation.MANDATORY: 如果A当前存在事务, 则B使用该事务; 如果A当前没有事务,则B抛出异常. 4. Propagation.REQUIRES_NEW: 表示创建一个新的事务, 如果A当前存在事务, 则把A当前事务挂起,B创建新事务,A如果没有事务,B创建一个新事务Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果A当前存在事务, 则把A当前事务挂起.B以非事务执行;如果A没有事务,则B以非事务执行 Propagation.NEVER: 以非事务方式运行, 如果A当前存在事务, 则抛出异常.如果A没有事务,则B以非事务执行Propagation.NESTED: 嵌套的事务,自己会创建一个新事务,但是这个新事务并不是自己单独提交的,而是等待外层事务一起提交,所以事务B后面 事务 A中的其他代码如果造成了 rollback 则也会导致事务B rollback6.5事务的实现Spring对数据库事务的支持有两种方式编程式事务管理:手动实现事务管理,需要通过TransactionManager手动管理事务声明式事务管理:在service的方法实现上,用@Transaction注解进行事务管理6.6代码实现事务操作的代码级别应该放在service层(涉及业务逻辑)①创建Spring Boot项目②环境配置③准备好数据库和表(dept)④提供对应mapper接口和相关操作数据库方法,以及对应的实体类⑤准备service层⑥测试7.Mybatis缓存7.1使用缓存的原因对于一些我们经常查询但是又不经常改变的数据,如果每次查询都要和数据库进行交互,会大大降低效率,因此我们可以使用缓存,把一些对结果影响不大但是经常查询的数据存放在内存中,从而减少和数据库的交互来提高效率。7.2Mybatis的缓存分类一级缓存SqlSession级别的缓存同一个SqlSession对象,在参数和SQL完全一样的情况下,只执行一次SQL语句(基本不用)二级缓存Mapper命名空间级别的缓存二级缓存的作用域是同一个namespace映射文件内容,多个SqlSession共享7.3 Spring Boot对Mybatis的支持Spring Boot默认支持二级缓存Mybatis默认支持一级缓存Mapper中的实体类需要实现序列化注解式开发需要在Mapper接口添加@CacheNamespace,开启二级缓存xml映射文件通过<cache/>,开启二级缓存使用二级缓存的时候,sql语句的映射要么全部都是xml格式表示,要么都是注解表示,选中其中一种情况就可以在实际项目中,一般会使用Redis非关系型数据库作为缓存数据库7.4代码实现首先是环境搭建,然后是代码实现提供实体类@Data @AllArgsConstructor @NoArgsConstructor public class Dept implements Serializable { private Integer deptno; private String dname; private String loc; }提供对应的接口@Mapper @CacheNamespace public interface DeptMapper { //查询所有部门信息 @Select("select * from dept ") List<Dept> queryAllDept(); } 测试@SpringBootTest @Slf4j class DeptMapperTest { @Autowired private DeptMapper deptMapper; @Test public void testQueryAllDept(){ List<Dept> deptList = deptMapper.queryAllDept(); for (Dept dept:deptList){ log.debug(dept.toString()); } List<Dept> deptList2 = deptMapper.queryAllDept(); for (Dept dept:deptList2){ log.debug(dept.toString()); } } }7.5缓存的底层细节(博客推荐)这部分内容,我推荐大家去看亦山大佬的博客,讲解很清楚深入理解MyBatis原理如果想要更透彻的理解的话,其实大家可以去看看计算机组成原理。
️1.全局配置文件1.1全局配置文件概述Spring Boot使用一个全局的配置文件,通过配置文件,可以修改Spring Boot自动配置的默认值,配置文件名是固定的,文件名必须是application配置文件格式properties或者yml,比如application.properties或者application.yml比如,我们可以通过修改application.properties来自定义服务器的端口号我们来新建一个项目1.2配置文件的位置和加载顺序/config/:当前项目根目录下的config目录下/:当前项目的根目录下/resources/config/:resources目录下的config目录下/resources/:resources目录下加载的优先级是从上到下,由高到低️2.多环境配置开发过程中,至少需要三个环境(开发环境dev-测试环境test-生产环境pro),但是每一个环境的配置都不一样,比如数据库等各种配置,Spring Boot提供多环境配置的机制,让开发者灵活根据需求而切换不同的配置环境比如:我们可以通过在不同环境下面,服务器端口号不同进行测试 2.1多环境配置文件在Spring Boot中多环境配置文件名必须满足:application-{profile}.properties的固定格式。其中,{profile}对应的是我们的环境标识,比如:application-dev.properties:开发环境 application-test.properties:测试环境 application-prod.properties:生产环境2.2激活某个环境配置在application.properties文件中添加配置,spring.profiles.active={profile} 来激活比如:spring-profiles.active=test 表示开启测试环境️3.yaml3.1yaml概述yaml(YAML A/isn't Markup Language),后缀名是.yaml或者.yml。有两层含义YAML是一个标记语言YAML不是一个标记语言yaml是以数据为中心,比如json\xml等更适合作为配置文件使用#添加注释# 服务器端口 server: port: 8080当我们创建一个Spring Boot项目的时候,它默认是给的配置文件是.properties的形式,我们也可以把它修改成yaml的形式3.2基本语法k:(空格)v,表示一对键值对,空格一定要有用缩进来控制层级关系,只要是左对齐的一列数据,都是同一个层级的属性和值是区分大小写的缩进的空格数不重要,但同级元素必须左侧对齐。例如:spring: profiles: dev datasource: url: jdbc:mysql://127.0.01/banchengbang_springboot username: root password: root driver-class-name: com.mysql.jdbc.Driver3.3值的写法YAML 支持以下三种数据结构:对象:键值对的集合数组:一组按次序排列的值字面量:单个的、不可拆分的值❄️3.3.1字面量字面量:普通的值(数字,字符串,布尔)字符串默认不需要加上单引号或者双引号字符串如果加上双引号,那么转义字符有效字符串如果加上单引号,那么转义字符只是一个普通的字符串数据字面量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、以及日期等name: "zhangsan \n lisi" # 输出的结果是 zhangsan lisiname: 'zhangsan \n lisi' # 输出的结果是 zhangsan \n lisi ❄️3.3.2 日期类型日期类型日期格式必须是 :"yyyy/MM/dd"birth: 1980/6/15+ 对象(属性和值)、Map(键值对) ### ❄️3.3.3对象 <font color="Red">对象写法方式一:换行缩进person: lastName: zhangsan age: 20 book:price: 222 <font color="red">对象写法方式二:行内写法book: {name: java,price: 2000}### ❄️3.3.4 数组、List、Set <font size=4>缩进的写法:用<font color="red">-</font>表示数组中的一个元素 pets:catdogpig行内写法,外部使用[]表示pets: [cat,dog,pig]# ️4.利用配置文件来给属性赋值 ## 4.1@Value,通过${}直接获得配置文件的配置值注入 ><font size=4> >测试 > ## 4.2@ConfigurationProperties <font size=4 color="red">可以让属性和配置文件中的配置自动绑定,需要指明一个prefix(前缀) ><font size=4>先把文件编码方式改成UTF-8 >还是以刚刚的Book类为例子,我们可以先在pom.xml文件中添加依赖 > >加入这个依赖的作用其实是为了便于我们写代码,我们在配置文件中可以看到提示 >  >测试一下 > ## 4.3@EnableConfigurationProperties + 方法一:通过@Component注解可以把指定类注入到Spring容器中 + 方法二:在Spring Boot启动类上,添加@EnableConfigurationProperties,把指定的类注入到Spring容器中,推荐这个方法,便于统一管理  我们点进这个注解去看看(大家对源码感兴趣的可以自己去深入了解)  ## 4.4@PropertySource <font size=4 color="red">可以指定加载一个独立的配置文件信息 用法: + 加载指定的属性文件(*.properties)到 Spring 的 Environment 中。可以配合 @Value 和@ConfigurationProperties 使用。 + @PropertySource 和 @Value 组合使用,可以将自定义属性文件中的属性变量值注入到当前类的使用@Value注解的成员变量中。 + @PropertySource 和 @ConfigurationProperties 组合使用,可以将属性文件与一个Java类绑定,将属性文件中的变量值注入到该Java类的成员变量中。 ><font size=4>比如说,我们本来是在全局配置文件中指定那些属性的信息,但是可能有很多需要配置,这个时候,我们可以把这些抽取出来,成为一个个小的配置文件,但是要注意的是,这个时候,这些配置文件==只能是properties形式的==,不能是yaml形式的 > > ><font color="red">有一个地方需要注意,这里的话,我们得使用@Component注解,如果使用@EnableConfigurationProperties进行注入的话,其实我们在测试的时候,会发现属性是null # ️5.自定义配置类 ><font size=4>从Spring3.0,==@Configuration用于定义配置类==,可以替换xml配置文件,被注解的类内部包含有一个或多个被<font color="red">@Bean</font>注解的方法,这些方法的返回值会被创建实例,托管给Spring容器 ## 5.1@Configuration ><font size=4>@Configuration标注在类上面,相当于把这个类作为Spring的xml配置文件中的``<bean> ``,作用是配置spring容器(应用上下文) ><font color="red">@Configuration注解的配置类有下面的几个要求: >1️⃣不可以是final类型 >2️⃣不可以是匿名类 >3️⃣嵌套的configuration必须是静态类 ## 5.2@Bean ><font size=4>@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<font color="red">\<bean>,作用是注册bean对象 ## 5.3代码演示 ><font size=4>我们先写一个实体类Stu > >然后写一个自定义的配置类 > >测试 > ## 5.4查看启动类的注解 ><font size=4> >点进去@SpringBootApplication注解 > >我们可以看到里面有@ComponentScan(包扫描)和@SpringBootConfiguration(继承于@Configuration),这样子就会把扫描到的包下面的类注入到spring容器中
Spring Boot优点简化maven依赖自动配置嵌入tomcatspring boot依赖的版本仲裁中心我们先创建一个项目我们可以发现它有一个父模块spring-boot-starter-parent <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.2</version> <relativePath/> <!-- lookup parent from repository --> </parent>我们可以按住CTRL,然后点击去看看我们发现这个父模块,自己还有父模块spring-boot-dependencies<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.7.2</version> </parent>我们继续点击进去看看这里面定义了很多依赖的版本定义了spring boot项目中所有子模块依赖的版本号,称为spring boot的版本的仲裁中心。spring boot启动器(简化maven依赖)官方把很多模块功能,封装成了一个个的启动器命名方式:spring-boot-starter-模块名我们点击spring-boot-starter-web,它里面的依赖如下代码所示添加了spring-web、spring-webmvc等springmvc的依赖添加了spring-boot-starter-tomcat依赖,嵌入了tomcat(精简版,不支持jsp,启动速度快) <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.7.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.7.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.7.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.22</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.22</version> <scope>compile</scope> </dependency> </dependencies> 主启动类和主配置类(xxxApplication)按住ctrl,点击这个注解进去看看@SpringBootApplication标注了@SpringBootApplication注解的是程序主入口类和主配置类,它是一个组合注解,包含以下几个主要的注解@SpringBootConfiguration 它的内部使用了原生注解@Configuration 说明当前类是项目的配置类@EnableAutoConfiguration 开启自动配置@ComponentScan 组件扫描,默认会扫描主配置类所在包下的所有注解@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default "**/*.class"; boolean useDefaultFilters() default true; ComponentScan.Filter[] includeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {}; boolean lazyInit() default false; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<?>[] value() default {}; @AliasFor("value") Class<?>[] classes() default {}; String[] pattern() default {}; } } 测试类我们可以看到用单元测试,也是可以的,那么什么情况单元测试不好用呢我们创建一个实体类stu然后测试一下,我们发现stu并没有注入进来因为这个时候,我们在单元测试中,并没有把springboot的一些配置整合进来,我们加上@SpringBootTest使用maven构建spring boot项目pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zyh.springboot</groupId> <artifactId>springboot04_maven</artifactId> <version>1.0-SNAPSHOT</version> <!-- 父模块--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.2</version> </parent> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!--模块启动器--> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>添加主程序先创建一个包,然后把主程序写在这个包下面在配置文件中可以指定端口号等信息编写Controller测试,运行启动类
@TOC文章是看楠哥的视频写的笔记这篇博客内容把Spring的基本知识点都讲到了,篇幅比较长,大家可以用于复习,也可以在学习相关知识点的时候,来看看对应内容。对于一些难点,IOC,AOP等,我通过流程图,代码,文字结合来进行讲解,可以更好的理解Spring FrameWork一、 前言Spring是当前Java开发的行业标准,第一框架Spring FrameWork已经从最初取代EJB的框架逐步发展成一套完整的生态,最新的版本是5.xSpring架构体系图Spring两大核心机制:IOC:工厂模式AOP:代理模式二、IOC(控制反转)2.1 对于IOC的理解传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new出来的但是在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转就比如说假设买东西,以前我们需要自己去超市买东西,但是现在我们可以不用自己去超市,我们只要把购物袋放在家门口,IOC就会自己去把我们想要的东西买回来,然后放在袋子里面,我们打开袋子拿起来用就可以了IOC的特点是解耦合。比如说A需要用到B,传统的开发,我们要直接创建B的实例,但是在Spring中,IOC这个容器会创建B的实例,然后把这个B注入到A2.2如何使用IOC创建maven工程,在pom.xml中导入相关依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springioc</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> </dependencies> </project>创建实体类Studentpublic class Student { private long id; private String name; private int age; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } 在resources路径下创建applicationContext.xml配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="stu" class="com.zyh.pojo.Student"></bean> </beans>传统的开发方式:手动new Student Student stu =new Student(); stu .setAge(25); stu.setId(1001); stu.setName("张三"); System.out.println(stu);IOC容器通过读取配置文件,加载配置bean标签来创建对象就像买菜一样,我们不需要自己亲自买,但是要写一张单子,告诉说要买什么,程序也是类似的,我们要告诉Spring框架要创建哪些对象,怎样创建对象调用API,从IOC获取对象//读取配置文件 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Student stu = applicationContext.getBean("stu", Student.class); System.out.println(stu);2.3配置文件的解读通过配置bean标签来完成对象的管理id:对象名class:对象的模板类(所有交给IOC容器来管理的类必须要有无参构造函数,因为Spring底层是通过反射机制来创建对象,调用的是无参构造)对象的成员变量通过property标签完成赋值name:成员变量名value:成员变量值(基本数据类型,String可以直接赋值,如果是其他引用类型不可以通过value赋值)ref:把IOC中的另一个bean赋给当前成员变量(DI)2.4IOC容器创建bean的两种方式无参构造函数(需要提供对应的set方法)有参构造函数 <bean id="stu1" class="com.zyh.pojo.Student"> <constructor-arg name="id" value="1"> </constructor-arg> <constructor-arg name="name" value="李四"></constructor-arg> </bean> <bean id="stu1" class="com.zyh.pojo.Student"> <constructor-arg index=0 value="1"> </constructor-arg> <constructor-arg index=1 value="李四"></constructor-arg> </bean>2.5从IOC容器中取bean通过id取值 Student stu = (Student)applicationContext.getBean("stu");通过类型取值 Student stu = applicationContext.getBean( Student.class);当IOC容器中存在两个以上Student Bean的时候就会抛出异常,因为此时没有唯一的bean2.6bean的属性如果包含特殊字符三、DI(依赖注入)DI:指bean之间的依赖注入,设置对象之间的级联关系Classespublic class Classes { private Integer id; private String name; 还有对应的get,set方法 }Studentpublic class Student { private long id; private String name; private int age; private Classes classes; public Student(){ System.out.println("使用无参构造创建对象"); } public Student(long id,String name){ this.id = id; this.name = name; } public Classes getClasses() { return classes; } public void setClasses(Classes classes) { this.classes = classes; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", classes=" + classes + '}'; } } applicationContext-di.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="classes" class="com.zyh.pojo.Classes"> <property name="name" value="1班"></property> <property name="id" value="1"></property> </bean> <bean id="student" class="com.zyh.pojo.Student"> <property name="id" value="1001"></property> <property name="name" value="张三"></property> <property name="age" value="22"></property> <property name="classes" ref="classes"></property> </bean> </beans>bean之间的级联需要使用ref属性,而不能用value属性,否则会抛出类型转换异常<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="classes" class="com.zyh.pojo.Classes"> <property name="name" value="1班"></property> <property name="id" value="1"></property> <property name="studentList"> <list> <ref bean="student"></ref> <ref bean="student2"></ref> </list> </property> </bean> <bean id="student" class="com.zyh.pojo.Student"> <property name="id" value="100"></property> <property name="name" value="张三"></property> <property name="age" value="22"></property> <property name="classes" ref="classes"></property> </bean> <bean id="student2" class="com.zyh.pojo.Student"> <property name="id" value="200"></property> <property name="age" value="18"></property> <property name="name" value="李四"></property> <property name="classes" ref="classes"></property> </bean> </beans>如果把学生装到班级里面,又把班级装到学生里面,就导致无限递归循环装配,最终栈溢出四、Spring中的beanbean是根据scope来生成的,表示bean的作用域,scope有4种类型singleton,单例,表示通过Spring容器获取的对象是唯一的,是默认值prototype,原型,表示通过Spring容器获取的对象是不同的配置文件<bean id="user" class="com.zyh.pojo.User" scope="prototype"> <property name="id" value="1"></property> <property name="name" value="张三"></property> </bean>request,请求,表示在异常HTTP请求内有效session,会话,表示在一个用户会话内有效request和session一般用于web项目singleton模式下,只要加载IOC容器,不管是否从IOC种取出bean,配置文件中的bean都会被创建,而且只会创建一个对象prototype模式下,如果不从IOC中取出bean,则不创建对象,取一次bean,就会创建一个对象五、Spring中的继承Spring中的继承不同于Java中的继承Java中的继承是针对于类的Spring中的继承是针对于对象(bean)<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user1" class="com.zyh.pojo.User" > <property name="id" value="1"></property> <property name="name" value="张三"></property> </bean> <bean id="user2" class="com.zyh.pojo.User" parent="user1"></bean> </beans>通过设置bean标签的parent属性建立继承关系,同时子bean可以覆盖父bean的属性值Spring的继承是针对对象的,所以子bean和父bean并不需要同一个数据类型,只要其成员变量列表一致即可六、Spring的依赖用来设置两个bean的创建顺序IOC容器默认情况下是通过applicationContext.xml中bean的配置顺序来决定创建顺序的,配置在前面的bean会先被创建在不更改applicationContext.xml配置顺序的前提下,通过设置bean之间的依赖关系来调整bean的创建顺序七、Spring读取外部资源在实际开发中,数据库的配置会一般会单独保存到后缀为properties的文件,方便维护和修改如果用Spring来加载数据源,就需要在applicationContext.xml中读取properties中的数据,这就是读取外部资源jdbc.propertiesuser=root password=root url=jdbc:mysql://localhost:3306/spring driverName=com.mysql.cj.jdbc.Driverspring-properties.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--导入外部资源 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="dataSource" class="com.zyh.pojo.DataSource"> <property name="username" value="${user}"></property> <property name="password" value="${password}"></property> <property name="url" value="${url}"></property> <property name="driveName" value="${driverName}"></property> </bean> </beans>八、Spring的p命名空间用来简化xml配置<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="stu" class="com.zyh.pojo.Student" p:id="1" p:age="10" p:name="张三" p:classes-ref="classes"></bean> <bean id="classes" class="com.zyh.pojo.Classes" p:name="一班" p:id="1"></bean> </beans>九、Spring工厂方法IOC通过工厂模式创建bean有两种方式:静态工厂方法实例工厂方法静态工厂类不需要实例化,实例工厂类需要实例化9.1静态工厂方法创建Car类public class Car { private Integer num; private String brand; public Car() { } public Car(Integer num, String brand) { this.num = num; this.brand = brand; } public Integer getNum() { return num; } public void setNum(Integer num) { this.num = num; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } @Override public String toString() { return "Car{" + "num=" + num + ", brand='" + brand + '\'' + '}'; } } 创建静态工厂类,静态工厂方法public class StaticCarFactory { private static Map<Integer, Car> carMap; static { carMap = new HashMap<>(); carMap.put(1, new Car(1, "奥迪")); carMap.put(2, new Car(2,"奥拓")); } /** * 写一个方法,从map集合取数据 */ public static Car getCar(Integer num ){ return carMap.get(num); } } spring-factory.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="com.zyh.factory.StaticCarFactory" factory-method="getCar"> <constructor-arg name="num" value="1"></constructor-arg> </bean> </beans>factory-method 指向静态方法constructor-arg的value属性是调用静态方法传入的参数9.2实例工厂方法创建实例工厂类,工厂方法public class InstanceCarFactory { private Map<Integer, Car> carMap; public InstanceCarFactory() { carMap = new HashMap<>(); carMap.put(1, new Car(1, "奥迪")); carMap.put(2, new Car(2, "奥拓")); } public Car getCar(Integer num){ return carMap.get(num); } }spring.xml<!-- 实例工厂类--> <bean id="instanceCarFactory" class="com.zyh.factory.InstanceCarFactory"></bean> <!-- 通过实例工厂获取Car--> <bean id="car1" factory-bean="instanceCarFactory" factory-method="getCar"> <constructor-arg value="2"></constructor-arg> </bean>区别:静态工厂方法创建Car对象,不需要实例化工厂对象,因为静态工厂的静态方法,不需要创建对象就可以调用了实例工厂方法创建Car对象,需要实例化工厂对象,因为getCar方法是非静态的,就必须通过实例化对象才能调用,所以 必须创建工厂对象,spring.xml需要配置两个bean,一个是工厂bean,一个是Car Beanspring.xml中 class+factory-method的形式是直接调用类中的工厂方法spring.xml中factory-bean+factory-method的形式是调用工厂bean中的工厂方法,就必须先创建工厂bean十、Spring IOC 自动装配 autowire10.1自动装配自动装载是Spring提供的一种更加简便的方式来完成DI,不需要手动配置property,IOC 容器会自动选择bean来完成注入自动装载有两种方式:byName:通过属性名完成自动装载byType:通过属性对应的数据类型完成自动装载byName的操作如下:创建Person实体类public class Person { private Integer id; private String name; private Car car; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", car=" + car + '}'; } }在spring.xml中配置Car和Person对应的bean,并且通过自动装载完成依赖注入<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car2" class="com.zyh.pojo.Car"> <constructor-arg name="num" value="1"></constructor-arg> <constructor-arg name="brand" value="奥迪"></constructor-arg> </bean> <bean id="car" class="com.zyh.pojo.Classes"></bean> <bean id="person" class="com.zyh.pojo.Person" autowire="byName" > <property name="name" value="张三"></property> <property name="id" value="2"></property> </bean> </beans>注:如果bean的id有多个一致的,会报错,如Bean name 'car' is already used in this <beans> element byType的操作如下:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car2" class="com.zyh.pojo.Car"> <constructor-arg name="num" value="1"></constructor-arg> <constructor-arg name="brand" value="奥迪"></constructor-arg> </bean> <bean id="car" class="com.zyh.pojo.Classes"></bean> <bean id="person" class="com.zyh.pojo.Person" autowire="byType" > <property name="name" value="张三"></property> <property name="id" value="2"></property> </bean> </beans>使用byType进行自动装配的时候,必须保证IOC中有且只有一个符合,如果有多个符合,则报下面的异常:10.2 Spring IOC基于注解的开发Spring IOC的作用是帮助开发者创建项目中所需要的bean,同时完成bean之间的依赖注入关系,DI实现该功能有两种方式:基于XML配置基于注解基于注解有两步操作,缺一不可:配置自动扫包添加注解<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫包--> <context:component-scan base-package="com.zyh.pojo"></context:component-scan> <!-- <bean id="repository" class="com.zyh.pojo.Repository"></bean>--> </beans>@Component(value="repository") public class Repository { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public String toString() { return "Repository{" + "dataSource=" + dataSource + '}'; } } DI注解默认的beanid是类名以小写开头,我们可以通过value来设置如果我们想要把datasource也注入进来需要怎么做呢?首先我们要把DataSource先扫进来@Component public class Repository { @Autowired private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public String toString() { return "Repository{" + "dataSource=" + dataSource + '}'; } } @Autowired默认是通过byType进行装配,如果要改为byName,需要配合@Qualifier注解来完成@Component public class Repository { @Autowired @Qualifier(value = "datasource") private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public String toString() { return "Repository{" + "dataSource=" + dataSource + '}'; } } 这表明把IOC中id为datasource的bean注入到repository中实体类中普通的成员变量(String,包装类等),可以通过@Value注解来赋值@Component public class DataSource { @Value("root") private String username; @Value("123456") private String password; @Value("jdbc:mysql://localhost:3306/spring") private String url; @Value("com.mysql.cj.jdbc.Driver") private String driveName; }10.3实际开发中的使用实际开发中我们会把程序分成三层:ControllerServiceRepository(DAO)关系Controller--->Service---->Repository@Component注解是把标注的类加载到IOC容器中,实际开发中可以根据业务需求分别使用@Controller,@Service,@Repository注解来标注控制层类,业务层类,持久层类十一、Spring AOPAOP (Aspect Oriented Programming) 面向切面编程OOP (Object Oriented Programming) 面向对象编程,用对象化的思想来完成程序AOP是对OOP的一个补充,是在另外一个维度上抽象出对象具体指程序运行时动态地把非业务代码切入到业务代码中,从而实现程序的解耦合,把非业务代码抽象成一个对象,对对象编程就是面向切面编程上面这种形式的代码维护性很差,代码复用性差AOP的优点:可以降低模块之间的耦合性提供代码的复用性提高代码的维护性集中管理非业务代码,便于维护业务代码不受非业务代码影响,逻辑更加清晰通过一个例子来理解AOP。创建一个计算器接口Calpublic interface Cal { public int add(int num1,int num2); public int sub(int num1,int num2); public int mul(int num1,int num2); public int div(int num1,int num2); } 创建接口的实现类public class CalImpl implements Cal { @Override public int add(int num1, int num2) { int res = num1 + num2; return res; } @Override public int sub(int num1, int num2) { int res = num1 - num2; return res; } @Override public int mul(int num1, int num2) { int res = num1 * num2; return res; } @Override public int div(int num1, int num2) { int res=num1/num2; return res; } }日志打印在每个方法开始位置输出参数信息在每个方法结束位置输出结果信息对于计算器来说,加减乘除就是业务代码,日志打印就是非业务代码AOP如何实现? 使用动态代理的方式来实现代理首先要具备CalImpl的所有功能(实现同一个接口),并且在这个基础上,扩展出打印日志的功能删除CalImpl方法中国所有打印日志的代码,只保留业务代码创建MyInvocationHandler类(不是动态代理类),实现InvocationHandler接口,生成动态代理类 。动态代理类需要动态生成,需要获取到委托类的接口信息,根据这些接口信息动态生成一个代理类,然后再由ClassLoader用来把动态生成的类加载到JVMClassLoader用来把动态生成的类加载到JVM中代理类需要有和委托类一样的功能,所以委托类和代理类需要实现同样的接口,因此,我们要获取到委托类的接口信息,根据这个接口信息就可以生成一个类,再通过ClassLoader加载到内存里面public class MyInvocationHandler implements InvocationHandler { //委托对象 private Object object = null; //返回代理对象 public Object bind(Object object) { this.object = object; return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //实现业务代码和非业务代码的解耦合 System.out.println(method.getName()+"方法的参数是:"+ Arrays.toString(args)); Object res = method.invoke(this.object, args); System.out.println(method.getName()+"方法的结果是:"+res); return res; } } 上述代码通过动态代理机制实现了业务代码和非业务代码的解耦合,这是Spring AOP的底层实现机制,真正使用 Spring AOP进行开发的时候,不需要这么复杂Spring AOP的开发步骤创建切面类 Loggerspect@Component @Aspect public class LoggerAspect { @Before("execution(public int com.zyh.aop.impl.CalImpl.*(..))") public void before(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println(name + "方法的参数是:" + Arrays.toString(args)); } @After("execution(* com.zyh.aop.impl.CalImpl.*(..))") public void after(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法执行完毕"); } @AfterReturning(value = "execution(* com.zyh.aop.impl.CalImpl.*(..))",returning = "rs") public void afterReturning(JoinPoint joinPoint,Object rs){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法执行的结果是:"+rs); } @AfterThrowing(value = "execution(* com.zyh.aop.impl.CalImpl.*(..))",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ String name = joinPoint.getSignature().getName(); System.out.println(name+"方法抛出异常"+ex); } } @Component,把切面类加载到IOC容器中@Aspect,表示该类是一个切面类@Before,表示方法的执行时机是在业务方法之前,execution表达式表示切入点是CalImpl中的所有方法@After,表示方法的执行时机是在业务方法结束以后,execution表达式表示切入点是CalImpl类中的方法@AfterReturning,表示方法的执行时机是在业务方法返回结果后,execution表达式表示切入点是CalImpl类中的方法,returning是把业务方法的返回值和切面类方法的形参进行绑定@AfterThrowing,表示方法的执行时机是在业务方法抛出异常后,execution表达式表示切入点是CalImpl类中的方法,throwing是把业务方法的异常和切面类方法的形参进行绑定委托类也需要添加@Component@Component public class CalcImpl implements Calc{ @Override public int add(int a, int b) { int result=a+b; return result; } @Override public int sub(int a, int b) { int result=a-b; return result; } @Override public int mul(int a, int b) { int result= a*b; return result; } @Override public int div(int a, int b) { int result= a/b; return result; } } 3.spring-aop.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 自动扫包--> <context:component-scan base-package="com.zyh.aop"></context:component-scan> <!-- 为委托对象自动生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>aspectj-autoproxy ,Spring IOC容器会结合切面对象和委托对象自动生成动态代理对象,AOP底层就是通过动态代理机制来实现的4.测试AOP的概念切面对象:根据切面抽象出来的对象,CalImpl所有方法中需要加入日志的部分LoggerAspect通知:切面对象具体执行的代码,即非业务代码,LoggerAspect对象打印日志的代码目标:被横切的对象,即CalImpl,把通知加入其中代理:切面对象,通知,目标混合后的结果,即我们通过JDK动态代理机制创建的对象连接点:需要被横切的位置,即通知要插入业务代码的具体位置
@TOC本文的图片大部分引用于韩顺平老师,对网络编程基础做了总结,至于更详细的内容,大家可以去看计算机网络一、网络的相关概念1.网络通信2.网络网络编程的目的:直接或间接地通过网络协议与其他计算机实现数据交换进行通讯 要想实现网络编程,需要关注两个问题: 1.如何准确定位网络上的一台或多台主机,定位主机上的特定应用 2.找到主机以后怎么进行可靠高效的数据传输3 .IP地址IP地址就是网络上定位的一台主机,端口号就是定位这台主机是哪一个应用在进行通信,端口号用来区分一台主机上面的不同应用程序4.ipv4地址分类5.域名和端口号比如说我们想要访问一个网站,由于IP地址难以记忆,我们一般在浏览器上写的是域名,域名经过DNS域名解析服务器,会把域名解析出来,看它的IP是多少,解析完以后,再拿这个IP地址去访问对应的服务器,就可以把这个资源访问到了不同的进程对应不同的端口号端口号与IP地址的组合得出一个网络套接字:Socket一个服务要接收和发送数据的话,需要有一个端口。端口就类似于人的耳朵,我们要听懂别人的话,必须要有耳朵6.网络通信协议OSI模型过于理想化,没有被广泛推广7. TCP和UDPTCP类似于打电话UDP类似于发短信 TCP三次握手举一个生活中的小例子:打电话打电话的时候,假设一个叫tom,一个叫kim,tom想要打电话和kim说事情,这个时候,电话必须打通,才可以传递信息,就类似于TCP连接。为了确保kim有在听,tom在传递信息之前先问一句,你在听吗?kim听到后就回说在听,然后tom就说我要开始说事情了,紧接着开始说事情UDP协议无法保证传输的数据一定被接收到UDP协议就是没有确认对方是否能够接收到消息,就直接传输信息比如说:你发送信息给别人,但是这个电话号码可能停机了或者说注销了,你不能确保别人能接收消息,类似于UDP协议假如说现在有一个人也想要打电话给kim,由于kim这个时候在和tom打电话,他们两个如果电话没有挂掉,这个人的电话是打不通的,就类似于TCP协议要释放已经建立的连接tom发信息给kim,另外一个人同样也可以发信息给kim,就类似于UDP协议不需要释放资源二、InetAddress 类一个InetAddress类的对象就当于是一个IP地址//File file=new File("hello.txt"); //比如说在这里file就对应内存中的hello.txt这个文件 //类似于这个 //inet1就对应一个具体的IP地址 InetAddress inet1=InetAddress.getByName("192.168.10.14"); //写上主机名就可以返回对应的对象 1.相关方法getByName(String host) 、 getLocalHost() 两个常用方法:getHostName() 获取域名 / getHostAddress() 获取主机地址2.实例操作package com.zyh.java; import java.net.InetAddress; import java.net.UnknownHostException; /**InetAddress类的使用 * @author zengyihong * @create 2022--02--27 8:46 */ public class InetAddressMethod { public static void main(String[] args) throws UnknownHostException { //获取本机的InetAddress对象 InetAddress localHost = InetAddress.getLocalHost(); //计算机名称和识别到的ip地址 System.out.println(localHost); //我们也可以根据主机名来获取InetAddress对象 InetAddress byName = InetAddress.getByName("这里写上你的主机名"); System.out.println("host="+byName); //根据域名来返回InetAddress对象 InetAddress byName1 = InetAddress.getByName("www.baidu.com"); System.out.println("百度:"+byName1); //根据InetAddress对象获取对呀的主机地址 String hostAddress = byName1.getHostAddress(); System.out.println(hostAddress);//ip地址 //通过InetAddress对象,获取对应的主机名或者域名 String hostName = byName1.getHostName(); System.out.println(hostName); } } 三、SocketSocket就类似于通信的通道,发送数据是通过socket两台主机要通信首先要建立连接,就相当于是建立一个数据通道客户端发起连接,服务器端接收连接就会形成这样一个数据通道Socket有两个编程方式:TCP编程和UDP编程1.TCP网络通信编程1.1基本介绍Socket最后记得关闭,不然连接越来越多,最后就导致无法连接1.2案例1:客户端发送信息给服务端思路:ServerSocket可以有多个Socket,也就是说服务器端可以和多个客户端连接,否则就不能处理多并发问题服务器端:package com.zyh.java; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /**服务器端 * @author zengyihong * @create 2022--02--27 9:25 */ public class SocketTCP01Server { public static void main(String[] args) throws IOException { //在本机的9999端口监听,等待连接 //要求:本机没有其他服务监听这个端口 //ServerSocket可以有多个Socket 通过accept可以返回多个Socket ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务器端在9999端口等待连接..."); //如果没有客户端连接此端口,程序就会阻塞,等待连接 //如果有客户端连接,则返回Socket对象 Socket socket = serverSocket.accept(); //没有连接的话,下面的代码都不会运行 System.out.println("服务器端 socket="+socket.getClass()); System.out.println("服务器端开始接收信息"); //连接以后要读取客户端发送的信息 //通过IO读取 InputStream inputStream = socket.getInputStream(); byte[] bytes=new byte[10]; int len; while ((len=inputStream.read(bytes))!=-1){ String s = new String(bytes, 0, len); System.out.print (s); } //关闭流和socket inputStream.close(); socket.close(); serverSocket.close(); } } 客户端:package com.zyh.java; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * 客户端 * * @author zengyihong * @create 2022--02--27 9:31 */ public class SocketTCP01Client { public static void main(String[] args) throws IOException { //连接服务器端 (ip,端口) //连接某一台主机的9999端口,如果连接成功,返回socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回="+socket.getClass()); //连接上后,生成Socket,发送数据给服务端,通过socket,getOutputStream() //得到和socket对象关联的输出流对象 System.out.println("客户端要开始发消息给服务端了"); OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,server".getBytes()); //关闭流对象和socket outputStream.close(); socket.close(); System.out.println("客户端退出"); } } 运行效果:在服务器端:服务器端在9999端口等待连接... 服务器端 socket=class java.net.Socket hello,server在客户端:客户端 socket返回=class java.net.Socket 客户端退出1.3案例2:客户端发信息给服务器端,并接收服务器端发回来的消息注意:服务器端发消息给客户端的时候,客户端并不知道服务器端什么时候发送信息结束,也就是发送信息的时候,要有一个结束标记,不然就会一直等待,同样的,另外一个发送信息的时候,发送结束也应该要有一个结束标记,不然就不知道对方还要不要继续发送信息服务器端:package com.zyh.java; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /**服务器端 * @author zengyihong * @create 2022--02--27 9:25 */ public class SocketTCP02Server { public static void main(String[] args) throws IOException { //在本机的9999端口监听,等待连接 //要求:本机没有其他服务监听这个端口 //ServerSocket可以有多个Socket 通过accept可以返回多个Socket ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务器端在9999端口等待连接..."); //如果没有客户端连接此端口,程序就会阻塞,等待连接 //如果有客户端连接,则返回Socket对象 Socket socket = serverSocket.accept(); //没有连接的话,下面的代码都不会运行 System.out.println("服务器端 socket="+socket.getClass()); System.out.println("服务器端开始接收信息"); //连接以后要读取客户端发送的信息 //通过IO读取 InputStream inputStream = socket.getInputStream(); byte[] bytes=new byte[10]; int len; while ((len=inputStream.read(bytes))!=-1){ String s = new String(bytes, 0, len); System.out.print (s); } //获取 socket 相关联的输出流 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello, client".getBytes()); // 设置结束标记 socket.shutdownOutput(); //关闭流和socket outputStream.close(); inputStream.close(); socket.close(); serverSocket.close(); } } 客户端:package com.zyh.java; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * 客户端 * * @author zengyihong * @create 2022--02--27 9:31 */ public class SocketTCP02Client { public static void main(String[] args) throws IOException { //连接服务器端 (ip,端口) //连接某一台主机的9999端口,如果连接成功,返回socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回="+socket.getClass()); //连接上后,生成Socket,发送数据给服务端,通过socket,getOutputStream() //得到和socket对象关联的输出流对象 System.out.println("客户端要开始发消息给服务端了"); OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,server".getBytes()); // 设置结束标记 socket.shutdownOutput(); // 获取和 socket 关联的输入流. 读取数据(字节) ,并显示 InputStream inputStream = socket.getInputStream(); byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != - 1) { System.out.println(new String(buf, 0, readLen)); } //关闭流对象和socket inputStream.close(); outputStream.close(); socket.close(); System.out.println("客户端退出"); } } 运行效果: 在服务器端:服务器端在9999端口等待连接...服务器端 socket=class java.net.Sockethello,server在客户端:客户端 socket返回=class java.net.Sockethello,client客户端退出### 1.4案例3:使用字符流   客户端package com.hspedu.socket;import java.io.*;import java.net.ServerSocket;import java.net.Socket;@SuppressWarnings({"all"})public class SocketTCP03Server {public static void main(String[] args) throws IOException {//思路//1. 在本机 的 9999 端口监听, 等待连接// 细节: 要求在本机没有其它服务在监听 9999// 细节: 这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发] ServerSocket serverSocket = new ServerSocket(9999);System.out.println("服务端,在 9999 端口监听,等待连接..");//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接// 如果有客户端连接,则会返回 Socket 对象,程序继续Socket socket = serverSocket.accept(); System.out.println("服务端 socket =" + socket.getClass());////3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示InputStream inputStream = socket.getInputStream();//4. IO 读取, 使用字符流, 使用 InputStreamReader 将 inputStream 转成字符流BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); System.out.println(s);//输出 //5. 获取 socket 相关联的输出流OutputStream outputStream = socket.getOutputStream();// 使用字符输出流的方式回复信息BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello client 字符流"); bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束 bufferedWriter.flush();//注意需要手动的 flush //6.关闭流和 socket bufferedWriter.close(); bufferedReader.close(); socket.close(); serverSocket.close();//关闭 }}服务端:package com.hspedu.socket; import java.io.*;import java.net.InetAddress; import java.net.Socket; /**客户端,发送 "hello, server" 给服务端, 使用字符流 */@SuppressWarnings({"all"})public class SocketTCP03Client {public static void main(String[] args) throws IOException {//思路//1. 连接服务端 (ip , 端口)//解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket 对象Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket 返回=" + socket.getClass());//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()// 得到 和 socket 对象关联的输出流对象OutputStream outputStream = socket.getOutputStream();//3. 通过输出流,写入数据到 数据通道, 使用字符流BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello, server 字符流"); bufferedWriter.newLine(); //插入一个换行符,表示写入的内容结束, 注意,要求对方使用 readLine() //!!!! bufferedWriter.flush(); // 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道 //4. 获取和 socket 关联的输入流. 读取数据(字符) ,并显示InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); System.out.println(s); //5. 关闭流对象和 socket, 必须关闭bufferedReader.close();//关闭外层流 bufferedWriter.close(); socket.close(); System.out.println("客户端退出.....");}}运行结果:   ### 1.5案例4:发送图片   ### 1.6netstat指令   netstat - anb表示我们在查看程序的时候,知道是哪一个程序在占用这个端口 ### 1.7 TCP 网络通讯不为人知的秘密   当客户端和服务器连接成功以后,客户端这里也有一个端口,它的端口是不确定的,是由TCP/IP分配的。客户端这里对应的端口和服务器端通讯 当传输结束以后,客户端的端口就被释放了 ## 2.UDP网络编程(了解) 用得相对比较少,但是效率比较高 ### 1. 基本介绍   ### 2.基本流程   ### 3.案例   package com.zyh.java.udptest;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;/**UDP接收端@author zengyihong@create 2022--02--27 12:38*/public class SocketUDP01Reciver {public static void main(String[] args) throws IOException { //创建一个DatagramSocket对象,准备在9999端口接收数据 DatagramSocket datagramSocket=new DatagramSocket(9999); //创建 DatagramPacket对象,准备接收数据 //UDP数据包每一个大小被限制在64K内,不适合传输大量数据 byte[] bytes=new byte[1024]; DatagramPacket datagramPacket=new DatagramPacket(bytes,bytes.length); //调用接收方法,把通过网络传输的DatagramPacket对象 //填充到packet对象 //会在9999端口等待,如果有数据包发送到此端口,就会接收到数据 //如果没有数据包发送到9999端口,就会阻塞,一直等,直到有数据传输 System.out.println("接收端A等待接收数据..."); datagramSocket.receive(datagramPacket); //把datagramPacket进行拆包,取出数据,并显示 //因为数据的传输被包装起来了 //返回实际接收的长度 int length = datagramPacket.getLength(); //接收到的数据 byte[] data = datagramPacket.getData(); String s = new String(data, 0, length); System.out.println(s); //关闭资源 datagramSocket.close(); System.out.println("A端退出"); }}package com.zyh.java.udptest;import java.io.IOException;import java.net.*;/**UDP发送端发送端B也可以接收数据发送端将来也可以是接收端@author zengyihong@create 2022--02--27 12:39*/public class SocketUDP01Send {public static void main(String[] args) throws IOException { //创建DatagramSocket对象,准备发送和接收数据 //准备在9998端口接收数据 DatagramSocket socket=new DatagramSocket(9998); //把要发送的数据封装到DatagramPacket对象 内容字节数组,长度,主机(IP),端口 byte[] buff= "hello,明天吃火锅".getBytes(); //ipconfig可以查看网络配置 DatagramPacket packet=new DatagramPacket(buff,buff.length,InetAddress.getLocalHost(),9999); //发送数据 socket.send(packet); //关闭资源 socket.close(); System.out.println("B端退出"); }}运行结果接收端A等待接收数据...hello,明天吃火锅A端退出B端退出`
@TOCJDBC前言我觉得学习jdbc后面就是学会调用一系列的接口,方法,面向API编程,这个过程其实挺容易的,不过我觉得把原理弄清楚会更加的重要,弄清楚原理,我们也能更好理解为什么要使用这样的技术,并且对后面的框架也有帮助,学习起来也比较轻松,不然只会调用方法,但是却不知道它的原理,学习起来其实挺痛苦的。接下来,开始JDBC 的正式说明。说明:本文是学习韩顺平老师的JDBC所写,有些图片引用于韩老师一、JDBC概述1.原理1.JDBC为访问不同的数据库提供统一的接口,为使用者屏蔽了细节问题2.Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作3.模拟JDBC的基本原理我们想要通过Java程序来去操作数据库,如果没有一套规范,那么当我们想要操作数据库的时候,不同的数据库有不同的方法,这样子每次操作不同的数据库,都要写不同的代码,代码的复用性太差了,因为我们想要执行的操作都是一样的,但是由于没有统一的规范,每次都要调用不同的方法,也不利于我们的记忆。这个时候JDBC就出现了,它就提供了接口的规范,这套规范,由不同的厂商来实现,必须要遵守这个规范这个时候,我们只需要通过一套方法就可以操作不同的数据库。我们来想一下,定义的这套规范,数据库厂商可以不去遵守吗?很明显,如果不遵守这套规范,就意味着Java程序不能去调用它,这个数据库就没有人去用了。数据库厂商开发了数据库,结果没人用,这开发出来不就没有意义了吗?所以,很明显,各个数据库厂商都会去实现这个接口把实现这个接口的类打包,放在jar包,也称为驱动以后学jdbc就是看jdbc里面有哪一些方法,在一个项目中想要操作哪一个数据库,就把jar包导入项目中2.JDBC的模拟实现package com.bjpowernode.myjdbc; /**JDBC的模拟实现 * @author zengyihong * @create 2022--02--13 12:40 * @version 1.0 */ public interface JdbcInterface { //连接 public abstract Object getConnection(); //crud public void crud(); //关闭连接 public void close(); }package com.bjpowernode.myjdbc; /**mysql厂商对jdbc的规范进行实现 * @author zengyihong * @create 2022--02--13 14:10 */ public class MysqlJdbcImpl implements JdbcInterface{ @Override public Object getConnection() { System.out.println("得到MySQL的连接"); return null; } @Override public void crud() { System.out.println("完成MySQL的crud操作"); } @Override public void close() { System.out.println("关闭MySQL的连接"); } }package com.bjpowernode.myjdbc; /** * @author zengyihong * @create 2022--02--13 14:11 */ public class TestMyJdbc { public static void main(String[] args) { //获取驱动 JdbcInterface jdbc=new MysqlJdbcImpl(); //连接 jdbc.getConnection(); //进行操作 jdbc.crud(); //关闭 jdbc.close(); } }执行结果得到MySQL的连接 完成MySQL的crud操作 关闭MySQL的连接3.JDBC带来的好处假如我们不使用JDBC,那么直接通过Java程序直接操作数据库的话,首先,代码不统一, 第二,比如说,有一天,数据库升级了,那么,我们写的源代码也要发生改变,否则可能数据库连接不上了,这样的话,就很恐怖了,每一次数据库发生升级,我们都要修改源代码,否则无法运行,这样也不利于程序的维护,也带来很大的麻烦。使用JDBC的话,这个时候,把对数据库的维护交给各个数据库厂商来做,我们只需要调用接口,那么,假如现在数据库升级了,我们只需要把新的jar包替换旧的jar包就可以了,源代码也不需要改变所以这个时候,就进行改进了,不要通过Java程序粗暴直接的操作数据库。数据库实现了这个接口以后,我们只需要 和JDBC打交道,通过JDBC接口,去调用数据库实现类的方法4.JDBC APIJDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程就可以了,不同的数据库厂商,需要针对这套接口,提供不同实现JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行sql语句,并且对得到的返回结果进行一系列操作,相关的类和接口在java.sql 和javax.sql javax是扩展包二、JDBC快速入门相信经过上面的解释以后,大家对于JDBC已经有了一定的理解了,接下来通过一个案例来体会一下,具体的后面会细说1.编写步骤1.注册驱动--加载Driver类2.获取连接--得到Connection3.执行增删改查--发送SQL 给MySQL执行4.释放资源--关闭相关连接2.第一个JDBC程序通过jdbc对测试表 actor进行 添加,删除和修改操作事先准备一张表CREATE TABLE actor( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL DEFAULT'', sex CHAR(1) NOT NULL DEFAULT '女', borndate DATETIME, phone VARCHAR(12) );把MySQL驱动放在libsDirectiory下面把jar包拷贝放在libs然后把jar包加入我们的项目里面mysql是一个服务,它默认在3306端口监听,我们如果想要连接某一个服务,我们得指定是连接哪一个服务因为MySql管理了很多的数据库,所以我们要指明是连接哪一个数据库很明显,连接数据库肯定还要用户名和密码,所以我们也要指明package com.bjpowernode.myjdbc; import com.mysql.jdbc.Driver; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /**第一个JDBC程序,完成简单的操作 * @author zengyihong * @create 2022--02--13 15:00 */ public class JdbcTest1 { public static void main(String[] args) throws SQLException { //获取驱动 //注意:new Driver()的Driver和左边的Driver不一样,右边的Driver是数据库厂商写的实现类 Driver driver = new Driver(); //进行连接 /* * jdbc:mysql:// 是一个规定好的协议,通过jdbc的方式连接MySQL * localhost 表示主机 也可以是IP地址 * 3306 表示MySQL监听的端口 * hsp_db02 表示的是连接到哪一个数据库 * MySQL的连接本质就是前面学过的socket连接 * *连接数据库肯定还要有用户名和密码 * * */ String url="jdbc:mysql://localhost:3306/hsp_db02"; //把用户名和密码放入到Properties对象中 Properties properties = new Properties(); //user和password是规定好的,后面的值根据实际情况来写 properties.setProperty("user","写上你的用户名");//用户名 properties.setProperty("password","数据库连接的密码");//密码 //进行连接 Connection connect = driver.connect(url, properties); //进行sql操作 //String sql="insert into actor values(null, ' 刘德华', '男', '1970-11-11', '110')"; String sql="update actor set name='周星驰' where id=1"; //statement用来执行静态sql语句并返回其生成的结果的对象 Statement statement = connect.createStatement(); /*如果是dml语句,返回的就是影响的行数*/ int row=statement.executeUpdate(sql); if (row>0){ System.out.println("成功"); }else{ System.out.println("失败"); } //关闭连接资源 statement.close(); connect.close(); } } 在SQLyog来查询一下的结果根据上面的程序,如果我们修改要执行的sql语句,比如说把id=1的信息删除掉,就会出现下面的结果,注意:如果条件不存在,就会失败 String sql="delete from actor where id=1";接下来,我们来正式学习一下,连接数据库的方式三、连接数据库的方式1.方式一2.方式二以后可以把里面的内容写在配置文件当中,更加灵活3.方式三DriveManger是管理一组JDBC程序的基本服务4.方式四这个时候大家肯定有一个疑问,之前不是说连接数据库,需要先注册驱动,才可以获取连接吗,但是这里为什么不用注册驱动呢。接下来我们来看一下源码Driver里面有一个静态代码,他会自动注册驱动可以看到里面有静态代码块,在类加载的时候,会执行一次,会加载驱动,所以注册Driver的动作已经完成了Mysql8.0就替换成下面这个了,不过道理也一样public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }在经过测试的时候,发现,就算没有写Class.forName("com.mysql.jdbc.Driver);这段代码也不会报错5.方式五(推荐使用)方式五是对方式四的优化如果使用方式四的话,用户名和密码都已经写死了,如果将来想操作其他数据库还得修改,我们可以把这些信息写在配置文件中//方式 5 , 在方式 4 的基础上改进,增加配置文件,让连接 mysql 更加灵活 @Test public void connect05() throws IOException, ClassNotFoundException, SQLException { //通过 Properties 对象获取配置文件的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); Class.forName(driver);//建议写上 Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式 5 " + connection); } }获取数据库连接的练习package com.bjpowernode.exer; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /**获取数据库连接的练习 * @author zengyihong * */ public class ConnectionFirst { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { //获取数据库连接方式五 --- 推荐使用 //把配置文件的信息读取到,要先读取文件 Properties properties=new Properties(); properties.load(new FileInputStream(new File("D:\\jdbc\\JDBC\\src\\exer.properties"))); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); String user = properties.getProperty("user"); String password = properties.getProperty("password"); //注册驱动,获取连接 Class.forName(driver); Connection connection = DriverManager.getConnection(url, user, password); //进行数据库的操作 Statement statement = connection.createStatement(); String sql="insert into actor values(null, ' 刘华', '男', '1998-1-11', '523')"; int executeUpdate = statement.executeUpdate(sql); if (executeUpdate > 0) { System.out.println("操作成功"); }else { System.out.println("操作失败"); } } } 四、ResultSet[结果集]结果集就类似于返回一张表1.基本介绍当调用next方法后,光标往下移动next会判断下面还有没有记录,如果没有就返回false,我们一般通过while循环来读取数据2.案例演示package com.bjpowernode.resultset_; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.*; import java.util.Properties; /** *演示select语句返回ResultSet,并取出结果 * * @author zengyihong * */ public class ResultSetTest { public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException { //获取数据库连接方式五 --- 推荐使用 //把配置文件的信息读取到,要先读取文件 //获取相关的值 Properties properties = new Properties(); properties.load(new FileInputStream(new File("D:\\jdbc\\JDBC\\src\\exer.properties"))); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); String user = properties.getProperty("user"); String password = properties.getProperty("password"); //注册驱动,获取连接 Class.forName(driver); //得到连接 Connection connection = DriverManager.getConnection(url, user, password); //得到Statement Statement statement = connection.createStatement(); //写sql语句 String sql = "select id,name,sex, borndate from actor"; //执行给定的sql语句,该语句返回单个ResultSet对象 +----+---------+-----+---------------------+-------+ | id | name | sex | borndate | phone | +----+---------+-----+---------------------+-------+ | 2 | 刘德华 | 男 | 1970-11-11 00:00:00 | 110 | | 3 | 汤姆 | 男 | 1980-11-11 00:00:00 | 8990 | | 4 | 刘华 | 男 | 1998-01-11 00:00:00 | 523 | | 5 | 刘华 | 男 | 1998-01-11 00:00:00 | 523 | +----+---------+-----+---------------------+-------+ ResultSet resultSet = statement.executeQuery(sql); //让光标下移,如果没有更多记录,返回false System.out.println("id \t name \t sex \t date"); while (resultSet.next()){ //获取该行的第一列数据 int id = resultSet.getInt(1); //获取该行第二列 String name = resultSet.getString(2); String sex= resultSet.getString(3); Date date = resultSet.getDate(4); System.out.println(id+"\t"+name+"\t"+sex+"\t"+date); } //使用while取出数据 //关闭连接 resultSet.close(); statement.close(); } } 运行以后,我们来看一下控制台输出什么id name sex date 2 刘德华 男 1970-11-11 3 汤姆 男 1980-11-11 4 刘华 男 1998-01-11 5 刘华 男 1998-01-11接下来,我们来debug一下,看看源码elementData是存放真正的数据的,size表示有几条记录0代表第一行的数据1代表第二行的数据,以此类推五、Statement1.基本介绍Statement是一个接口,这个接口会被不同的数据库厂商实现。它用来执行静态SQL语句并返回其生成的结果的对象我们在开发中,一般不使用Statement,因为会有SQL注入问题2.SQL注入问题接下来,我们来演示一下SQL注入问题,我们以后需要避免出现这样的问题-- 演示 sql 注入 -- 创建一张表 CREATE TABLE admin ( -- 管理员表 NAME VARCHAR(32)NOT NULL UNIQUE, pwd VARCHAR(32) NOT NULL DEFAULT '') CHARACTER SET utf8; -- 添加数据 INSERT INTO admin VALUES('tom', '123'); -- 查找某个管理是否存在 //正常来说,我们查数据的时候,肯定是用户名和密码都正确才能查到,否则查不到 SELECT * FROM admin WHERE NAME = 'tom' AND pwd = '123' -- SQL -- 输入用户名 为 1' or -- 输入万能密码 为 or '1'= '1 SELECT * FROM admin WHERE NAME = '1' OR' AND pwd = 'OR '1'= '1' SELECT * FROM admin 我们知道'1'= '1'这个条件肯定是成立的 这个时候我们再进行查询,会发现可以查到结果,这就相当于被注入了然后我们如果在验证的时候,写程序说只要这个人存在,就让它他登录成功,这个后果不用我说大家也知道,明明不存在这个人,但是通过这样的方式,却可以登录成功,这样子是很危险的package com.zyh.jdbc.statement_; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.sql.*; import java.util.Properties; import java.util.Scanner; /** * @author zengyihong * * 演示 statement 的注入问题 */ @SuppressWarnings({"all"}) public class Statement_ { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); //让用户输入管理员名和密码 System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束 String admin_name = scanner.nextLine(); // 说明,如果希望看到 SQL 注入,这里需要用 nextLine System.out.print("请输入管理员的密码: "); String admin_pwd = scanner.nextLine(); //通过 Properties 对象获取配置文件的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); //1. 注册驱动 Class.forName(driver);//建议写上 //2. 得到连接 Connection connection = DriverManager.getConnection(url, user, password); //3. 得到 Statement Statement statement = connection.createStatement(); //4. 组织 SqL String sql = "select name , pwd from admin where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'"; ResultSet resultSet = statement.executeQuery(sql); if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在 System.out.println("恭喜, 登录成功"); } else { System.out.println("对不起,登录失败"); } //关闭连接 resultSet.close(); statement.close(); connection.close(); 3.预处理查询1.基本介绍在学习PreparedStatement之前,我们先来看看类的继承图2.好处为了有跟加深层次的理解,就下来通过案例演示一下3.案例演示package com.zyh.jdbc.preparedstatement_; import java.io.FileInputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Properties; import java.util.Scanner; /** * @author zengyihong * @version 1.0 * 演示 PreparedStatement 使用 dml 语句 */ @SuppressWarnings({"all"}) public class PreparedStatementDML_ { public static void main(String[] args) throws Exception { //看 PreparedStatement 类图 Scanner scanner = new Scanner(System.in); //让用户输入管理员名和密码 System.out.print("请输删除管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束 String admin_name = scanner.nextLine(); // 老师说明,如果希望看到 SQL 注入,这里需要用 nextLine System.out.print("请输入管理员的新密码: "); String admin_pwd = scanner.nextLine(); //通过 Properties 对象获取配置文件的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); //1. 注册驱动 Class.forName(driver);//建议写上 //2. 得到连接 Connection connection = DriverManager.getConnection(url, user, password); //3. 得到 PreparedStatement //3. 1 组织 SqL , Sql 语句的 ? 就相当于占位符 //添加记录 //String sql = "insert into admin values(?, ?)"; //String sql = "update admin set pwd = ? where name = ?"; String sql = "delete from admin where name = ?"; //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象 PreparedStatement preparedStatement = connection.prepareStatement(sql); //3.3 给 ? 赋值 preparedStatement.setString(1, admin_name); //preparedStatement.setString(2, admin_name); //4. 执行 dml 语句使用 executeUpdate int rows = preparedStatement.executeUpdate(); System.out.println(rows > 0 ? "执行成功" : "执行失败"); //关闭连接 preparedStatement.close(); connection.close(); } }我们运行一下,看看结果我们惊喜的发现,真的可以控制住SQL注入问题相信经过讲解以及案例的演示,大家已经对此应该印象更加深刻了。大家可以自己动手试试哦。六.JDBC相关API小结封装JDBC经过上面一系列的练习,,我们发现在jdbc的操作当中,获取连接和释放资源都是很经常用到的,我们可以把它封装成一个工具类JDBCUtils数据库的连接和资源的关闭都是通用操作,sql语句是不一样的,我们可以把一样的操作,写在工具类中,这样可以减少代码的冗余,提高代码的复用性package com.zyh.jdbc.utils; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; /** * @author zengyihong * * 这是一个工具类,完成 mysql 的连接和关闭资源 */ public class JDBCUtils { //定义相关的属性(4 个), 因为只需要一份,因此,我们做出 static private static String user; //用户名 private static String password; //密码 private static String url; //url private static String driver; //驱动名 //在 static 代码块去初始化 static { try { Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //读取相关的属性值 user = properties.getProperty("user"); password = properties.getProperty("password"); url = properties.getProperty("url"); driver = properties.getProperty("driver"); } catch (IOException e) { //在实际开发中,我们可以这样处理 //1. 将编译异常转成 运行异常 //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便. throw new RuntimeException(e); } } //连接数据库, 返回 Connection public static Connection getConnection() { try { return DriverManager.getConnection(url, user, password); } catch (SQLException e) { //1. 将编译异常转成 运行异常 //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便. throw new RuntimeException(e); } } //关闭相关资源 /* 1. ResultSet 结果集 2. Statement 或者 PreparedStatement 3. Connection 4. 如果需要关闭资源,就传入对象,否则传入 null */ public static void close(ResultSet set, Statement statement, Connection connection) { //判断是否为 null try { if (set != null) { set.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { //将编译异常转成运行异常抛出 throw new RuntimeException(e); } } }package com.zyh.jdbc.utils; import org.junit.jupiter.api.Test; import java.sql.*; /** * @author zengyihong * * 该类演示如何使用 JDBCUtils 工具类,完成 dml 和 select */ public class JDBCUtils_Use { @Test public void testSelect() { //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "select * from actor where id = ?"; PreparedStatement preparedStatement = null; ResultSet set = null; //3. 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); System.out.println(connection.getClass()); //com.mysql.jdbc.JDBC4Connection preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 5);//给?号赋值 //执行, 得到结果集 set = preparedStatement.executeQuery(); //遍历该结果集 while (set.next()) { int id = set.getInt("id"); String name = set.getString("name"); String sex = set.getString("sex"); Date borndate = set.getDate("borndate"); String phone = set.getString("phone"); System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone); } } catch (SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtils.close(set, preparedStatement, connection); } } @Test public void testDML() {//insert , update, delete //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "update actor set name = ? where id = ?"; // 测试 delete 和 insert , 自己玩. PreparedStatement preparedStatement = null; //3. 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); preparedStatement = connection.prepareStatement(sql); //给占位符赋值 preparedStatement.setString(1, "周星驰"); preparedStatement.setInt(2, 4); //执行 preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtils.close(null, preparedStatement, connection); } } }七、事务大家学到这里的话,如果对事务的概念不清楚或者已经忘记的话,大家可以先去复习一下基本概念,然后再接着往下看,否则可能比较懵。1.基本介绍2.转账问题转账问题是经典的事务问题,我们来模拟一下先准备一下表CREATE TABLE Account( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL DEFAULT'', balance DOUBLE NOT NULL DEFAULT 0)CHARACTER SET utf8; INSERT INTO Account VALUES (NULL,'张三',3000); INSERT INTO Account VALUES (NULL,'李四',10000); 我们先来看看,如果不使用事务的话,会出现什么样的情况 @Test public void noTransaction() { //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "update account set balance=balance-1000 where id= 1"; String sql2 = "update account set balance=balance+1000 where id= 2"; PreparedStatement preparedStatement = null; ResultSet set = null; //3. 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); System.out.println(connection.getClass()); //com.mysql.jdbc.JDBC4Connection preparedStatement = connection.prepareStatement(sql); //执行 preparedStatement.executeUpdate(); int i=1/0;//会抛出异常 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate();//执行第二条语句 }catch(SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtils.close(set, preparedStatement, connection); } }这个时候就发现,1000元不翼而飞了如果我们使用事务来解决的话,就会发现,可以避免这样的问题 //事务来解决 @Test public void useTransaction() { //操作转账的业务 //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "update account set balance = balance - 100 where id = 1"; String sql2 = "update account set balance = balance + 100 where id = 2"; PreparedStatement preparedStatement = null; //3. 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); // 在默认情况下,connection 是默认自动提交 //将 connection 设置为不自动提交 connection.setAutoCommit(false); //开启了事务 preparedStatement = connection.prepareStatement(sql); preparedStatement.executeUpdate(); // 执行第 1 条 sql int i = 1 / 0; //抛出异常 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate(); // 执行第 3 条 sql //这里提交事务 connection.commit(); } catch (SQLException e) { //这里我们可以进行回滚,即撤销执行的 SQL //默认回滚到事务开始的状态. System.out.println("执行发生了异常,撤销执行的 sql"); try { connection.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } e.printStackTrace(); } finally { //关闭资源 JDBCUtils.close(null, preparedStatement, connection); } } } 八、批处理1.基本介绍传统的执行方式,sql语句一条一条执行,这样子效率不高,我们就会这样想,为什么不把这些sql语句一起执行呢?比如说,一个人家里有两个小孩,要送孩子上学,可以先送一个人上学,再回来送另外一个,但是这样子效率比较低;但是还有另外一种方式,两个孩子一起送,这样子效率比较高对clearBatch()的说明,我们可以这样想,开车送小孩上学,但是车一次载的人数是有限的,所以一批送到学校以后,我们要让车上的孩子下车,清空车位,这样子才可以继续送其他的小孩2.应用实例准备一张表来演示package com.zyh.jdbc.batch_; import com.zyh.jdbc.utils.JDBCUtils; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; /** * @author zengyihong * @version 1.0 * 演示java 的批处理 */ public class Batch_ { //传统方法,添加 5000 条数据到 admin2 @Test public void noBatch() throws Exception { Connection connection = JDBCUtils.getConnection(); String sql = "insert into admin2 values(null, ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); System.out.println("开始执行"); long start = System.currentTimeMillis();//开始时间 for (int i = 0; i < 5000; i++) {//5000 执行 preparedStatement.setString(1, "jack" + i); preparedStatement.setString(2, "666"); preparedStatement.executeUpdate(); } long end = System.currentTimeMillis(); System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702 //关闭连接 JDBCUtils.close(null, preparedStatement, connection); } //使用批量方式添加数据 @Test public void batch() throws Exception { Connection connection = JDBCUtils.getConnection(); String sql = "insert into admin2 values(null, ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); System.out.println("开始执行"); long start = System.currentTimeMillis();//开始时间 for (int i = 0; i < 5000; i++) {//5000 执行 preparedStatement.setString(1, "jack" + i); preparedStatement.setString(2, "666"); //将 sql 语句加入到批处理包中 -> 看源码 /* //1. //第一就创建 ArrayList - elementData => Object[] //2. elementData => Object[] 就会存放我们预处理的 sql 语句 //3. 当 elementData 满后,就按照 1.5 扩容 //4. 当添加到指定的值后,就 executeBatch //5. 批量处理会减少我们发送 sql 语句的网络开销,而且减少编译次数,因此效率提高 public void addBatch() throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { if (this.batchedArgs == null) { this.batchedArgs = new ArrayList(); } for(int i = 0; i < this.parameterValues.length; ++i) { this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i); } this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull)); } } */ preparedStatement.addBatch(); //当有 1000 条记录时,在批量执行 if((i + 1) % 1000 == 0) {//满 1000 条 sql preparedStatement.executeBatch(); //清空一把 preparedStatement.clearBatch(); } } long end = System.currentTimeMillis(); System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108 //关闭连接 JDBCUtils.close(null, preparedStatement, connection); } }九、数据库连接池在说明数据库连接池之间,我们想来想想传统获取连接的问题1.传统获取连接的问题连接底层是socket连接,比较耗费时间,Java程序在连接数据库的时候,它的最大连接数也是有要求的,如果连接过多的话,可能数据库就崩了。 而且,效率还低。---可以自己去测试一下时间所以,为了解决这个问题,我们引入了数据库连接池。将来可能有好多Java程序去连接数据库,那么,如果用传统的连接方式的话,数据库还顶得住吗2.数据库连接池下面的图片对上面做出解释3.C3P0应用实例package com.zyh.jdbc.datasource; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.jupiter.api.Test; import java.io.FileInputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @author zengyihong * * 演示 c3p0 的使用 */ public class C3P0_ { //方式 1: 相关参数,在程序中指定 user, url , password 等 @Test public void testC3P0_01() throws Exception { //1. 创建一个数据源对象 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //2. 通过配置文件 mysql.properties 获取相关连接的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //读取相关的属性值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); //给数据源 comboPooledDataSource 设置相关的参数 //注意: 连接管理是由 comboPooledDataSource 来管理 comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(user); comboPooledDataSource.setPassword(password); //设置初始化连接数 comboPooledDataSource.setInitialPoolSize(10); //最大连接数 comboPooledDataSource.setMaxPoolSize(50); //测试连接池的效率, 测试对 mysql 5000 次操作 long start = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接 口 实现的 //System.out.println("连接 OK"); connection.close(); } long end = System.currentTimeMillis(); //c3p0 5000 连接 mysql 耗时=391 System.out.println("c3p0 5000 连接 mysql 耗时=" + (end - start)) } //第二种方式 使用配置文件模板来完成 //1. 将 c3p0 提供的 c3p0.config.xml 拷贝到 src 目录下 //2. 该文件指定了连接数据库和连接池的相关参数 @Test public void testC3P0_02() throws SQLException { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hsp_edu"); //测试 5000 次连接 mysql long start = System.currentTimeMillis(); System.out.println("开始执行...."); for (int i = 0; i < 500000; i++) { Connection connection = comboPooledDataSource.getConnection(); //System.out.println("连接 OK~"); connection.close(); } long end = System.currentTimeMillis(); //c3p0 的第二种方式 耗时=413 System.out.println("c3p0 的第二种方式(500000) 耗时=" + (end - start));//1917 } 记得先把对应jar包加载在自己的项目中4.德鲁伊连接池记得把jar包导入进来,然后加入配置文件(去官方找),把配置文件拷贝到项目的src目录下面记得修改参数package com.zyh.jdbc.datasource; import com.alibaba.druid.pool.DruidDataSourceFactory; import org.junit.jupiter.api.Test; import javax.sql.DataSource; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.Connection; import java.util.Properties; /** * @author zengyihong * @version 1.0 * 测试 druid 的使用 */ public class Druid_ { @Test public void testDruid() throws Exception { //1. 加入 Druid jar 包 //2. 加入 配置文件 druid.properties , 将该文件拷贝项目的 src 目录 //3. 创建 Properties 对象, 读取配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\druid.properties")); //4. 创建一个指定参数的数据库连接池, Druid 连接池 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); long start = System.currentTimeMillis(); for (int i = 0; i < 500000; i++) { Connection connection = dataSource.getConnection(); System.out.println(connection.getClass()); //System.out.println("连接成功!"); connection.close(); } long end = System.currentTimeMillis(); //druid 连接池 操作 5000 耗时=412 System.out.println("druid 连接池 操作 500000 耗时=" + (end - start));//539 } }5.将 JDBCUtils 工具类改成 Druid(德鲁伊)实现package com.zyh.jdbc.datasource; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * @author zengyihong * * 基于 druid 数据库连接池的工具类 */ public class JDBCUtilsByDruid { private static DataSource ds; //在静态代码块完成 ds 初始化 static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } //编写 getConnection 方法 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //关闭连接, 再次强调: 在数据库连接池技术中,close 不是真的断掉连接 //而是把使用的 Connection 对象放回连接池 public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }package com.zyh.jdbc.datasource; import org.junit.jupiter.api.Test; import java.sql.*; import java.util.ArrayList; /** * @author zengyihong * @version 1.0 */ @SuppressWarnings({"all"}) public class JDBCUtilsByDruid_USE { @Test public void testSelect() { System.out.println("使用 druid 方式完成"); //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "select * from actor where id >= ?"; PreparedStatement preparedStatement = null; ResultSet set = null; //3. 创建 PreparedStatement 对象 try { connection = JDBCUtilsByDruid.getConnection(); System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1);//给?号赋值 //执行, 得到结果集 set = preparedStatement.executeQuery(); //遍历该结果集 while (set.next()) { int id = set.getInt("id"); String name = set.getString("name");//getName() String sex = set.getString("sex");//getSex() Date borndate = set.getDate("borndate"); String phone = set.getString("phone"); System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone); } } catch (SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtilsByDruid.close(set, preparedStatement, connection); } } //使用方法来解决 ResultSet =封装=>Arraylist @Test public ArrayList<Actor> testSelectToArrayList() { System.out.println("使用 druid 方式完成"); //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "select * from actor where id >= ?"; PreparedStatement preparedStatement = null; ResultSet set = null; ArrayList<Actor> list = new ArrayList<>();//创建 ArrayList 对象,存放 actor 对象 //3. 创建 PreparedStatement 对象 try { connection = JDBCUtilsByDruid.getConnection(); System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1);//给?号赋值 //执行, 得到结果集 set = preparedStatement.executeQuery(); //遍历该结果集 while (set.next()) { int id = set.getInt("id"); String name = set.getString("name");//getName() String sex = set.getString("sex");//getSex() Date borndate = set.getDate("borndate"); String phone = set.getString("phone"); //把得到的 resultset 的记录,封装到 Actor 对象,放入到 list 集合 list.add(new Actor(id, name, sex, borndate, phone)); } System.out.println("list 集合数据=" + list); for(Actor actor : list) { System.out.println("id=" + actor.getId() + "\t" + actor.getName()); } } catch (SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtilsByDruid.close(set, preparedStatement, connection); } //因为 ArrayList和 connection 没有任何关联,所以该集合可以复用. return list; } } 6.Apache—DBUtils我相信看完这个,对大家今后学习mybatis也有帮助,可以更好的理解1.引入我们之前,在得到表中的字段的数据的时候,使用的方法比如说getString("字段名"),但是这样有一种弊端,如果我们字段名写错了怎么办,而且getString方法,只知道到返回的是字符串,我们更希望的是,如果表中字段名为xxx,我们可以通过方法getXxx()来获取相关信息这个时候,我们可以在Java程序中,写一个类和表对应,,类名和表名一致,类中的属性和表的字段对应-------->domain(JavaBean)我们有时候需要把得到的结果集返回,给其他地方使用,但是这样就导致没有关闭,影响并发,如果关闭了,其他的地方又没法使用。这个时候,我们想到一个方法,把结果集封装到集合中,这样子在每一个遍历,得到一条表单记录,就把它放在集合当中,这样子后,就算结果集关闭了,但是集合里面的记录仍然存在。 不过要注意一下,在结果集没有遍历结束之前,我们不能关闭结果集。大家可以看看下面的图,可能更加好理解,也比较形象:2.我们自己尝试写一下属性和无参构造器有参构造器提供get和set方法开始写程序模拟//自己尝试来解决 ResultSet =封装=> Arraylist @Test public ArrayList<Actor> testSelectToArrayList() { System.out.println("使用 druid 方式完成"); //1. 得到连接 Connection connection = null; //2. 组织一个 sql String sql = "select * from actor where id >= ?"; PreparedStatement preparedStatement = null; ResultSet set = null; ArrayList<Actor> list = new ArrayList<>();//创建 ArrayList 对象,存放 actor 对象 //3. 创建 PreparedStatement 对象 try { connection = JDBCUtilsByDruid.getConnection(); System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1);//给?号赋值 //执行, 得到结果集 set = preparedStatement.executeQuery(); //遍历该结果集 while (set.next()) { int id = set.getInt("id"); String name = set.getString("name");//getName() String sex = set.getString("sex");//getSex() Date borndate = set.getDate("borndate"); String phone = set.getString("phone"); //在循环的时候,得到一条表的记录 //把得到的 resultset 的记录,封装到 Actor 对象,放入到 list 集合 list.add(new Actor(id, name, sex, borndate, phone)); } System.out.println("list 集合数据=" + list); for(Actor actor : list) { System.out.println("id=" + actor.getId() + "\t" + actor.getName()); } } catch (SQLException e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtilsByDruid.close(set, preparedStatement, connection); } //因为 ArrayList 和 connection 没有任何关联,所以该集合可以复用. return list; }3.Apache——DBUtils我们来想想刚刚自己写的程序,我们要指定把哪一个类封装到集合中,但是这个类是可能发生变化的,封装过程是大致相同的,我们可以把重复的动作封装在工具类中。package com.zyh.jdbc.datasource; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import org.junit.jupiter.api.Test; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * @author zengyihong * */ @SuppressWarnings({"all"}) public class DBUtils_USE { //使用 apache-DBUtils 工具类 + druid 完成对表的 crud 操作 @Test public void testQueryMany() throws SQLException { //返回结果是多行的情况 //1. 得到 连接 (druid) Connection connection = JDBCUtilsByDruid.getConnection(); //2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的jar , 加入到本 Project //3. 创建 QueryRunner QueryRunner queryRunner = new QueryRunner(); //4. 就可以执行相关的方法,返回 ArrayList 结果集 //String sql = "select * from actor where id >= ?"; // 注意: sql 语句也可以查询部分列 String sql = "select id, name from actor where id >= ?"; 解读 (1) query 方法就是执行 sql 语句,得到 resultset ---封装到 --> ArrayList 集合中 (2) 返回集合 (3) connection: 连接 (4) sql : 执行的 sql 语句 (5) new BeanListHandler<>(Actor.class): 在将 resultset -> Actor 对象 -> 封装到 ArrayList 底层使用反射机制 去获取 Actor 类的属性,然后进行封装 (6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数 Object... params (7) 底层得到的 resultset ,会在 query 关闭, 关闭 PreparedStatment 分析 queryRunner.query 方法: public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { PreparedStatement stmt = null;//定义 PreparedStatement ResultSet rs = null;//接收返回的 ResultSet Object result = null;//返回 ArrayList try { stmt = this.prepareStatement(conn, sql);//创建 PreparedStatement this.fillStatement(stmt, params);//对 sql 进行 ? 赋值 rs = this.wrap(stmt.executeQuery());//执行 sql,返回 resultset result = rsh.handle(rs);//返回的 resultset --> arrayList[result] [使用到反射,对传入 class 对象 } catch (SQLException var33) { this.rethrow(var33, sql, params); } finally { try { this.close(rs);//关闭 resultset } } finally { this.close((Statement)stmt);//关闭 preparedstatement 对象 } return result; } List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1); System.out.println("输出集合的信息"); for (Actor actor : list) { System.out.print(actor); } //释放资源 JDBCUtilsByDruid.close(null, null, connection); }运行结果//演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象) @Test public void testQuerySingle() throws SQLException { //1. 得到 连接 (druid) Connection connection = JDBCUtilsByDruid.getConnection(); //2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的jar , 加入到本 Project //3. 创建 QueryRunner QueryRunner queryRunner = new QueryRunner(); //4. 就可以执行相关的方法,返回单个对象 String sql = "select * from actor where id = ?"; // 老韩解读 // 因为我们返回的单行记录<--->单个对象 , 使用的 Hander 是 BeanHandler Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10); System.out.println(actor); // 释放资源 JDBCUtilsByDruid.close(null, null, connection); } //演示 apache-dbutils + druid 完成查询结果是单行单列-返回的就是 object @Test public void testScalar() throws SQLException { //1. 得到 连接 (druid) Connection connection = JDBCUtilsByDruid.getConnection(); //2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的jar , 加入到本 Project //3. 创建 QueryRunner QueryRunner queryRunner = new QueryRunner(); //4. 就可以执行相关的方法,返回单行单列 , 返回的就是 Object String sql = "select name from actor where id = ?"; // 解读: 因为返回的是一个对象, 使用的 handler 就是 ScalarHandler Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4); System.out.println(obj); // 释放资源 JDBCUtilsByDruid.close(null, null, connection); }运行结果://演示 apache-dbutils + druid 完成 dml (update, insert ,delete) @Test public void testDML() throws SQLException { //1. 得到 连接 (druid) Connection connection = JDBCUtilsByDruid.getConnection(); //2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的jar , 加入到本 Project //3. 创建 QueryRunner QueryRunner queryRunner = new QueryRunner(); //4. 这里组织 sql 完成 update, insert delete //String sql = "update actor set name = ? where id = ?"; //String sql = "insert into actor values(null, ?, ?, ?, ?)"; String sql = "delete from actor where id = ?"; 解读 (1) 执行 dml 操作是 queryRunner.update() (2) 返回的值是受影响的行数 (affected: 受影响) int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966- 10- 10", "116"); int affectedRow = queryRunner.update(connection, sql, 1000 ); System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表"); // 释放资源 JDBCUtilsByDruid.close(null, null, connection); } } 十、DAO 和增删改查通用方法-BasicDao1.分析问题想操作哪一张表,就用对应的dao2.基本说明3.应用说明因为其他的都是类似的操作,这里就以其中一个表来演示,以Actor为例domain----类映射数据库的表domainpackage com.zyh.dao_.domain; import java.util.Date; /** * @author zengyihong * * Actor 对象和 actor 表的记录对应 * */ public class Actor { //Javabean, POJO, Domain 对象 private Integer id; private String name; private String sex; private Date borndate; private String phone; public Actor() { //一定要给一个无参构造器[反射需要] } public Actor(Integer id, String name, String sex, Date borndate, String phone) { this.id = id; this.name = name; this.sex = sex; this.borndate = borndate; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBorndate() { return borndate; } public void setBorndate(Date borndate) { this.borndate = borndate; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "\nActor{" + "id=" + id + " name='" + name + '\'' + ", sex='" + sex + '\'' + ", borndate=" + borndate + ", phone='" + phone + '\'' + '}'; } }utilspackage com.zyh.dao_.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * @author zengyihong * @version 1.0 * 基于 druid 数据库连接池的工具类 */ public class JDBCUtilsByDruid { private static DataSource ds; //在静态代码块完成 ds 初始化 static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } //编写 getConnection 方法 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //关闭连接, 再次强调: 在数据库连接池技术中,close 不是真的断掉连接 //而是把使用的 Connection 对象放回连接池 public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }BASICDaopackage com.zyh.dao_.dao; import com.hspedu.dao_.utils.JDBCUtilsByDruid; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; /** * @author zengyihong * * 开发 BasicDAO , 是其他 DAO 的父类, 使用到 apache-dbutils */ public class BasicDAO<T> { //泛型指定具体类型 private QueryRunner qr = new QueryRunner(); //开发通用的 dml 方法, 针对任意的表 public int update(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); int update = qr.update(connection, sql, parameters); return update; } catch (SQLException e) { throw new RuntimeException(e); //将编译异常->运行异常 ,抛出 } finally { JDBCUtilsByDruid.close(null, null, connection); } } //返回多个对象(即查询的结果是多行), 针对任意表 /** * * @param sql sql 语句,可以有 ? * @param clazz 传入一个类的 Class 对象 比如 Actor.class * @param parameters 传入 ? 的具体的值,可以是多个 * @return 根据 Actor.class 返回对应的 ArrayList 集合 */ public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters); } catch (SQLException e) { throw new RuntimeException(e); //将编译异常->运行异常 ,抛出 } finally { JDBCUtilsByDruid.close(null, null, connection); } } //查询单行结果 的通用方法 public T querySingle(String sql, Class<T> clazz, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters); } catch (SQLException e) { throw new RuntimeException(e); //将编译异常->运行异常 ,抛出 } finally { JDBCUtilsByDruid.close(null, null, connection); } } //查询单行单列的方法, 即返回单值的方法 public Object queryScalar(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new ScalarHandler(), parameters); } catch (SQLException e) { throw new RuntimeException(e); //将编译异常->运行异常 ,抛出 } finally { JDBCUtilsByDruid.close(null, null, connection); } } }ActorDAOpackage com.zyh.dao_.dao; import com.hspedu.dao_.domain.Actor; /** * @author zengyihong * @version 1.0 */ public class ActorDAO extends BasicDAO<Actor> { //1. 就有 BasicDAO 的方法 //2. 根据业务需求,可以编写特有的方法. } package com.hspedu.dao_.test; import com.hspedu.dao_.dao.ActorDAO; import com.hspedu.dao_.domain.Actor; import org.junit.jupiter.api.Test; import java.util.List; /** * @author 韩顺平 * @version 1.0 */ public class TestDAO { //测试 ActorDAO 对 actor 表 crud 操作 @Test public void testActorDAO() { ActorDAO actorDAO = new ActorDAO(); //1. 查询 List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1); System.out.println("===查询结果==="); for (Actor actor : actors) { System.out.println(actor); } //2. 查询单行记录 Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6); System.out.println("====查询单行结果===="); System.out.println(actor); //3. 查询单行单列 Object o = actorDAO.queryScalar("select name from actor where id = ?", 6); System.out.println("====查询单行单列值==="); System.out.println(o); //4. dml 操作 insert ,update, delete int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000- 11- 11", "999"); System.out.println(update > 0 ? "执行成功" : "执行没有影响表"); } }
@TOC本文是观看计算机网络--微课堂所写计算机网络这个思维导图是第一章所讲内容 ,当看完后,可以回过头来看看自己能否根据思维导图中的内容回想起相关内容第一章 概述1.1计算机网络在信息时代的作用计算机网络已经由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络成为生活中不可或缺的一部分1.2因特网概述1.2.1网络、互联网(互连网)和因特网网络由若干个结点和连接这些结点的链路组成笔记本电脑是一个结点,台式电脑是一个结点,网路打印机是一个结点,把它们互连起来的交换机也是一个结点,结点之间的链路可以是有线链路或无线链路多个网络还可以通过路由器互连起来,这样就构成了一个覆盖范围更大的网络,即互联网因此,互联网是网络的网络如图所示,这些网络可以通过路由器互连,形成更大的互联网因特网(Internet)是世界上最大的互连网络(用户数以亿计,互连的网络数以百万计)因特网常用一朵云来表示连接在因特网上的计算机称为主机1.2.2因特网发展的三个阶段2.2.1因特网服务提供者ISP普通用户是通过ISP接入因特网的,ISP可以从因特网管理机构申请到成块的IP地址,同时拥有通信线路以及路由器等连网设备,任何机构和个人只要向IP交纳相应的费用,就可以从ISP得到所需要的IP地址。这点其实很重要,因为因特网上的主机都必须有IP地址才能进行通信,这样就可以通过这个ISP接入因特网我国主要的ISP是中国移动,中国电信,中国联通三大电信运营商根据提供服务的覆盖面积大小以及所拥有的IP地址数量的不同,ISP也分成不同的层次2.2.2基于ISP的三层结构的因特网第一层ISP通常被称为因特网主干网,一般都能覆盖国际性区域范围,拥有高速链路和交换设备,第一次ISP之间直接互联第二次ISP和一些大公司都是第一层ISP的用户,第一层ISP通常具有区域性或国家兴覆盖规模,和少数第一次ISP相连接第三层ISP又称为本地ISP,它们是第二次ISP的用户,而且只拥有本地范围的网络,一般的校园网或企业网,以及住宅用户和无线移动用户,都是第三层ISP的用户经常遇到以下这种情况:相隔较远的两台主机之间的通信可能需要经过多个ISP,一旦某一个用户能够接入到因特网,那么他也可以成为ISP,需要做的就算购买一些比如调制解调器或路由器这样的设备,让其他用户能和他相连1.2.3因特网的标准化工作因特网的标准化工作对因特网的发展起到很重要的作用因特网在制定其标准上的一个很大的特点是面向公众因特网所有的RFC(Request for comments)技术文档都可以从因特网上免费下载任何人都可以随时调用电子邮件发表对某一个文档的意见或建议因特网协会ISOC是一个国际性组织,它负责对因特网进行全面管理,以及在世界范围内促进其发展和使用因特网体系结构委员会IAB,负责管理因特网有关协议的开发因特网工程部IETF,负责研究中短期工程问题,主要针对协议的开发和标准化因特网研究部IRTF,从事理论方法的研究和开发一些需要长期考虑的问题制定因特网的正式标准需要经过以下四个阶段(1)因特网草案(在这个阶段还不是RFC文档)(2)建议标准(从这个阶段开始成为RFC文档)(3)草案标准(4)因特网标准不是所有的RFC文档都能成为因特网标准,只有一小部分RFC文档最后才能成为因特网标准1.2.4因特网的组成边缘部分由所有连接在因特网上的主机组成的,这部分是用户直接使用的,用来进行通信(传送数据、音频或视频)和资源共享核心部分由大量网络和连接这些网络的路由器组成,这部分是为边缘部分提供服务的(提供连通性和交换)1.3三种交换方式1.3.1电路交换诞生背景电话诞生后,人们发现想让所有电话机两两相连接是不现实的,如果电话机数量大,这种连接方式就不现实。所以人们就发现,应该让一个中间设备,把这些电话机连接起来, 这个中间设备就是电话交换机。电话交换机可以看成是一个有很多开关的开关器,可以把需要通信的两部电话的电话线路按照需求接通,大大减少电话机连接的数量当电话机数量增多,就要使用很多彼此连接起来的电话交换机来完成全网交换任务,用这样的方法,就构成了覆盖全世界的电信网。相关概念电话交换机接通电话线的方式叫电路交换从通信资源的分配角度来看,交换就是按照某种方式动态地分配传输线路的资源电路交换的三个步骤①建立连接(分配通信资源)-----例如,使用电话交换打电话之前,必须先拨号请求连接②通话(一直占用通信资源)------其他用户无法再打电话进来③释放连接(归还通信资源)------电话挂断,把占用资源还给电信网中继线由许多用户共享,用户线是电话用户专用我们来思考一个问题:能不能用电路交换的方式传输计算机数据?尽管采用电路交换可以实现计算机之间的数据传送,但是线路的传输效率往往很低。计算机传输的数据是突发式的传输的,比如说:用户在输入和编写一份还没有传输的文件的时候,用户占用的通信资源这个时候还没有得到利用,但是这个通信资源也不能被其他用户利用,就会造成资源浪费,所以计算机网络通常采用分组交换。1.3.2分组交换(重点)概念因特网中,最重要的分组交换机就是路由器,负责把各种网络连接起来,并且对接收到的分组进行转发-也就是分组交换。例子如图:假设用户h6要给h2发消息,通常我们把表示信息的整块数据称为一个报文,在发送之前,我们先把较长的报文划分成为一个个更小的等长数据段,在每一个数据段的前面,加上一些由必要的控制信息组成的首部(简称包头)后,就构成一个分组(简称为包)首部的作用:包含分组的目的地址,否则分组传输路径的各个分组交换机,就不知道如何转发分组分组交换机收到一个分组后,先把分组暂时存储下来,然后检查首部,按照首部中的目的地址进行查表转发,转发给下一个分组交换机。各个交换机进行对分组信息的储存转发后,最终到达主机H2。目的地处理及再去除分组首部,还原出报文。传输完成。分组传输的特点:①各个分组从源站到目的站可以走不同路径②分组到达目的站的顺序不一定和分组在源站的发送顺序相同分组中各个角色的功能:1.3.3报文交换(现在较少使用) 报文交换中的交换节点也采用存储转发的方式,但报文交换对报文大小没有限制1.3.4三种交换方式对比1.4计算机网络的定义和分类计算机网络目前还没有精确的定义计算机网络最简单的定义:一些互相连接的、自治的计算机的集合。互连:计算机之间可以通过有线或者无线的方式进行数据通信自治:独立的计算机,有自己的硬件和软件,可以单独运行使用集合:至少要两台计算机计算机网络较全面的定义:计算机网络主要是一些通用的、可编程的硬件互连而成的,而硬件并非专门用来实现某一特定目的的,这些可编程的硬件能够用来传送多种不同类型的数据(如音频、数据),这些可编程的软件能支持广泛和日益增长的应用。计算机网络连接的硬件,不局限于一般的计算机,还包括智能手机等智能硬件计算机网络不是专门用来传送数据,而是能支持很多应用(包括以后可能出现的应用)计算机网络的分类公用网:只要愿意按照电信公司的规定交纳费用的人,都可以使用这种网络专用网:某个部门为本单位的特殊业务工作的需要建造的网络,不对外提供有线网络:包括双绞线网络,光纤网络无线网络:主要有WiFi广域网WAN:覆盖范围一般是几十公里到几千公里,可以覆盖国家,地区,甚至是横跨几个洲,有时候也成为远程网,广域网是因特网的核心部分,负责互连分布在不同区域的城域网和局域网,是最大范围的网络。城域网MAN:覆盖范围一般是一个城市。作用距离为5到50公里。通常作为城市骨干网,互连大量企业、机构、学校。局域网LAN:局域网一般是微信计算机或工作站通过告诉线路相连,范围一般是一个实验室,一个校园,一栋楼,通常由某个单位单独拥有、使用和维护。个域网PAN:个人区域网络。不是用来连接普通计算机,而是在个人工作的地方把个人使用的电子设备,鼠标、键盘、耳机等用无线的方式连接起来形成的个人网络系统。也常常称为无限个人区域网WPAN,覆盖范围大约10米总线型网络:使用单根传输线把计算机连接起来优点:建网冗余,增减结点方便,节省线路缺点:重负载时通信效率不高,总线只要有一个地方出现故障,全网瘫痪星型网络:每个计算机都以单独的线路和中央设备相连,中央设备现在一般是交换机或路由器优点:便于网络的集中控制和管理缺点:成本高,中央设备对故障敏感环形网络:把所有计算机的网络接口连接成一个环,环可以是单环,双环,环中信号是单向传输的网状型网络一般情况,每个结点至少由两个路径和其他结点相连,多用在广域网优点:可靠性高缺点:控制复杂,线路成本高注意:以上四种基本的网络拓扑还可以互连成更复杂的网络1.5计算机网络的性能指标速率先了解一下比特比特:计算机数据量的单位,一个比特就是二进制的1或0常用数据量单位:1Byte=8bit Byte简写为B,比特简写为b 1KB=2^10Byte 1MB=K*KB=2^20B 1GB=K*MB=2^30B 1TB=K*GB=2^40B速率:连接在计算机网络的主机在数组信道上的传送比特的速率,也称比特率或数据率常用数据率单位bit/s (b/s, bps) kb/s=10^3 b/s (bps) Mb/s=k*kb/s=10^6b/s Gb/s=K*Mb/s=10^9 b/s Tb/s=k*Gb/s=10^12 b/s注意:在数据量单位中,1K=2^10在数据率单位中,1k=10^3这两个的区别,一定一定一定要注意区别,不然会做错!!!!!!!带宽带宽在模拟信号系统中的意义信号包含的各种不同频率成分所占的频率范围单位:Hz(kHZ MHz GHz)在传统的通信线路上传送的电话信号的标准带宽为3.1kHz,范围从300Hz到3.4kHz,这是话音主要成分的范围带宽在计算机网络的意义用来表示网络的通信线路所能传送的数据的能力,因此网络带宽表示在单位时间内从网络中的某一点到另一点所能通过的最高数据率单位:b/s(kb/s, Mb/s, Gb/s, Tb/s),与速率相同。在日常生活中的宽带带宽除以8,一般就是平时使用中的最高传输速率。(1B = 8bit)是一个很重要的计算机网络性能指标。直接关系网络的应用体验。吞吐量吞吐量表示在单位时间内通过某个网络(或信道、接口)的数据量吞吐量被经常用于对现实世界中的网络的一种测量,以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的 带宽或额定速率的限制。时延发送时延:源主机将分组发往传输线路产生的时延公式:分组长度(b)/发送速率(b/s)发送速率 由网卡发送速率,信道带宽,交换机或路由器的接口速率的最小速率所决定传播时延:分组的电信号在链路上传输产生的时延公式:信道长度(m)/电磁波传输速率(m/s)电磁波在不同介质的传播速率不同自由空间:3×10^8 m/s铜线:2.3×10^8 m/s光纤:2.0×10^8 m/s处理时延:路由器接到分组后,对其存储转发的时延不方便计算网络中的数据流量动态变化,因此路由器的繁忙程度也是动态变化而且路由器的软硬件性能可能不同处理时延在题目上一般忽略不计在处理时延忽略不计的情况,发送时延和传播时延哪一个占据主导需要具体问题具体分析时延带宽积传播时延和带宽的乘积我们可以把传输链路看成管道往返时间信息很多情况是双向交互有时候想知道双向交互一次的时间往返时间RTT是重要的性能指标利用率总结1.6计算机网络体系结构(重点难点)1.6.1常见的计算机网络体系结构为了让计算机在世界范围内互连,提出OSI体系结构——发系统互连参考模型OSI体系结构过于复杂,而且效率低,层次划分不合理TCP/IP体系结构TCP/IP体系结构把OSI体系结构的物理层和数据链路层合并成网络接口层去掉了会话层和表示层TCP/IP在网络层使用的协议是IP协议(网际协议)TCP/IP体系结构的网络接口层没有规定什么内容,目的是为了互连全世界的网络接口所以实际上TCP/IP协议只有三层:网际层、运输层、应用层。IP协议是网际协议的核心协议,包含TCP和UDP两个协议1.6.2计算机网络体系结构分层的必要性计算机网络很复杂分层是为了更好解决问题,把大问题分为小问题,这样有利于研究各个层次解决的问题物理层:采用怎样的传输媒体(介质)采用怎样的物理接口使用怎样的信号表示比特0和1解决物理层的问题后,主机间就可以发送信号来传输比特0或1了数据链路层:接下来看一下在总线型的网络上,还面临什么问题主机A要给主机C发数据,但是表示数据的信号会通过总线传播到总线上的每一台主机主机C怎么知道数据是发给他的?其他主机又怎么知道数据不是发给自己的?所以要怎么表示网络中的主机呢(主机编址问题,例如MAC地址)主机发送数据的时候要加上目的地址,每台主机根据接收的地址和自身地址来看是否发给自己那又怎么从信号所表示的一连串比特流中区分出地址和数据可能某一时刻主线空闲,但是下一时刻,有多个主机发送信息给总线,可能发生信号碰撞所以要怎么协调主机争用总线说明:总线型网络已经淘汰现在常用的是以太网交换机把多台主机互连形成的交换式以太网解决此问题后可以实现分组在一个网络上传输。网络层:如何标识各网络以及网络中的各主机(网络和主机共同编址的问题,例如IP地址)?源主机和目的主机的传输路径往往不止一条,路由器如何转发分组,如何进行路由选择?解决物理层、数据链路层、网络层,就可以实现分组在网络间的传输问题但是一台主机可能有不同的进程,某一时刻主机收到来自服务器的分组,这些分组交给谁来分组呢?运输层:如何解决进程之间基于网络的通信?出现传输错误时如何处理?解决了物理层,数据链路层,网络层,运输层各自的问题后,我们就可以实现进程间基于网络的互相通信应用层:通过应用进程间的交互来完成特定的网络应用。例如:支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议。1.6.3计算机网络体系结构分层思想举例以主机访问web服务器为例,他们之间的网络通信,实际上是主机上的进程之间的通信应用层:按照HTTP协议的规定,构建一个HTTP请求报文,应用层把HTTP请求交给运输层处理运输层给HTTP请求报文加上TCP首部,让它称为TCP报文段首部的作用主要是为了区别不同进程,以及实现可靠传输运输层把TCP报文段交给网络层处理网络层给TCP报文段加上IP首部,让它成为IP数据报,然后将其交付给数据链路层处理。数据链路层给IP数据包添加首部和尾部使之成为帧其首部的作用是使其能够在一段链路或者网络上传输,以及被目的交换机接收并处理。其尾部的作用是为了让目的主机检查是否有误码。最后将其交给物理层。物理层:将帧看作是比特流(01编码),由于是在以太网传输,因而给其加上前导码,便于传输。并且将其变成相应的信号发送到传输媒体。此时,发送端处理结束。1.6.4计算机网络体系结构的专用术语实体:任何可以发送或者接收信息的硬件或软件进程对等实体:收发双方相同层次的实体协议:控制两个对等实体进行逻辑通信的规则的集合这种通信并不存在,是我们假设出来的协议三要素:语法 语义 同步语法:定义交换信息的格式语法定义了所交换信息由那些字段以及何种顺序构成。语义定义通信双方所要完成的操作。例如,主机HTTP的GET请求给Web服务器,Web服务器收到后执行相应的操作,然后给主机发回HTTP的响应。同步定义通信双方的时序关系。例如,TCP的“三报文握手”建立连接。服务1.6.5总结1.7章节小结第一章主要是概述性的东西,概念比较多,大家不理解也没关系,可以再后续学习中,不断的复习,回头看看第一章的概念性的东西,最后争取自己能用语言组织出来接下来,大家看看能否根据思维导图来回忆起相关内容。如果做到了,恭喜你掌握了,有遗忘的地方可以再回头看看。如果觉得本文对大家有一定的帮助,可以点赞收藏,方便后续复习
在idea中报错Cannot start process, the working directory '*' does not exist。虽然反复看自己的路径,文件名等都没有问题,但是点击运行就出错。报错原因是:idea没有工作空间这个概念,所以在创建工程的时候指定的工作区间,idea无法找到对应的工程,导致编译不通过。我们要设置一下,idea才会识别到你的工程位置。 # 1.点击RUN,选中edit configurations  # 2.然后,找到我们的工作目录,working directory,把他改成$MODULE_DIR $  # 3.然后再点击运行就可以了 这也是我第一次遇到这种情况,希望这个方法对大家有帮助。
递归递归就是方法自己调用自己规则 1.执行一个方法的时候,就创建一个新的受保护的独立空间(栈空间) 2.方法的局部变量是独立的,不会互相影响 3.如果方法中使用的是引用数据类型变量,就会共享该引用类型的数据 4.递归必须要有结束条件,不然会出现栈溢出 5.递归占用很大的内存 6.当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就把结果返回给谁,同时当方法执行完毕或者返回的时候,这个方法也就执行完毕了 递归调用的执行机制public class Recursion01 { //编写一个 main 方法 public static void main(String[] args) { T t1 = new T(); t1.test(4);//输出什么? n=2 n=3 n=4 } } class T { //分析 public void test(int n) { if (n > 2) { test(n - 1); } System.out.println("n=" + n); } }以上面那一个作为例子:首先进入main方法,然后在栈内存开辟空间,接着执行main方法里面的代码片段,当main方法调用test()方法的时候,会在栈中另外开辟一个新的空间,然后把实参4传进去由于,if(n>2)判断条件为true,所以会执行test(n-1)方法,在栈中另外开辟新的空间,直到n=2的时候,判断条件不成立,这个时候控制台输出2,然后n=2的这个方法结束了,这个栈空间就会释放掉,再回到 n = 3 这个空间的text(n -1);继续执行后面的语句,直到程序结束。
在说明映射文件规则之前,先来回顾一下ORM相关概念。1.ORM概念ORM(Object Relationship Mapping)对象关系映射对象:Java的实体类对象关系:关系型数据库映射:二者之间的对应关系字段名和属性名要一一对应才可以,它们的名字要相同,底层调用的是反射机制Java概念数据库概念属性列,字段类表对象记录# 2.映射文件命名规则 表对应的实体类的类名+Mapper.xml 举例:假如数据库的表的名字是t_user,它对应的实体类是User,那么对应的映射文件为UserMapper.xml 一个映射文件对应一个实体类,对应一张表的操作,调用Mapper中的方法就是来执行SQLMybatis映射文件用来写SQL语句,访问和操作表的数据Mybatis映射文件存放位置是src/main/resources/mappers目录下面3.Mybatis的两个一致Mybatis可以面向接口操作数据,如果我们以包为单位引入映射文件,需要有两个一致① 映射文件的namespace要和mapper接口的全类名一致当调用Mapper接口中的方法,它会先根据Mapper接口的全类名去找到映射文件,然后根据方法名去找到对应的SQL语句②映射文件中SQL语句的id要和mapper接口中的方法名一致4.总结创建mybatis的步骤创建maven工程在pom.xml中引入相关依赖,比如数据库驱动,mybatis,junit单元测试,log4j日志在src/main/java建包3.1 在pojo包下面创建对应的实体类 注:实体类对应数据库表的记录 也就是说数据库查询出来的结果要以什么方式返回3.2 在mapper包下面创建mapper接口,里面定义操作数据库中表的相关方法在resources目录下面建mybatis的核心配置文件<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- The content of element type "configuration" must match " (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?, databaseIdProvider?,mappers?)".-->--><properties resource="jdbc.properties"/><typeAliases><!-- typeAlias:设置某个类型的别名 属性: type:设置需要设置别名的类型 alias:设置某个类型的别名,如果不设置该属性,则这个类型有默认的别名,而且类名不区分大小写--> <typeAlias type="com.atguigu.mybatis.mybatis.pojo.User" alias="User"></typeAlias> <package name="com.atguigu.mybatis.pojo"/> </typeAliases> <!-- 环境可以有多个,我们用默认的环境--> <!-- enviments:配置连接数据库的环境 id:表示连接数据库环境的唯一标识,不能重复--> <environments default="development"> <environment id="development"> <!-- transactionManager设置事务管理方式 type=DBC|MANAGED JDBC:表示在当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理 MANAGED:表示被谁管理,例如Spring--> <transactionManager type="JDBC"/><!-- type用来设置数据源的类型type=POOLED|UNPOOLED|JNDIPOOLED:表示使用的是数据库连接池缓存数据库连接UNPOOLED:表示不使用数据库连接池JNDI:表示使用的是上下文中的数据源--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 数据源就是连接是连接数据库的信息--> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <!-- type=POOLED表示使用数据库连接池--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!-- 引入映射文件--> <!-- 每一个Mapper.xml都需要在Mybatis的核心配置文件中注册--><!-- mapper文件的位置是为了找到要执行的sql语句resources属性指定的是mapper文件的路径这个路径是从target/classes路径开始的 用/作为分隔符--><mappers>--><!-- 以包为单位引入映射文件 要求: 1.mapper接口所在的包要和映射文件所在的包一致 2.mapper接口要和映射文件的名字一致 --> <package name="com.atguigu.mybatis.mapper"/> </mappers> 4. 在resources目录下面建立mapper映射文件  5. 测试 //加载核心配置文件 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //获取SqlSessionFactoryBuilder SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //获取SqlSessionFactory 工厂模式 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); //获取mybatis操作的会话对象 //sqlSession默认是不自动提交事务的,如果我们写上参数true,就代表自动提交 SqlSession sqlSession = sqlSessionFactory.openSession(true); //获取mapper接口的对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.insertUser(); //提交事务// sqlSession.commit(); System.out.println("结果:"+i);
第一步在pom.xml添加依赖<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>第二步在Mybatis核心配置文件中设置分页插件<plugins> <!--设置分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>测试分页功能/**limit index,pageSize * index:当前页的起始索引 * pageSize:每页显示的条数 * pageNum:表示当前页的页码 * index=(pageNum-1)*pageSize * @author zengyihong * @create 2022--04--05 19:34 */ @Test public void testPageHelperTest() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = build.openSession(true); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); PageHelper.startPage(1, 4); List<Emp> list=mapper.selectByExample(null); for (Emp emp:list){ System.out.println(emp); } }①在查询功能之前使用PageHelper.startPage(int pageNum, intpageSize)开启分页功能pageNum:当前页的页码 pageSize:每页显示的条数②在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list,int navigatePages)获取分页相关数据 list:分页之后的数据navigatePages:导航分页的页码数 ③分页相关数据PageInfo{ pageNum=8, pageSize=4, size=2, startRow=29, endRow=30,total=30, pages=8, list=Page{count=true, pageNum=8, pageSize=4,startRow=28, endRow=32, total=30, pages=8, reasonable=false,pageSizeZero=false}, prePage=7, nextPage=0, isFirstPage=false,isLastPage=true, hasPreviousPage=true, hasNextPage=false,navigatePages=5, navigateFirstPage4, navigateLastPage8,navigatepageNums=[4, 5, 6, 7, 8] }常 用 数 据 : pageNum:当前页的页码 pageSize:每页显示的条数 size:当前页显示的真实条数 total: 总 记 录 数 pages: 总 页 数 prePage:上一页的页码 nextPage:下一页的页码isFirstPage/isLastPage:是否为第一页/最后一页hasPreviousPage/hasNextPage:是否存在上一页/下一页navigatePages:导航分页的页码数navigatepageNums:导航分页的页码,[1,2,3,4,5]
@TOC缓存就是把我们查询到的数据进行记录,那么下一次我们再进行数据查询的时候,就不会从数据库中查询数据,而是直接从缓存中取出数据一、Mybatis的一级缓存1.概述mybatis的缓存分成一级缓存和二级缓存,一级缓存是默认开启的。一级缓存的范围是SqlSession级别的,当我们用SqlSession来查询数据的时候,如果下一次再使用相同的SqlSession进行查询的时候,就会直接从缓存中取数据,如果没有才从数据库中取数据。缓存只针对查询功能有效接下来测试一下,为了方便看信息,我们在resources中加入log4j配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout> </appender> <logger name="java.sql"> <level value="debug" /> </logger> <logger name="org.apache.ibatis"> <level value="info" /> </logger> <root> <level value="debug" /> <appender-ref ref="STDOUT" /> </root> </log4j:configuration>然后进行测试2.一级缓存失效的四种情况让一级缓存失效的四种情况1.不同的SqlSession对应不同的一级缓存如果我们用不同的SqlSession来查询的话,就会执行sql语句,而不是从缓存中取出数据,我们来看看测试2.同一个SqlSession但是查询条件不同3.同一个SqlSession两次查询期间执行了任何一次增删改操作我们都知道缓存是为了提高查询速度的,但是如果数据发生了改变,我们还从缓存中继续取出数据,那么获得的数据就不准确了,所以很好理解,增删改后,就不应该从缓存中取数据,这种设计很合理。在mapper接口定义增加数据的方法public interface CacheMapper { List<Emp> getEmpByEid(@Param("id") Integer eid); void insertEmp(Emp emp); 在mapper接口的映射文件写相应的sql语句<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.atguigu.mybatis.mapper.CacheMapper"> <insert id="insertEmp"> insert into t_emp values (null,#{empName},#{age},#{sex},#{email},null); </insert> <select id="getEmpByEid" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp where eid= #{id} </select> </mapper>测试4.同一个SqlSession两次查询期间手动清空了缓存3. 缓存和缓冲的区别缓存与缓冲的区别二、Mybatis的二级缓存1.概述二级缓存是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果是会被缓存的,此后如果再次执行相同的查询语句,结果就会从缓存中获取。二级缓存需要手动开启。2.二级缓存开启的条件1.在核心配置文件中,设置全局配置属性cacheEnabled="true" (这个是默认的)2.在映射文件中设置标签\<cache/>3二级缓存必须在SqlSession关闭或提交以后才有效如果我们没有提交SqlSession或者关闭SqlSession的时候,那么数据就会保存到一级缓存当中补充一个知识点:Cache Hit Ratio是缓存命中率的意思先来看看官方解释这是一个计算机术语,终端用户访问加速节点时,如果该节点有缓存住了要被访问的数据时就叫做命中,如果没有的话需要回原服务器取,就是没有命中。取数据的过程与用户访问是同步进行的,所以即使是重新取的新数据,用户也不会感觉到有延时。 命中率=命中数/(命中数+没有命中数), 缓存命中率是判断加速效果好坏的重要因素之一。其实这个解释简单来说就是我们在查数据的时候,如果缓存中有数据,我们就可以从缓存中取数据,代表命中一次。如果缓存中没有数据,就得从数据库中取数据,代表没有命中一次。命中率=命中数/(命中数+没有命中数)4.查询的数据所转换的实体类型必须实现序列化的接口3.二级缓存失效的条件使得二级缓存失效的条件是在两次查询之间执行了任意的增删改,会使得一级缓存和二级缓存同时失效。4.二级缓存的相关配置缓存是缓存到我们的内存中的,数据是不能无限缓存到我们的内存中的在mapper配置文件中添加cache标签可以设置一些属性eviction属性:缓存回收策略LRU(Least Recently Used)--最近最少使用的:移除最长时间不被使用的对象(我们检测到在指定的一段时间内,某些给数据的缓存最少使用,我们可以先把它给回收掉,为后面的缓存提供空间)FIFO(Firse in Firse out)--先进先出:按照对象进入缓存的顺序来移除它们。SOFT--软引用:移除基于垃圾回收器状态和软引用规则的对象WEAK--弱引用:更积极地移除基于垃圾回收器状态和弱引用规则的对象。默认的是LRUflushInterval属性:刷新间隔,单位毫秒默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(调用增删改的语句)时刷新size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出readOnly属性:只读,true/falsetrue:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存(不仅可以读取缓存中的数据,还可以写缓存中的数据,把缓存的数据拷贝一份出来,返回给调用者,对这个数据进行读和写,对实际的数据没有影响);会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。5.Mybatis缓存查询的顺序先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。 如果二级缓存没有命中,再查询一级缓存如果一级缓存也没有命中,则查询数据库SqlSession关闭之后,一级缓存中的数据会写入二级缓存我们可以使用其他技术来代替mybatis的二级缓存,但是不能代替一级缓存6.整合第三方缓存EHCache①添加依赖(放在pom.xml中)<!-- Mybatis EHCache整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <!-- slf4j日志门面的一个具体实现 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>②各个jar包功能jar包名称jar包功能mybatis-ehcacheMybatis和EHCache的整合包ehcacheEHCache核心包slf4j-apiSLF4J日志门面包logback-classic支持SLF4J门面接口的一个具体实现### ③创建EHCache的配置文件ehcache.xml(名字必须叫ehcache) 在resources目录下面创建<?xml version="1.0" encoding="utf-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\atguigu\ehcache"/> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>④设置二级缓存类型<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>⑤加入logback日志存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件logback.xml<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 - -> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="DEBUG"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.atguigu.crowd.mapper" level="DEBUG"/> </configuration>
@TOC前言mybatis的动态sql是一种根据特定的条件来进行动态拼接sql语句的字符串问题,动态 SQL 是 MyBatis 的强大特性之一。使用过 JDBC 或其它类似的框架,大家应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。下面,我就以t_emp表来讲解if标签我们在查询数据的时候,一般来说都会根据条件来进行查询,现在我们来根据emp_name和sex来进行数据查询 先不用动态sql来写<select id="selectEmpByNameAndSex" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp where emp_name=#{empName} and sex=#{sex} </select>当我们传入的名字为空的时候,数据就查不出来了而且以后做项目的时候,通常是由用户在浏览器输入信息,然后后台根据返回的信息进行查询,我们能不能动态进行查询,也就是说当字段名为null或者等于字符串为空的时候,我们就 不把这个字段作为查询条件这个时候就得使用动态sql了我们很容易想到,可以进行判断,如果不满足条件的话,就不作为查询条件,可以用if标签但是现在这样写的话,其实是有问题的,如果我们的查询条件都为空,那么在sql语句中where后面就没有任何条件了,这样会报错 ,所以,我们肯定希望where关键字也是动态生成的,如果查询条件不存在,那么where关键字就不写出来。我们可以用where标签来解决。if+where <select id="selectEmpByNameAndSex" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp <where> <if test="empName !=null and empName !=''"> emp_name=#{empName} </if> <if test="sex !=null and sex !=''"> and sex=#{sex} </if> </where> </select>where标签可以动态生成where,而且当where后面直接跟上的是and的话,可以把and给省略掉 如果where标签没有任何内容的话,where关键字也不生成 注意:where标签可以自动生成where关键字,并且把内容之前多余的and或者or去掉 当where标签中没有内容时,此时where标签没有任何效果,它不生成where关键字 where标签不能把其中内容后面多余的and去掉trim标签trim标签可以代替where trim标签: prefix:把trim标签中内容前面添加指定内容 suffix:把trim标签中内容后面添加指定内容 suffixOverrides:把trim标签中内容后面去掉指定内容 prifixOverrides:把trim标签中内容前面去掉指定内容若标签有内容 prefix,suffix:可以把trim标签中内容前面或后面添加指定内容 suffixOverrides,prefixOverrides:把trim标签中内容前面或后面去掉指定内容 若标签没有内容: trim标签也没有任何效果choose(when,otherwise)标签有的时候,我们并不需要用到所有的查询条件,我们希望只要满足其中一个条件就可以了,我们可以用choose标签来解决,相当于Java的switch case<select id="selectEmpByNameAndSex" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp <where> <choose> <when test="age !='' and age != null"> age=#{age} </when> <when test="empName !='' and empName != null"> and emp_name=#{empName} </when> <otherwise> and sex=#{sex} </otherwise> </choose> </where> </select>使用了choose标签的话,那么只要有一个条件满足,就不会判断其他条件了,所以不需要and,or等关键字foreach标签collection:设置需要循环的数组或集合 item:表示数组或集合中的每一个数据 separator:循环体之间的分隔符 open:foreach标签中所循环的所有内容的开始符close:foreach标签所循环的所有内容的结束符foreach很明显就是用来遍历的比如说我们要查询t_emp表中满足eid在[1,2,3]中的用户我们有两种方法①select * from t_emp where eid=1 or eid=2 or eid=3②select * from t_emp where eid in(1,2,3)mapper接口定义方法int deleteMore(@Param("eids") Integer[] eids);我们用foreach改写select * from t_emp where eid=1 or eid=2 or eid=3 <select id="selectEmpByNameAndSex" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp <!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from user where 1=1 and (eid=1 or eid=2 or eid=3) --> <foreach collection="eids" item="eid" separator="or"> eid=#{eid} </foreach> </where> </select>我们用 foreach 来改写 select * from t_emp where id in (1,2,3) <select id="selectEmpByNameAndSex" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp <where> <!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from user where 1=1 and id in (1,2,3) --> <foreach collection="eids" item="eid" open="eid in (" close=") " separator=","> #{eid} </foreach> </where> </select>sql片段我们都知道,查询数据库中的数据的时候,最好不要用*,那么我们难道每一次查数据都要把字段一个一个写出来,这样会很麻烦的,为了解决这个问题,我们可以使用sql片段,把我们要查询的字段写在sql片段中,以后要使用的话,直接引用sql片段就可以了。设置sql片段<sql id="empColumns">eid,emp_name,age,sex,email</sql>引用sql片段 select <include refid="empColumns"></include> from t_emp
HTTP协议什么是协议?协议实际上是某些人,或者某些组织提前制定好的一套规范,大家都按照这个规范来,这样可以做到沟通无障碍。协议就是一套规范,就是一套标准。由其他人或其他组织来负责制定的。我说的话你能听懂,你说的话,我也能听懂,这说明我们之间是有一套规范的,一套协议的,这套协议就是:中国普通话协议。我们都遵守这套协议,我们之间就可以沟通无障碍。什么是HTTP协议?HTTP协议:是W3C制定的一种超文本传输协议。(通信协议:发送消息的模板提前被制定好。)W3C:万维网联盟组织负责制定标准的:HTTP HTML4.0 HTML5 XML DOM等规范都是W3C制定的。万维网之父:蒂姆·伯纳斯·李什么是超文本? 超文本说的就是:不是普通文本,比如流媒体:声音、视频、图片等。HTTP协议支持:不但可以传送普通字符串,同样支持传递声音、视频、图片等流媒体信息。这种协议游走在B和S之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合。什么是解耦合?B不依赖S。S也不依赖B。耦合是对象之间有依赖关系,减少耦合,可以扩展软件功能B/S表示:B/S结构的系统(浏览器访问WEB服务器的系统)浏览器 向 WEB服务器发送数据,叫做:请求(request)WEB服务器 向 浏览器发送数据,叫做:响应(response)HTTP协议包括:请求协议浏览器 向 WEB服务器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。响应协议WEB服务器 向 浏览器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。HTTP协议就是提前制定好的一种消息模板。不管你是哪个品牌的浏览器,都是这么发。不管你是哪个品牌的WEB服务器,都是这么发。FF浏览器 可以向 Tomcat发送请求,也可以向Jetty服务器发送请求。浏览器不依赖具体的服务器品牌。WEB服务器也不依赖具体的浏览器品牌。可以是FF浏览器,也可以是Chrome浏览器,可以是IE,都行。HTTP的请求协议(B --> S)HTTP的请求协议包括:4部分 - 请求行 - 请求头 - 空白行 - 请求体HTTP请求协议的具体报文:GET请求GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9</font> 空白行 请求体 HTTP请求协议的具体报文:POST请求 POST /servlet05/postServlet HTTP/1.1 请求行Host: localhost:8080 请求头 Connection: keep-alive Content-Length: 25 Cache-Control: max-age=0 sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: http://localhost:8080 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 username=lisi&userpwd=123 请求体 请求行包括三部分:第一部分:请求方式(7种)get(常用的)post(常用的)deleteputheadoptionstrace第二部分:URI什么是URI? 统一资源标识符。代表网络中某个资源的名字。但是通过URI是无法定位资源的。 什么是URL?统一资源定位符。代表网络中某个资源,同时,通过URL是可以定位到该资源的。URI和URL什么关系,有什么区别?URL包括URIhttp://localhost:8080/servlet05/index.html 这是URL。/servlet05/index.html 这是URI。第三部分:HTTP协议版本号请求头请求的主机主机的端口浏览器信息平台信息cookie等信息....空白行空白行是用来区分“请求头”和“请求体”请求体向服务器发送的具体数据。HTTP的响应协议(S --> B)HTTP的响应协议包括:4部分状态行响应头空白行响应体HTTP响应协议的具体报文:HTTP/1.1 200 ok 状态行 Content-Type: text/html;charset=UTF-8 响应头 Content-Length: 160 Date: Mon, 08 Nov 2021 13:19:32 GMT Keep-Alive: timeout=20 Connection: keep-alive 空白行 <!doctype html> 响应体 <html> <head> <title>from get servlet</title> </head> <body> <h1>from get servlet</h1> </body> </html>状态行三部分组成第一部分:协议版本号(HTTP/1.1)第二部分:状态码(HTTP协议中规定的响应状态号。不同的响应结果对应不同的号码。)200 表示请求响应成功,正常结束。404表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源并没有启动成功。总之404错误是前端错误。405表示前端发送的请求方式与后端请求的处理方式不一致时发生:比如:前端是POST请求,后端的处理方式按照get方式进行处理时,发生405比如:前端是GET请求,后端的处理方式按照post方式进行处理时,发生405500表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。以4开始的,一般是浏览器端的错误导致的。以5开始的,一般是服务器端的错误导致的。第三部分:状态的描述信息ok 表示正常成功结束。not found 表示资源找不到。响应头:响应的内容类型响应的内容长度响应的时间....空白行:用来分隔“响应头”和“响应体”的。响应体:响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。怎么查看的协议内容?使用chrome浏览器:F12。然后找到network,通过这个面板可以查看协议的具体内容。怎么向服务器发送GET请求,怎么向服务器发送POST请求?到目前为止,只有一种情况可以发送POST请求:使用form表单,并且form标签中的method属性值为:method="post"。其他所有情况一律都是get请求:在浏览器地址栏上直接输入URL,敲回车,属于get请求。在浏览器上直接点击超链接,属于get请求。使用form表单提交数据时,form标签中没有写method属性,默认就是get或者使用form的时候,form标签中method属性值为:method="get"....HTTP协议的特点1.简单快速:客户向服务器请求服务的时候,只需要传送请求方法和路径,请求方法一般是get和post,因为HTTP协议简单,所以HTTP服务器的程序规模小,通信速度快。2.灵活:HTTP协议运行传输任意类型的数据对象,传输的类型由Content-Type标记3.无连接:客户端向服务端发送一次请求,服务端接收后,连接就断开了,无连接表示每一次连接都只处理一个请求,这种方式可以节省传输时间.举个例子:你打电话给朋友说让他帮忙买一瓶水,然后就把电话挂掉了,这个时候,想到说还要麻烦朋友做什么事情,就再打电话过去。HTTP1.1版本以后,支持可连续连接,通过这种连接就有可能在建立一个TCP连接后,发送请求并得到回应,然后接着发送请求并得到回应。通过建立和释放TCP连接的开销分摊到多个请求,对每一个请求来说,因为TCO造成的相对开销被大大降低了,而且还可以发送流水线请求,就是说在发送请求1之后,在回应到来之前就可以发送请求2.4.无状态。HTTP协议是无状态协议,无状态指的是对事务处理没有记忆能力,如果后面想要处理之前的信息的话,就必须重传,这样会导致每一次连接传送的数据量增大GET请求和POST请求有什么区别?get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。(get请求在“请求行”上发送数据) http://localhost:8080/servlet05/getServlet?username=zhangsan&userpwd=1111post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。(post在“请求体”当中发送数据)get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。get请求无法发送大数据量。post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。post请求可以发送大数据量,理论上没有长度限制。 get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。可能好多人,一看到说get请求会把内容显示在地址栏上,就是不安全的get请求是安全的。get请求是绝对安全的。为什么?因为get请求只是为了从服务器上获取数据。不会对服务器造成威胁。(get本身是安全的,你不要用错了。用错了之后又冤枉人家get不安全,你这样不好(太坏了),那是你自己的问题,不是get请求的问题。)post请求是危险的。为什么?因为post请求是向服务器提交数据,如果这些数据通过后门的方式进入到服务器当中,服务器是很危险的。另外post是为了提交数据,所以一般情况下拦截请求的时候,大部分会选择拦截(监听)post请求。注意:我这里说的安全是相对而言,事实上它们都不安全,比如get请求,会把数据显示在url上,但是我们要正确使用就不会出现这种问题。post请求则比较危险。get请求支持缓存。https://n.sinaimg.cn/finance/590/w240h350/20211101/b40c-b425eb67cabc342ff5b9dc018b4b00cc.jpg任何一个get请求最终的“响应结果”都会被浏览器缓存起来。在浏览器缓存当中:一个get请求的路径a 对应 一个资源。一个get请求的路径b 对应 一个资源。一个get请求的路径c 对应 一个资源。......- 实际上,你只要发送get请求,浏览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取。这种缓存机制目的是为了提高用户的体验。有没有这样一个需求:我们不希望get请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个get请求都去服务器上找资源,我不想从本地浏览器的缓存中取。只要每一次get请求的请求路径不同即可。https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897898https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897899https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=系统毫秒数怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样,浏览器就不走缓存了。post请求不支持缓存。(POST是用来修改服务器端的资源的。)post请求之后,服务器“响应的结果”不会被浏览器缓存起来。因为这个缓存没有意义。GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求?怎么选择GET请求和POST请求呢?衡量标准是什么呢?你这个请求是想获取服务器端的数据,还是想向服务器发送数据。如果你是想从服务器上获取资源,建议使用GET请求,如果你这个请求是为了向服务器提交数据,建议使用POST请求。大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。如果表单中有敏感信息,还是建议适用post请求,因为get请求会回显敏感信息到浏览器地址栏上。(例如:密码信息)做文件上传,一定是post请求。要传的数据不是普通文本。其他情况都可以使用get请求。不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:name=value&name=value&name=value&name=valuename是什么?以form表单为例:form表单中input标签的name。value是什么?以form表单为例:form表单中input标签的value。总结get和post啰嗦一句,不要主观臆断。大家可能都会有这样的主观错误,认为get请求会把信息显示在地址栏,所以就是不安全,post就是安全的。我觉得这是一种常见的误区。其实这种安全只是相对的这个表格是从w3cschool截取的 更加详细的介绍,具体的可以看这篇文章 get和post的区别
一、初识数据库1.数据库基本概念1.数据数据是对客观事务进行描述并且可以鉴别的符号,这些符号是可识别的、抽象的,有多种表现形式,可以是字母、文字、文本、图形、音频等2.数据库(Database DB)----本质是文件系统数据库指的是以一定格式存放,能够实现多个用户共享、与程序彼此独立的数据集合通俗一点来说,数据库就是数据的仓库,可以存放很多数据,比如说车库可以存放车一样3.数据库管理系统(数据库系统的基础和核心)用来定义和管理数据的软件,可以保证数据的安全性和完整性,比较流行的数据库管理系统有:MySql、Oracle、SQL server、DB2等比如说我们写了xxx.doc,这个就是一个word文件,那么我们是不是使用word去打开里面的文件,那么word这个软件就当于这里说的DBMS数据库管理系统(DBMS)可以管理多个数据库,一般开发人员会针对每一个应用创建一个数据库。为保存应用中实体的数据,一般会在数据库创建多个表,以保存程序中实体用户的数据。 数据库管理系统、数据库和表的关系如图所示:4.数据库应用程序在数据库管理系统的基础上,使用数据库管理系统的语法,开发的直接面对最终用户的应用程序,比如:学生管理系统、人事管理系统、图书管理系统我们可以这样来理解:不是每一个人都可以接触到数据库的,而且接触到数据库也不一定会使用,举个例子:以学生管理系统来说,学生只要点击选课,就代表选到了,点击退选,就把课程给退了,底层其实还是操作数据库,不过这样子对那些不懂数据库的人来说是很方便的。5.数据库管理员(DBA)对数据库管理系统进行操作的人员,主要负责数据库的运营和维护6.最终用户指的是数据库应用程序的使用者,用户面向的是数据库应用程序,通过程序来操作数据,并不会直接面向数据库7.数据库系统(DBS)一般是由数据库、数据库管理系统、数据库应用程序、数据库管理员和最终用户构成通过下面的图来总结一下:2.数据库类型数据库模型主要是:关系型数据库和非关系型数据库关系型数据库关系型数据库把复杂的数据结构用简单的二元关系(二维表)来表示关系型数据库主要有:MySQL、Oracle、DB2、SQL server等关系型数据库的数据是存在硬盘中的,获取数据需要通过io操作,形象效率。非关系型数据库的数据存在内存,这种数据库要求不能断电,否则数据会消失关系型数据库对数据是有约束的,不能随意插入数据,需要遵守一定规范关系型数据库的行代表一条记录,列代表字段我们可以把Java的对象和关系型数据库来类比一下非关系型数据库NOSQL主要有:MongDB、redis、HBase数据存在内存,访问数据很快,但是机器断电的话,数据就没了3.常见数据库1.Oracle:关系型数据库,世界上最流行的数据库,性能高、安全性高、风险低,管理维护和操作复杂,价格昂贵,一般用在银行、金融、保险等行业2.DB2:用起来繁琐,时候大型的分布式应用系统3.SQL server:Microsoft开发和推广的关系型数据库,功能全面,但只能用在Windows操作系统4.MySQL:开源,体积小,速度快,成本低二、MySQL数据库1. MySQL安装以及目录信息MySQL官方下载地址安装过程:1.双击MySQL安装文件mysql-installer-community-8.0.18.0.msi,出现安装类型选项。² Developer Default:开发者默认² Server only:只安装服务器端 ² Client only:只安装客户端² Full:安装全部选项² Custom:自定义安装2.选择,然后继续:3.进入产品配置向导,配置多个安装细节,点击Next按钮即可。4.高可靠性High Availability,采用默认选项即可。Standalone MySQL Server/Classic MySQL Replication:独立MySQL服务器/经典MySQL复制InnoDB Cluster:InnoDB集群5.类型和网络 Type and Networking,采用默认选项即可。记住MySQL的监听端口默认是3306。6.身份验证方法Authentication Method,采用默认选项即可。7.账户和角色 Accounts and Roles。MySQL管理员账户名称是root,在此处指定root用户的密码。还可以在此处通过Add User按钮添加其他新账户,此处省略该操作。8.Windows服务:Windows Service。Configure MySQL Server as a Windows Service:给MySQL服务器配置一个服务项。Windows Service Name:服务名称,采用默认名称MySQL80即可。Start the MySQL at System Startup:系统启动时开启MySQL服务9.Apply Configuration:点击Execute按钮执行开始应用这些配置项。Writing configuration file: 写配置文件。Updating Windows Firewall rules:更新Windows防火墙规则Adjusting Windows services:调整Windows服务Initializing database:初始化数据库Starting the server: 启动服务器Applying security setting:应用安全设置Updating the Start menu link:更新开始菜单快捷方式链接PS:如果配置出错,查看右侧的log,查看对应错误信息。执行完成后,如下图所示。单击Finish完成安装,进入产品配置环节。10.产品配置Product Configuration到此结束:点击Next按钮。11.安装完成 Installation Complete。点击Finish按钮完成安装。2.查看MySQL应用程序电脑->管理->服务和应用程序->服务3.各个目录文件夹修改my.ini可以达到重新配置的作用有几个配置项需要注意:port=3306 监听端口是3306basedir="D:/Program Files/MySQL/MySQL Server 8.0/" 软件安装位置datadir=D:/ProgramData/MySQL/MySQL Server 8.0\Data 数据文件夹安装位置default_authentication_plugin=caching_sha2_password 默认验证插件default-storage-engine=INNODB 默认存储引擎 允许最大连接数max_connections=200 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统max_connect_errors=10 服务端使用的字符集默认为UTF8character-set-server=utf8 创建新表时将使用的默认存储引擎default-storage-engine=INNODB[mysql] 设置mysql客户端默认字符集default-character-set=utf8(这些内容在Linux下可能会手动更改)4.常见命令1. .登录访问MySQL服务器相关命令:注意:mysql.exe需要带参数执行,所以直接在图形界面下执行该命令会自动结束,就是说,直接点击这个命令,它会直接退出打开控制命令台:win+r 输入cmd控制台要重启才有效登录的命令出现下面这个页面就登录成功了2.访问数据库系统默认给的数据库3.退出数据库退出数据库可以使用quit或者exit命令完成,也可以用\q完成退出操作5.数据库的卸载6 SQL yog(MySQL图形化界面工具)三、SQL语言入门1.概述数据库管理人员通过数据库管理系统可以对数据库的数据进行操作,具体的操作需要通过sql语言SQL(结构化查询语言) SQL是一种非过程化语言,只需要指明做什么,不需要指明怎么做2.SQL分类DQL:数据查询语言,主要用于数据查询,基本结构使用select子句,from子句,where子句的组合来查询一条或多条数据DML:数据操作语言,DML用于对数据库中的数据进行增加,修改,删除操作insert:增加数据update:修改数据delete删除数据DDL:数据定义语言,主要针对数据库对象(数据库,表,索引,视图,存储过程,函数)进行创建,修改,删除操作create:创建数据库对象alter:修改数据库对象drop:删除数据库对象DCL:数据控制语言用来授予或者回收访问数据库的权限比如说用户的登录权限grant:授予用户某种权限revoke:回收授予的某种权限TCL:事务控制语言 TCL用于数据库的事务管理,包括start transaction:开启事务commit:提交事务rollback:回滚事务set transaction:设置事务属性3.创建数据库表1.认识数据库表2.创建数据库表t_student新手建议使用命令,不要用图形界面先创建数据库 mysql> create database if not exists mytestdb charset utf8mb4; Query OK, 1 row affected (0.01 sec)mysql> show databases; +--------------------+ | Database | +--------------------+ | atguigudb | | bjpowernode | | dbtest1 | | hsp_db02 | | hsp_db03 | | information_schema | | mysql | | mysqlstudy | 我们刚刚建立的数据库 | mytest1 | | mytestdb | | performance_schema | | sys | | test01_office | | test02_market | | test03_company | +--------------------+ 15 rows in set (0.01 sec)mysql> use mytestdb; 用来说明我们要操作哪一个数据库 Database changedmysql> show tables; Empty set (0.00 sec) 目前还没有表 CREATE TABLE IF NOT EXISTS t_student( num INT(6), `name` VARCHAR(10), sex CHAR(1), age INT(3), enterdate DATE, classname VARCHAR(10), email VARCHAR(15) 注意这里不要加逗号 );创建好以后,我们来查看表的结构 DESC t_student;查看建表语句SHOW CREATE TABLE t_student;把里面的内容复制出来CREATE TABLE `t_student` ( `num` int DEFAULT NULL, `name` varchar(10) DEFAULT NULL, `sex` char(1) DEFAULT NULL, `age` int DEFAULT NULL, `enterdate` date DEFAULT NULL, `classname` varchar(10) DEFAULT NULL, `email` varchar(15) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci4.数据库表列类型整数类型MySQL支持选择在该类型关键字后面的括号内指定整数值的显示宽度(例如,INT(4))。显示宽度并不限制可以在列内保存的值的范围,也不限制超过列的指定宽度的值的显示注意:INT(4)只是用来建议我们最好输入的值不要超过4位,比如写1234,但是我们写12345这样也是可以的,因为MySQL会自动增加宽度主键自增:不使用序列,通过auto_increment,要求是整数类型(后面说,先有个印象)主键可以唯一定位一条记录浮点数类型需要注意的是与整数类型不一样的是,浮点数类型的宽度不会自动扩充。 score double(4,1) score double(4,1)--小数部分为1位,总宽度4位,并且不会自动扩充。字符串类型CHAR和VARCHAR类型相似,均用于存于较短的字符串,主要的不同之处在于存储方式。CHAR类型长度固定,VARCHAR类型的长度可变。VARCHAR类型能够根据字符串的实际长度来动态改变所占字节的大小,所以在不能明确该字段具体需要多少字符时推荐使用VARCHAR类型,这样可以大大地节约磁盘空间、提高存储效率。CHAR和VARCHAR表示的是字符的个数,而不是字节的个数日期和时间类型TIMESTEMP类型的数据指定方式与DATETIME基本相同,两者的不同之处在于以下几点:(1) 数据的取值范围不同,TIMESTEMP类型的取值范围更小。(2) 如果我们对TIMESTAMP类型的字段没有明确赋值,或是被赋与了NULL值,MySQL会自动将该字段赋值为系统当前的日期与时间。(3) TIMESTEMP类型还可以使用CURRENT_TIMESTAMP来获取系统当前时间。(4) TIMESTEMP类型有一个很大的特点,那就是时间是根据时区来显示的。例如,在东八区插入的TIMESTEMP数据为2017-07-11 16:43:25,在东七区显示时,时间部分就变成了15:43:25,在东九区显示时,时间部分就变成了17:43:25。5.DML操作数据注意事项int 宽度是显示宽度,如果超过,可以自动增大宽度 int底层都是4个字节时间的方式多样 '1256-12-23' "1256/12/23" "1256.12.23"字符串不区分单引号和双引号如何写入当前的时间 now() , sysdate() , CURRENT_DATE()char varchar 是字符的个数,不是字节的个数,可以使用binary,varbinary表示定长和不定长的字节个数。如果不是全字段插入数据的话,需要加入字段的名字-- 查看表记录:select * from t_student;1.插入数据如果表名后面没有加字段名的话,就是默认插入全部数据insert into 表名 values(值1,值2值3...)一次性插入多条数据insert into t_student values (1,'张三','男',18,'2022-5-8','软件1班','123@126.com'),(10010010,'张三','男',18,'2022-5-8','软件1班','123@126.com');想想看,如果表后面没有加字段名,而且我们插入的数据也和字段数量不匹配,会出现什么后果INSERT INTO t_student VALUES(5);如果我们只是想要插入部分数据,怎么办呢?insert into 表名(字段1,字段2...) values(值1,值2...) INSERT INTO t_student (num,`name`,enterdate) VALUES (10,'李四','2023-7-5');-- 在t_student数据库表中插入数据:insert into t_student values (1,'张三','男',18,'2022-5-8','软件1班','123@126.com'); insert into t_student values (10010010,'张三','男',18,'2022-5-8','软件1班','123@126.com'); insert into t_student values (2,'张三','男',18,'2022.5.8','软件1班','123@126.com'); insert into t_student values (2,"张三",'男',18,'2022.5.8','软件1班','123@126.com'); insert into t_student values (7,"张三",'男',18,now(),'软件1班','123@126.com'); insert into t_student values (9,"易烊千玺",'男',18,now(),'软件1班','123@126.com'); insert into t_student (sno,sname,enterdate) values (10,'李四','2023-7-5');没有插入的数据,就用默认值来替代2.修改表中数据update 表名 set 字段名=value这种方式是不加筛选条件,那么表中所有数据的这个字段的值都会发生变化update t_student set sex='女';所以,我们修改数据应该要加上条件update t_student set sex='男' where num=2;3.删除数据delete from 表名这种不加筛选条件的,会删除表中所有数据,但是表的结构还在delete from 表名 where 筛选条件这种只会删除符合条件的数据注意事项1.关键字,表名,字段名不区分大小写2.默认情况下,内容不区分大小写3.删除操作from关键字不可缺少4.修改,删除数据别忘记加限制条件6.DDL操作表结构1.修改表结构使用alter关键字1.1增加列增加列addalter table t_student add score double(5,2)update t_student set score=123.5678 where num=2;显示的数据是123.57,会进行四舍五入我们增加列的时候,默认加在最后一列,那么我们可以指定这个列出现在哪一个位置吗?--- 可以增加一列(放最前面)alter table t_student add score double(5,2) first;增加一列,放在sex列的后面alter table t_student add score double(5,2) after sex;增加一列,放在sex列的前面alter table t_student add score double(5,2) before sex;1.2修改列modify修改的是列的定义,不会改变列的名字alter table t_student modify score float(4,1);changealter table t_student change score score1 double(3,1);alter table t_student change score score1 double(5,1);2.删除表结构删除一列alter table t_student drop score;删除整个表drop table t_student;7.DDL和DML补充如何快速创建一张表 添加一张表:快速添加:结构和数据跟t_student 都是一致的 create table t_student2 as select * from t_student; 快速添加,结构跟t_student一致,数据没有: create table t_student3 as select * from t_student where 1=2; select * from t_student3; 快速添加:只要部分列,部分数据: create table t_student4 as select sno,sname,age from t_student where sno = 2; select * from t_student4;delete和truncate的区别:从最终的结果来看,虽然使用TRUNCATE操作和使用DELETE操作都可以删除表中的全部记录,但是两者还是有很多区别的,其区别主要体现在以下几个方面:(1)DELETE为数据操作语言DML;TRUNCATE为数据定义语言DDL。(2) DELETE操作是将表中所有记录一条一条删除直到删除完;TRUNCATE操作则是保留了表的结构,重新创建了这个表,所有的状态都相当于新表。因此,TRUNCATE操作的效率更高。(3)DELETE操作可以回滚;TRUNCATE操作会导致隐式提交,因此不能回滚(后面会讲解事务的提交和回滚)。(4)DELETE操作执行成功后会返回已删除的行数(如删除4行记录,则会显示“Affected rows:4”);截断操作不会返回已删除的行量,结果通常是“Affected rows:0”。DELETE操作删除表中记录后,再次向表中添加新记录时,对于设置有自增约束字段的值会从删除前表中该字段的最大值加1开始自增;TRUNCATE操作则会重新从1开始自增。四、表的完整性约束数据库中表的数据应该要有约束的,不然什么数据都可以插入数据库中,以之前的学生表为例,里面的数据 没有任何约束,什么数据都可以放进去,但是,学生的学号,邮箱应该是唯一的,性别只有男和女,我们应该保证在插入的数据,学号,邮箱唯一,性别只能是男和女,不然就报错。为防止不符合规范的数据存入数据库,在用户对数据进行插入、修改、删除等操作时,MySQL提供了一种机制来检查数据库中的数据是否满足规定的条件,以保证数据库中数据的准确性和一致性,这种机制就是完整性约束。MySQL中主要支持以下几种种完整性约束,如表所示。 其中Check约束是MySQL8中提供的支持。约束条件约束描述PRIMARY KEY主键约束,约束字段的值可唯一地标识对应的记录NOT NULL非空约束,约束字段的值不能为空UNIQUE唯一约束,约束字段的值是唯一的CHECK检查约束,限制某个字段的取值范围DEFAULT默认值约束,约束字段的默认值AUTO_INCREMENT自动增加约束,约束字段的值自动递增FOREIGN KEY外键约束,约束表与表之间的关系## 1. 非外键约束 代码演示主键约束建立一张用来存储学生信息的表字段包含学号、姓名、性别,年龄、入学日期、班级,email等信息约束:建立一张用来存储学生信息的表字段包含学号、姓名、性别,年龄、入学日期、班级,email等信息【1】学号是主键 = 不能为空 + 唯一 ,主键的作用:可以通过主键查到唯一的一条记录【2】如果主键是整数类型,那么需要自增【3】姓名不能为空【4】Email唯一【5】性别默认值是男【6】性别只能是男女【7】年龄只能在18-50之间-- 创建数据库表: create table t_student( sno int(6) primary key auto_increment, sname varchar(5) not null, sex char(1) default '男' check(sex='男' || sex='女'), age int(3) check(age>=18 and age<=50), enterdate date, classname varchar(10), email varchar(15) unique );-- 添加数据:-- 1048 - Column 'sname' cannot be null 不能为null-- 3819 - Check constraint 't_student_chk_1' is violated. 违反检查约束insert into t_student values (1,'张三','男',21,'2023-9-1','java01班','zs@126.com');-- 1062 - Duplicate entry '1' for key 't_student.PRIMARY' 主键重复-- > 1062 - Duplicate entry 'ls@126.com' for key 't_student.email' 违反唯一约束 insert into t_student values (2,'李四','男',21,'2023-9-1','java01班','ls@126.com'); insert into t_student values (3,'露露','男',21,'2023-9-1','java01班','ls@126.com');-- 如果主键没有设定值,或者用null.default都可以完成主键自增的效果insert into t_student (sname,enterdate) values ('菲菲','2029-4-5'); insert into t_student values (null,'小明','男',21,'2023-9-1','java01班','xm@126.com'); insert into t_student values (default,'小刚','男',21,'2023-9-1','java01班','xg@126.com');-- 如果sql报错,可能主键就浪费了,后续插入的主键是不连号的,我们主键也不要求连号的insert into t_student values (null,'小明','男',21,'2023-9-1','java01班','oo@126.com');-- 查看数据:select * from t_student;2.约束分为两类(从作用上来分)(1) 表级约束:可以约束表中任意一个或多个字段。与列定义相互独立,不包含在列定义中;与定义用‘,’分隔;必须指出要约束的列的名称;### (2) 列级约束: 包含在列定义中,直接跟在该列的其它定义之后 ,用空格分隔;不必指定列名;自增只能是列级约束,而且自增是基于主键的情况,没有主键就没有自增-- 删除表:drop table t_student;-- 创建数据库表:create table t_student( sno int(6) auto_increment, sname varchar(5) not null, sex char(1) default '男', age int(3), enterdate date, classname varchar(10), email varchar(15), constraint pk_stu primary key (sno), -- pk_stu 主键约束的名字 constraint ck_stu_sex check (sex = '男' || sex = '女'), constraint ck_stu_age check (age >= 18 and age <= 50), constraint uq_stu_email unique (email) );-- 添加数据:insert into t_student values (1,'张三','男',21,'2023-9-1','java01班','zs@126.com');-- > 3819 - Check constraint 'ck_stu_sex' is violated.-- > 3819 - Check constraint 'ck_stu_age' is violated.-- > 1062 - Duplicate entry 'zs@126.com' for key 't_student.uq_stu_email'insert into t_student values (3,'李四','男',21,'2023-9-1','java01班','zs@126.com');-- 查看数据:select * from t_student;(3)在创建表以后添加约束:-- 删除表:drop table t_student;-- 创建数据库表:create table t_student( sno int(6), sname varchar(5) not null, sex char(1) default '男', age int(3), enterdate date, classname varchar(10), email varchar(15) );-- > 1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key-- 错误的解决办法:就是auto_increment去掉-- 在创建表以后添加约束:alter table t_student add constraint pk_stu primary key (sno) ; -- 主键约束 alter table t_student modify sno int(6) auto_increment; -- 修改自增条件 alter table t_student add constraint ck_stu_sex check (sex = '男' || sex = '女'); alter table t_student add constraint ck_stu_age check (age >= 18 and age <= 50); alter table t_student add constraint uq_stu_email unique (email);-- 查看表结构:desc t_student;验证约束添加成功:查看表结构:(4)总结:1.主键约束 主键约束(PRIMARY KEY,缩写PK),是数据库中最重要的一种约束,其作用是约束表中的某个字段可以唯一标识一条记录。因此,使用主键约束可以快速查找表中的记录。就像人的身份证、学生的学号等等,设置为主键的字段取值不能重复(唯一),也不能为空(非空),否则无法唯一标识一条记录。主键可以是单个字段,也可以是多个字段组合。对于单字段主键的添加可使用表级约束,也可以使用列级约束;而对于多字段主键的添加只能使用表级约束。2.非空约束 非空约束(NOT NULL,缩写NK)规定了一张表中指定的某个字段的值不能为空(NULL)。设置了非空约束的字段,在插入的数据为NULL时,数据库会提示错误,导致数据无法插入。无论是单个字段还是多个字段非空约束的添加只能使用列级约束(非空约束无表级约束)为已存在表中的字段添加非空约束 alter table student8 modify stu_sex varchar(1) not null;使用ALTER TABLE语句删除非空约束 alter table student8 modify stu_sex varchar(1) null;唯一约束唯一约束(UNIQUE,缩写UK)比较简单,它规定了一张表中指定的某个字段的值不能重复,即这一字段的每个值都是唯一的。如果想要某个字段的值不重复,那么就可以为该字段添加为唯一约束。无论单个字段还是多个字段唯一约束的添加均可使用列级约束和表级约束检查约束检查约束(CHECK)用来限制某个字段的取值范围,可以定义为列级约束,也可以定义为表级约束。MySQL8开始支持检查约束。默认值约束默认值约束(DEFAULT)用来规定字段的默认值。如果某个被设置为DEFAULT约束的字段没插入具体值,那么该字段的值将会被默认值填充。 默认值约束的设置与非空约束一样,也只能使用列级约束。 字段值自动增加约束自增约束(AUTO_INCREMENT)可以使表中某个字段的值自动增加。一张表中只能有一个自增长字段,并且该字段必须定义了约束(该约束可以是主键约束、唯一约束以及外键约束),如果自增字段没有定义约束,数据库则会提示“Incorrect table definition; there can be only one auto column and it must be defined as a key”错误。由于自增约束会自动生成唯一的ID,所以自增约束通常会配合主键使用,并且只适用于整数类型。一般情况下,设置为自增约束字段的值会从1开始,每增加一条记录,该字段的值加1。为已存在表中的字段添加自增约束 /创建表student11/ create table student11 ( stu_id int(10) primary key, stu_name varchar(3), stu_sex varchar (1) );为student11表中的主键字段添加自增约束alter table student11 modify stu_id int(10) auto_increment;使用ALTER TABLE语句删除自增约束alter table studen11 modify stu_id int(10);3.外键策略-- 学生表删除:drop table t_student;-- 班级表删除:drop table t_class;-- 注意:先删除从表,再删除主表。(视频中这个位置笔误,笔记现在已经更正)-- 先创建父表:班级表:create table t_class( cno int(4) primary key auto_increment, cname varchar(10) not null, room char(4) )-- 可以一次性添加多条记录:insert into t_class values (null,'java001','r803'),(null,'java002','r416'),(null,'大数据001','r103');-- 添加学生表,添加外键约束:create table t_student( sno int(6) primary key auto_increment, sname varchar(5) not null, classno int(4),-- 取值参考t_class表中的cno字段,不要求字段名字完全重复,但是类型长度定义 尽量要求相同。 constraint fk_stu_classno foreign key (classno) references t_class (cno) );-- 可以一次性添加多条记录:insert into t_student values (null,'张三',1),(null,'李四',1),(null,'王五',2),(null,'朱六',3);-- 查看班级表和学生表:select * from t_class; select * from t_student;-- 删除班级2:如果直接删除的话肯定不行因为有外键约束:-- 加入外键策略:-- 策略1:no action 不允许操作-- 通过操作sql来完成:-- 先把班级2的学生对应的班级 改为nullupdate t_student set classno = null where classno = 2;-- 然后再删除班级2:delete from t_class where cno = 2;-- 策略2:cascade 级联操作:操作主表的时候影响从表的外键信息:-- 先删除之前的外键约束: alter table t_student drop foreign key fk_stu_classno; -- 重新添加外键约束: alter table t_student add constraint fk_stu_classno foreign key (classno) references t_class (cno) on update cascade on delete cascade; -- 试试更新: update t_class set cno = 5 where cno = 3; -- 试试删除: delete from t_class where cno = 5;-- 策略3:set null 置空操作:-- 先删除之前的外键约束: alter table t_student drop foreign key fk_stu_classno; -- 重新添加外键约束: alter table t_student add constraint fk_stu_classno foreign key (classno) references t_class (cno) on update set null on delete set null; -- 试试更新: update t_class set cno = 8 where cno = 1;-- 注意:-- 1. 策略2 级联操作 和 策略2 的 删除操作 可以混着使用:alter table t_student add constraint fk_stu_classno foreign key (classno) references t_class (cno) on update cascade on delete set null ;-- 2.应用场合:-- (1)朋友圈删除,点赞。留言都删除 -- 级联操作-- (2)解散班级,对应的学生 置为班级为null就可以了,-- set null看完这篇以后,大家可以看我的下一篇文章。下一篇MySQL的基本查询
一、基本的select语句1.查询常量2.从表中查数据 SELECT 标识选择哪些列 FROM 标识从哪个表中选择很明显,它的意思就是说,我们要从哪一张表中查询数据比如说,我们要从员工表emp中查询所有数据,当然了,如果我们只是想要查询部分数据,我们完全可以指定要查询的字段名字MySQL中的SQL语句是不区分大小写的,因此SELECT和select的作用是相同的,但是,许多开发人员习惯将关键字大写、数据列和表名小写,读者也应该养成一个良好的编程习惯,这样写出来的代码更容易阅读和维护。注意:一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符‘*’。使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。通配符的优势是,当不知道所需要的列的名称时,可以通过它获取它们。在开发的时候,我们最好不要使用*来进行查询,你想想看,开发过程中,数据库中的数据是不是有很多,然后如果使用通配符一下子把所有记录查询出来,电脑可能就直接死机了。3.使用列的别名查询我们发现在进行数据查询的时候,有的时候字段名很长,我们写起来也不是很方便,我们就考虑能不能通过别名来查数据呢方式一:列名和别名之间加上as关键字方式二:列名和别名之间用空格隔开注意:我们需要保证表中的字段、表名等没有和保留字、数据库系统或常用方法冲突。如果真的相同,请在SQL语句中使用一对``(着重号)引起来。我们使用select语句来进行查询,那么查询的结果会影响到数据库中表的记录吗?接下来我们来看一下我们刚刚不是用来别名了吗,为什么表中没有改变呢,大家可能会有这样的疑惑,事实上,使用select语句查询出来的是一个临时的表。你看看,我们用select查询出来的结果,是不是和表长得很像,上面是第一行都是字段,然后下面都是数据。就相当于是客户端新建了一张临时的表用来存放我们刚刚用select查询出来的数据,当它把查询结果显示出来给我们看以后,内存就释放了,当然不会影响硬盘中的数据,。4.去重使用关键字distinct我们先来看看distinct用在单给字段上面的情况接下来我们来看看对多个字段进行去重的情况5.空值NULL参与运算所有运算符或列值遇到null值,运算的结果都为null先回顾一下表中数据发现和null运算结果为null6.过滤数据使用where可以过滤数据,筛选出我们想要选的数据SELECT 字段1,字段2FROM 表名WHERE 过滤条件补充一下:desc可以显示表的结构Field:表示字段名称。 Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。 Null:表示该列是否可以存储NULL值。 Key:表示该列是否已编制索引。PRI表示该列是表主键的一部分;UNI表示该列是UNIQUE索引的一部分;MUL表示在列中某个给定值允许出现多次。Default:表示该列是否有默认值,如果有,那么值是多少。 Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等。二、 运算符1.算术运算符2.比较运算符比较运算符用来对表达式左边的操作数和右边的操作数进行比较,比较的结果为真则返回1,比较的结果为假则返回0,其他情况则返回NULL。比较运算符经常被用来作为SELECT查询语句的条件来使用,返回符合条件的结果记录。1.等号运算符2.安全等于运算符3.不等于运算符空运算符(IS NULL或者ISNULL)判断一个值是否为NULL,如果为NULL则返回1,否则返回0。mysql> SELECT NULL IS NULL, ISNULL(NULL), ISNULL('a'), 1 IS NULL; +--------------+--------------+-------------+-----------+ | NULL IS NULL | ISNULL(NULL) | ISNULL('a') | 1 IS NULL | +--------------+--------------+-------------+-----------+ | 1 | 1 | 0 | 0 | +--------------+--------------+-------------+-----------+BETWEEN运算符使用的格式通常为SELECT D FROM TABLE WHERE C BETWEEN A AND B,此时,当C大于或等于A,并且C小于或等于B时,结果为1,否则结果为0。 IN运算符IN运算符用于判断给定的值是否是IN列表中的一个值,如果是则返回1,否则返回0。如果给定的值为NULL,或者IN列表中存在NULL,则结果为NULL。 LIKE运算符主要用来匹配字符串,通常用于模糊匹配,如果满足条件则返回1,否则返回0。如果给定的值或者匹配条件为NULL,则返回结果为NULL。 LIKE运算符通常使用如下通配符: “%”:匹配0个或多个字符。 “_”:只能匹配一个字符。3.逻辑运算符逻辑非逻辑与逻辑或OR可以和AND一起使用,但是在使用时要注意两者的优先级,由于AND的优先级高于OR,因此先对AND两边的操作数进行操作,再与OR中的操作数结合。逻辑异或运算符4.位运算符位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算,最后将计算结果从二进制变回十进制数。MySQL支持的位运算符如下:1.按位与运算符按位与(&)运算符将给定值对应的二进制数逐位进行逻辑与运算。当给定值对应的二进制位的数值都为1时,则该位返回1,否则返回0。mysql> SELECT 1 & 10, 20 & 30; +--------+---------+ | 1 & 10 | 20 & 30 | +--------+---------+ | 0 | 20 | +--------+---------+ 1 row in set (0.00 sec)1的二进制数为0001,10的二进制数为1010,所以1 & 10的结果为0000,对应的十进制数为0。20的二进制数为10100,30的二进制数为11110,所以20 & 30的结果为10100,对应的十进制数为20。2. 按位或运算符按位或(|)运算符将给定的值对应的二进制数逐位进行逻辑或运算。当给定值对应的二进制位的数值有一个或两个为1时,则该位返回1,否则返回0。mysql> SELECT 1 | 10, 20 | 30; +--------+---------+ | 1 | 10 | 20 | 30 | +--------+---------+ | 11 | 30 | +--------+---------+ 1 row in set (0.00 sec)1的二进制数为0001,10的二进制数为1010,所以1 | 10的结果为1011,对应的十进制数为11。20的二进制数为10100,30的二进制数为11110,所以20 | 30的结果为11110,对应的十进制数为30。3. 按位异或运算符按位异或(^)运算符将给定的值对应的二进制数逐位进行逻辑异或运算。当给定值对应的二进制位的数值不同时,则该位返回1,否则返回0。mysql> SELECT 1 ^ 10, 20 ^ 30; +--------+---------+ | 1 ^ 10 | 20 ^ 30 | +--------+---------+ | 11 | 10 | +--------+---------+ 1 row in set (0.00 sec)1的二进制数为0001,10的二进制数为1010,所以1 ^ 10的结果为1011,对应的十进制数为11。20的二进制数为10100,30的二进制数为11110,所以20 ^ 30的结果为01010,对应的十进制数为10。再举例:mysql> SELECT 12 & 5, 12 | 5,12 ^ 5 FROM DUAL; +--------+--------+--------+ | 12 & 5 | 12 | 5 | 12 ^ 5 | +--------+--------+--------+ | 4 | 13 | 9 | +--------+--------+--------+ 1 row in set (0.00 sec)可能这样还是很懵,可以看看这张图理解一下,位运算会先把数字转换成二进制,然后逐位进行运算4. 按位取反运算符按位取反(~)运算符将给定的值的二进制数逐位进行取反操作,即将1变为0,将0变为1。mysql> SELECT 10 & ~1; +---------+ | 10 & ~1 | +---------+ | 10 | +---------+ 1 row in set (0.00 sec)由于按位取反(~)运算符的优先级高于按位与(&)运算符的优先级,所以10 & ~1,首先,对数字1进行按位取反操作,结果除了最低位为0,其他位都为1,然后与10进行按位与操作,结果为10。5. 按位右移运算符按位右移(>>)运算符将给定的值的二进制数的所有位右移指定的位数。右移指定的位数后,右边低位的数值被移出并丢弃,左边高位空出的位置用0补齐。mysql> SELECT 1 >> 2, 4 >> 2; +--------+--------+ | 1 >> 2 | 4 >> 2 | +--------+--------+ | 0 | 1 | +--------+--------+ 1 row in set (0.00 sec)6. 按位左移运算符按位左移(<<)运算符将给定的值的二进制数的所有位左移指定的位数。左移指定的位数后,左边高位的数值被移出并丢弃,右边低位空出的位置用0补齐。按位左移和上面的右移是一样道理,这里就不说了。mysql> SELECT 1 << 2, 4 << 2; +--------+--------+ | 1 << 2 | 4 << 2 | +--------+--------+ | 4 | 16 | +--------+--------+ 1 row in set (0.00 sec) 三、 排序和分页1.排序使用order by进行排序ASC(ascend): 升序**DESC(descend):降序** 如果没有使用排序操作,默认情况下查询返回的数据是按照我们添加的顺序显示。如果在ORDER BY后没有显示的指明排序顺序,则默认为升序排列。可以使用列的别名进行排序。列的别名只能ORDER BY中使用,不能在WHERE 当中使用来进行筛选。ORDER BY的字段不一定是要查询的字段。强调格式:WHERE 需要声明在FROM后,ORDER BY之前。可以使用不在SELECT列表中的列排序。 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。2.分页使用LIMIT进行分页什么是分页呢,我们来看一下下面的图片,其实分页很常见的我们在浏览器输入要查找的数据,浏览器就会返回一些数据给我们,当我们点击下一页,或者是指定也是页数的时候,它就会跳转到相应页面。那么我们为什么要进行分页呢,理由其实很简单假如现在,我们想要查找一些信息,然后浏览器把所有的信息都返回给我们,那这样的话,服务器的压力就会很大,因为数据很庞大,而且对于用户来说,我们可能在前面几条信息就已经得到了我们想要的答案,那我们就不会浪费时间继续往下看。那么后面的那些数据是不是就浪费掉了呢?而且这样也很影响效率,速度很慢,而且你想想把所有数据返回,网站相应速度就会很慢的,用户等了好久也等不到网站的响应,那用户可能就等不下去了,这样长久以往,这个网站就会失去很多的用户。再来举个例子:以我们在淘宝买东西为例,我们想买东西,我们点击下一页,才返回一些物品信息给我们,我们如果没有点击下一页,就没有必要返回物品信息给我们了。格式:LIMIT [位置偏移量,] 行数第一个“位置偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定“位置偏移量”,将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);第二个参数“行数”指示返回的记录条数。--前10条记录: SELECT * FROM 表名 LIMIT 0,10; 或者 SELECT * FROM 表名 LIMIT 10; --第11至20条记录: SELECT * FROM 表名 LIMIT 10,10; --第21至30条记录: SELECT * FROM 表名 LIMIT 20,10;MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。分页显式公式 :(当前页数-1)*每页条数,每页条数`SELECT * FROM tableLIMIT(PageNo - 1)*PageSize,PageSize;`注意:LIMIT 子句必须放在整个SELECT语句的最后!使用 LIMIT 的好处约束返回结果的数量可以减少数据表的网络传输量,也可以提升查询效率。如果我们知道返回结果只有 1 条,就可以使用`LIMIT1`,告诉 SELECT 语句只需要返回一条记录即可。这样的好处就是 SELECT不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。下一篇我会更新数据库的高级查询,比如多表查询,子查询等,数据库的CRUD语句中,最重要的就是select语句了,希望对大家有帮助下一篇MySQL查询进阶
@TOC一、多表查询多表查询,也称为关联查询,指两个或更多个表一起完成查询操作。前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,这两个表依靠“部门编号”进行关联。1.引出假如我们现在要查询员工的姓名还有部门名称 这两个字段在不同表中,如果没有关联条件的话,查询出来的结果会怎么样呢,让我们来看看。SELECT last_name, department_name FROM employees, departments; +-----------+----------------------+ | last_name | department_name | +-----------+----------------------+ | King | Administration | | King | Marketing | | King | Purchasing | | King | Human Resources | | King | Shipping | | King | IT | | King | Public Relations | | King | Sales | | King | Executive | | King | Finance | | King | Accounting | | King | Treasury | ... | Gietz | IT Support | | Gietz | NOC | | Gietz | IT Helpdesk | | Gietz | Government Sales | | Gietz | Retail Sales | | Gietz | Recruiting | | Gietz | Payroll | +-----------+----------------------+ 2889 rows in set (0.01 sec) SELECT COUNT(employee_id) FROM employees; #输出107行 SELECT COUNT(department_id)FROM departments; #输出27行 SELECT 107*27 FROM dual; 107*27=2889很明显上面的操作是错误的上面的操作,会导致员工表的一条记录会和部门表的每一条记录相匹配,就好像一个员工在所有部门都工作过一样,从现实角度来说,很明显,是不会出现这种情况的,这种现象就是笛卡尔积。2.笛卡尔积笛卡儿积就是关系代数里的一个概念,表示两个表中的每一行数据任意组合的结果。比如:有两个表,左表有m条数据记录,x个字段,右表有n条数据记录,y个字段,则执行交叉连接后将返回m*n条数据记录,x+y个字段。笛卡儿积示意图如图所示。 SQL92中,笛卡尔积也称为交叉连接,英文是 CROSS JOIN。在 SQL99 中也是使用 CROSS JOIN表示交叉连接。它的作用就是可以把任意表进行连接,即使这两张表不相关。在MySQL中如下情况会出现笛卡尔积:查询员工姓名和所在部门名称SELECT last_name,department_name FROM employees,departments; SELECT last_name,department_name FROM employees CROSS JOIN departments; SELECT last_name,department_name FROM employees INNER JOIN departments;3. 笛卡尔积的解决方法笛卡尔积的错误会在下面条件下产生:省略多个表的连接条件(或关联条件)连接条件(或关联条件)无效所有表中的所有行互相连接为了避免笛卡尔积, 可以在 WHERE 加入有效的连接条件。SELECT table1.column, table2.column FROM table1, table2 WHERE table1.column1 = table2.column2; #连接条件#案例:查询员工的姓名及其部门名称 SELECT last_name, department_name FROM employees, departments WHERE employees.department_id = departments.department_id;注意:如果不同的表中有相同的字段,我们要声明我们查的是哪一张表的字段,表名.字段名这个和Java中,类名.属性是类似的,挺好理解的。SELECT employees.last_name, departments.department_name,employees.department_id FROM employees, departments WHERE employees.department_id = departments.department_id;二、多表查询分类1.等值连接和非等值连接等值连接其实很好理解,就是谁等于谁的意思,使用=。非等值连接的话,比如查询某个字段>某个值的记录等等SELECT employees.employee_id, employees.last_name, employees.department_id, departments.department_id, departments.location_id FROM employees, departments WHERE employees.department_id = departments.department_id;拓展:使用别名可以简化查询。--- 有的字段名太长了列名前使用表名前缀可以提高查询效率。d.department_id, d.location_idFROM employees e , departments dWHERE e.department_id = d.department_id;2.自连接和非自连接自连接,它的字面意思就是自己和自己连接比如说现在有一张表,我们想要查找员工信息和对应的上级信息我们知道,只有一张表是没办法把它们关联起来的,要想把它们他们关联起来,肯定是要有关联条件的,那么就应该要有两张表,这个时候,我们就可以抽取出一张表,和本来的表本质上是一样的,然后我们对表起别名,table1和table2本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义。然后两个表再进行内连接,外连接等查询。比如说:现在我们想要查找员工和对应老板的名字,我们就可以使用自连接SELECT CONCAT(worker.last_name ,' works for ' , manager.last_name) FROM employees worker, employees manager WHERE worker.manager_id = manager.employee_id ;练习:查询出last_name为 ‘Chen’ 的员工的 manager 的信息。3.内连接和外连接内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。如果是左外连接,则连接条件中左边的表也称为主表,右边的表称为从表。如果是右外连接,则连接条件中右边的表也称为主表,左边的表称为从表。外连接查询的数据比较多SQL92:使用(+)创建连接在 SQL92 中采用(+)代表从表所在的位置。即左或右外连接中,(+) 表示哪个是从表。Oracle 对 SQL92 支持较好,而 MySQL 则不支持 SQL92 的外连接。#左外连接 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id = departments.department_id(+); #右外连接 SELECT last_name,department_name FROM employees ,departments SQL99语法实现多表查询1.基本语法使用JOIN...ON子句创建连接的语法结构:SELECT table1.column, table2.column,table3.column FROM table1 JOIN table2 ON table1 和 table2 的连接条件 JOIN table3 ON table2 和 table3 的连接条件 语法说明: 可以使用 ON 子句指定额外的连接条件 。这个连接条件是与其它条件分开的。ON 子句使语句具有更高的易读性。关键字 JOIN、INNER JOIN、CROSS JOIN 的含义是一样的,都表示内连接 2.内连接(INNER JOIN)语法select 字段from 表1join 表2 on 两个表的连接条件where 其他子句比如我们现在想要查询各个部门的员工的信息,他们的连接条件就是员工表中部门id和部门表中的部门id一样SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id); 这里截取部分结果 +-------------+-------------+---------------+---------------+-------------+ | employee_id | last_name | department_id | department_id | location_id | +-------------+-------------+---------------+---------------+-------------+ | 103 | Hunold | 60 | 60 | 1400 | | 104 | Ernst | 60 | 60 | 1400 | | 105 | Austin | 60 | 60 | 1400 | | 106 | Pataballa | 60 | 60 | 1400 | | 107 | Lorentz | 60 | 60 | 1400 | | 120 | Weiss | 50 | 50 | 1500 | | 121 | Fripp | 50 | 50 | 1500 | | 122 | Kaufling | 50 | 50 | 1500 | | 123 | Vollman | 50 | 50 | 1500 | | 124 | Mourgos | 50 | 50 | 1500 | | 125 | Nayer | 50 | 50 | 1500 | | 126 | Mikkilineni | 50 | 50 | 1500 | | 127 | Landry | 50 | 50 | 1500 | | 128 | Markle | 50 | 50 | 1500 | | 129 | Bissot | 50 | 50 | 1500 |使用内连接的一个问题就是他们把所有的信息都显示出来,它只能够显示匹配的数据,而外连接可以把不匹配的数据也显示出来先来看看表的数据,方便后续操作mysql> select * from emp; +-------+--------+-----------+------+------------+---------+---------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+--------+-----------+------+------------+---------+---------+--------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | +-------+--------+-----------+------+------------+---------+---------+--------+ 14 rows in set (0.00 sec) mysql> select * from dept; +--------+------------+----------+ | DEPTNO | DNAME | LOC | +--------+------------+----------+ | 10 | ACCOUNTING | NEW YORK | | 20 | RESEARCH | DALLAS | | 30 | SALES | CHICAGO | | 40 | OPERATIONS | BOSTON | +--------+------------+----------+ 4 rows in set (0.00 sec)mysql> select * from emp e -> join dept d -> on e.deptno=e.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 10 | ACCOUNTING | NEW YORK | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 10 | ACCOUNTING | NEW YORK | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 10 | ACCOUNTING | NEW YORK | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 10 | ACCOUNTING | NEW YORK | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 10 | ACCOUNTING | NEW YORK | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 10 | ACCOUNTING | NEW YORK | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 10 | ACCOUNTING | NEW YORK | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 10 | ACCOUNTING | NEW YORK | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 10 | ACCOUNTING | NEW YORK | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 10 | ACCOUNTING | NEW YORK | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 10 | ACCOUNTING | NEW YORK | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 20 | RESEARCH | DALLAS | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 20 | RESEARCH | DALLAS | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 20 | RESEARCH | DALLAS | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 20 | RESEARCH | DALLAS | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 20 | RESEARCH | DALLAS | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 20 | RESEARCH | DALLAS | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 20 | RESEARCH | DALLAS | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 20 | RESEARCH | DALLAS | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 20 | RESEARCH | DALLAS | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 30 | SALES | CHICAGO | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 30 | SALES | CHICAGO | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 30 | SALES | CHICAGO | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 30 | SALES | CHICAGO | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 30 | SALES | CHICAGO | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 30 | SALES | CHICAGO | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 40 | OPERATIONS | BOSTON | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 40 | OPERATIONS | BOSTON | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 40 | OPERATIONS | BOSTON | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 40 | OPERATIONS | BOSTON | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 40 | OPERATIONS | BOSTON | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 40 | OPERATIONS | BOSTON | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 40 | OPERATIONS | BOSTON | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 40 | OPERATIONS | BOSTON | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 40 | OPERATIONS | BOSTON | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 40 | OPERATIONS | BOSTON | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 40 | OPERATIONS | BOSTON | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 40 | OPERATIONS | BOSTON | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 40 | OPERATIONS | BOSTON | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 56 rows in set (0.01 sec)-- 问题:-- 1.40号部分没有员工,没有显示在查询结果中-- 2.员工scott没有部门,没有显示在查询结果中所以想显示所有数据,要使用外连接外连接(OUTER JOIN)1.左外连接 左外连接: left outer join -- 左面的那个表的信息,即使不匹配也可以查看出效果SELECT 字段列表FROM A表 LEFT JOIN B表ON 关联条件WHERE 等其他子句;2.右外连接SELECT 字段列表FROM A表 RIGHT JOIN B表ON 关联条件WHERE 等其他子句;mysql> select * -> from emp e -> right outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 15 rows in set (0.00 sec)3.满外连接(FULL OUTER JOIN) 满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。SQL99是支持满外连接的。使用FULL JOIN 或 FULL OUTER JOIN来实现。需要注意的是,MySQL不支持FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT join代替。在讲满外连接之前,我们先来介绍一下union关键字的使用,相信看了以后大家就清楚了4.UNION合并查询结果利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且相互对应。各个SELECT语句之间使用UNION或UNION ALL关键字分隔。语法格式:SELECT column,... FROM table1UNION [ALL]SELECT column,... FROM table2UNION操作符UNION 操作符返回两个查询的结果集的并集,去除重复记录。`UNION ALL操作符UNION ALL操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重。 注意:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效率。 为什么union all的效率比较高呢?首先我们如果使用union的话,它会先把数据查询出来,紧接着还要进去去重操作,它多了一步去重操作,当然花费的时间就比较多了,影响效率。mysql> select * -> from emp e -> left outer join dept d -> on e.deptno = d.deptno -> union -- 并集 去重 效率低 -> select * -> from emp e -> right outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 15 rows in set (0.01 sec) mysql> ^C mysql> https://blog.csdn.net/weixin_42250835/article/details/123535439^Z^Z^C mysql> select * -> from emp e -> left outer join dept d -> on e.deptno = d.deptno -> union -- 并集 去重 效率低 -> select * -> from emp e -> right outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 15 rows in set (0.00 sec) mysql> select * -> from emp e -> left outer join dept d -> on e.deptno = d.deptno -> union all-- 并集 不去重 效率高 -> select * -> from emp e -> right outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 29 rows in set (0.00 sec)为了让大家更清楚知道他们的区别,我们分别看一下有多少记录 -> on e.deptno = d.deptno' at line 2 mysql> select * -> from emp e -> left outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 14 rows in set (0.00 sec) mysql> select * -> from emp e -> right outer join dept d -> on e.deptno = d.deptno; +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | DEPTNO | DNAME | LOC | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | 10 | ACCOUNTING | NEW YORK | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | 20 | RESEARCH | DALLAS | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | 30 | SALES | CHICAGO | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | 30 | SALES | CHICAGO | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | 30 | SALES | CHICAGO | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | 30 | SALES | CHICAGO | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 30 | SALES | CHICAGO | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 40 | OPERATIONS | BOSTON | +-------+--------+-----------+------+------------+---------+---------+--------+--------+------------+----------+ 15 rows in set (0.00 sec)14+15=29所=所以可以看出union all确实是不去重总结 中图:内连接 A∩B SELECT employee_id,last_name,department_name FROM employees e JOIN departments d ON e.`department_id` = d.`department_id`; 左上图:左外连接 SELECT employee_id,last_name,department_name FROM employees e LEFT JOIN departments d ON e.`department_id` = d.`department_id`; 右上图:右外连接 SELECT employee_id,last_name,department_name FROM employees e RIGHT JOIN departments d ON e.`department_id` = d.`department_id`; 左中图:A - A∩B SELECT employee_id,last_name,department_name FROM employees e LEFT JOIN departments d ON e.`department_id` = d.`department_id` WHERE d.`department_id` IS NULL 右中图:B-A∩B SELECT employee_id,last_name,department_name FROM employees e RIGHT JOIN departments d ON e.`department_id` = d.`department_id` WHERE e.`department_id` IS NULL 左下图:满外连接 左中图 + 右上图 A∪B SELECT employee_id,last_name,department_name FROM employees e LEFT JOIN departments d ON e.`department_id` = d.`department_id` WHERE d.`department_id` IS NULL UNION ALL #没有去重操作,效率高 SELECT employee_id,last_name,department_name FROM employees e RIGHT JOIN departments d ON e.`department_id` = d.`department_id`; 右下图 左中图 + 右中图 A ∪B- A∩B 或者 (A - A∩B) ∪ (B - A∩B) SELECT employee_id,last_name,department_name FROM employees e LEFT JOIN departments d ON e.`department_id` = d.`department_id` WHERE d.`department_id` IS NULL UNION ALL SELECT employee_id,last_name,department_name FROM employees e RIGHT JOIN departments d ON e.`department_id` = d.`department_id` WHERE e.`department_id` IS NULL## 4.自然连接SQL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN 用来表示自然连接。我们可以把自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段,然后进行等值连接。SELECT employee_id,last_name,department_name FROM employees e NATURAL JOIN departments d;上面的写法的效果和下面是一样的SELECT employee_id,last_name,department_name FROM employees e JOIN departments d ON e.`department_id` = d.`department_id` AND e.`manager_id` = d.`manager_id`;5.using连接当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的同名字段进行等值连接。但是只能配合JOIN一起使用。比如:SELECT employee_id,last_name,department_name FROM employees e JOIN departments d USING (department_id);你能看出与自然连接 NATURAL JOIN 不同的是,USING 指定了具体的相同的字段名称,你需要在 USING 的括号 () 中填入要指定的同名字段。同时使用 JOIN...USING 可以简化 JOIN ON 的等值连接。它与下面的 SQL 查询结果是相同的:SELECT employee_id,last_name,department_name FROM employees e ,departments d WHERE e.department_id = d.department_id;注意:using只能和join配合使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等三、子查询1.不相关子查询子查询就是查询语句的嵌套,有多个select语句 子查询的引入:-- 查询所有比“CLARK”工资高的员工的信息 -- 步骤1:“CLARK”工资mysql> select * from emp where ename='clark'; 工资2450 +-------+-------+---------+------+------------+---------+------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+-------+---------+------+------------+---------+------+--------+ | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | +-------+-------+---------+------+------------+---------+------+--------+ 1 row in set (0.00 sec)-- 步骤2:查询所有工资比2450高的员工的信息mysql> select * from emp where sal > 2450; +-------+-------+-----------+------+------------+---------+------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+-------+-----------+------+------------+---------+------+--------+ | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | +-------+-------+-----------+------+------------+---------+------+--------+ 5 rows in set (0.01 sec) 两次命令解决问题的话,效率低 ,第二个命令依托于第一个命令,第一个命令的结果给第二个命令使用,但是 因为第一个命令的结果可能不确定要改,所以第二个命令也会导致修改 将步骤1和步骤2合并 --》子查询:-- 一个命令解决问题 --》效率高mysql> select *from emp where sal>(select sal from emp where ename='clark'); +-------+-------+-----------+------+------------+---------+------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+-------+-----------+------+------------+---------+------+--------+ | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | +-------+-------+-----------+------+------------+---------+------+--------+ 5 rows in set (0.00 sec)【2】执行顺序:先执行子查询,再执行外查询;【3】不相关子查询:子查询可以独立运行,称为不相关子查询。【4】不相关子查询分类:根据子查询的结果行数,可以分为单行子查询和多行子查询。练习单行子查询mysql> -- 单行子查询 mysql> -- 查询工资高与拼接工资的员工名字和工资 mysql> select ename,sal from emp -> where sal>(select avg(sal) from emp); +-------+---------+ | ename | sal | +-------+---------+ | JONES | 2975.00 | | BLAKE | 2850.00 | | CLARK | 2450.00 | | SCOTT | 3000.00 | | KING | 5000.00 | | FORD | 3000.00 | +-------+---------+ 6 rows in set (0.00 sec)-- 查询和CLARK同一部门且比他工资低的雇员名字和工资。 select ename,sal from emp where deptno = (select deptno from emp where ename = 'CLARK') and sal < (select sal from emp where ename = 'CLARK') +--------+---------+ | ename | sal | +--------+---------+ | MILLER | 1300.00 | +--------+---------+ 1 row in set (0.00 sec) 多行子查询: 【1】查询【部门20中职务同部门10的雇员一样的】雇员信息。 -- 查询雇员信息 select * from emp; +-------+--------+-----------+------+------------+---------+---------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+--------+-----------+------+------------+---------+---------+--------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | +-------+--------+-----------+------+------------+---------+---------+--------+ 14 rows in set (0.00 sec) -- 查询部门20中的雇员信息 select * from emp where deptno = 20; +-------+-------+---------+------+------------+---------+------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+-------+---------+------+------------+---------+------+--------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | +-------+-------+---------+------+------------+---------+------+--------+ 5 rows in set (0.00 sec) -- 部门10的雇员的职务: select job from emp where deptno = 10; -- MANAGER,PRESIDENT,CLERK +-----------+ | job | +-----------+ | MANAGER | | PRESIDENT | | CLERK | +-----------+ 3 rows in set (0.00 sec) -- 查询部门20中职务同部门10的雇员一样的雇员信息。 select * from emp where deptno = 20 and job in (select job from emp where deptno = 10) -- > Subquery returns more than 1 row select * from emp where deptno = 20 and job = any(select job from emp where deptno = 10) 【2】查询工资比所有的“SALESMAN”都高的雇员的编号、名字和工资。 -- 查询雇员的编号、名字和工资 select empno,ename,sal from emp +-------+--------+---------+ | empno | ename | sal | +-------+--------+---------+ | 7369 | SMITH | 800.00 | | 7499 | ALLEN | 1600.00 | | 7521 | WARD | 1250.00 | | 7566 | JONES | 2975.00 | | 7654 | MARTIN | 1250.00 | | 7698 | BLAKE | 2850.00 | | 7782 | CLARK | 2450.00 | | 7788 | SCOTT | 3000.00 | | 7839 | KING | 5000.00 | | 7844 | TURNER | 1500.00 | | 7876 | ADAMS | 1100.00 | | 7900 | JAMES | 950.00 | | 7902 | FORD | 3000.00 | | 7934 | MILLER | 1300.00 | +-------+--------+---------+ 14 rows in set (0.00 sec) -- “SALESMAN”的工资: select sal from emp where job = 'SALESMAN'; +---------+ | sal | +---------+ | 1600.00 | | 1250.00 | | 1250.00 | | 1500.00 | +---------+ 4 rows in set (0.00 sec) -- 查询工资比所有的“SALESMAN”都高的雇员的编号、名字和工资。 -- 多行子查询: select empno,ename,sal from emp where sal > all(select sal from emp where job = 'SALESMAN'); +-------+-------+---------+ | empno | ename | sal | +-------+-------+---------+ | 7566 | JONES | 2975.00 | | 7698 | BLAKE | 2850.00 | | 7782 | CLARK | 2450.00 | | 7788 | SCOTT | 3000.00 | | 7839 | KING | 5000.00 | | 7902 | FORD | 3000.00 | +-------+-------+---------+ 6 rows in set (0.00 sec) 2.相关子查询【1】不相关的子查询引入:不相关的子查询:子查询可以独立运行,先运行子查询,再运行外查询。相关子查询:子查询不可以独立运行,并且先运行外查询,再运行子查询【2】不相关的子查询优缺点:好处:简单 功能强大(一些使用不相关子查询不能实现或者实现繁琐的子查询,可以使用相关子查询实现)缺点:稍难理解【3】sql展示:-- 【1】查询最高工资的员工 (不相关子查询) select * from emp where sal = (select max(sal) from emp) -- 【2】查询本部门最高工资的员工 (相关子查询) -- 方法1:通过不相关子查询实现: select * from emp where deptno = 10 and sal = (select max(sal) from emp where deptno = 10) union select * from emp where deptno = 20 and sal = (select max(sal) from emp where deptno = 20) union select * from emp where deptno = 30 and sal = (select max(sal) from emp where deptno = 30) -- 缺点:语句比较多,具体到底有多少个部分未知 -- 方法2: 相关子查询 select * from emp e where sal = (select max(sal) from emp where deptno = e.deptno) order by deptno -- 【3】查询工资高于其所在岗位的平均工资的那些员工 (相关子查询) -- 不相关子查询: select * from emp where job = 'CLERK' and sal >= (select avg(sal) from emp where job = 'CLERK') union ...... -- 相关子查询: select * from emp e where sal >= (select avg(sal) from emp e2 where e2.job = e.job) 四、聚合函数1.聚合函数介绍聚合函数作用于一组数据,并对一组数据返回一个值。聚合函数类型AVG()SUM()MAX()MIN()COUNT()语法注意:聚合函数不允许嵌套使用1.1 AVG和SUM函数可以对数值型数据使用AVG 和 SUM 函数。他们在计算有空值的时候,会把非空计算进去,然后自动忽略空值AVG=SUM/COUNT mysql> select * from emp; +-------+--------+-----------+------+------------+---------+---------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+--------+-----------+------+------------+---------+---------+--------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 | | 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 | | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 | | 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 | | 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 | | 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 | | 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 | | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 | | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 | | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 | | 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 | | 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 | +-------+--------+-----------+------+------------+---------+---------+--------+ 14 rows in set (0.00 sec)1.2 MIN和MAX函数可以对任意数据类型的数据使用 MIN 和 MAX 函数。1.3 COUNT函数COUNT(*)返回表中记录总数,适用于任意数据类型。 mysql> select count(*) from emp; +----------+ | count(*) | +----------+ | 14 | +----------+ 1 row in set (0.01 sec)计算指定字段再查询结果中出现的个数 mysql> select count(comm) from emp; +-------------+ | count(comm) | +-------------+ | 4 | +-------------+ 1 row in set (0.00 sec)COUNT(expr) 返回expr不为空的记录总数。-问题:用count(*),count(1),count(列名)谁好呢?其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)。问题:能不能使用count(列名)替换count(*)?不要使用 count(列名)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 说明: count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。这样子讲的话,大家可能还比较懵,接下来,我来演示一下2.group by使用group by可以进行分组,我们以前使用avg可以求出所有员工的平均工资,但是如果我们想要求各个部门的员工的平均工资的话,就得对部门进行分组,以部门为单位来划分,然后求出他们各自的平均工资注意:字段不可以和多行函数一起使用,因为记录个数不匹配,这样就会导致查询的数据没有全部展示,但是,如果这个字段属于分组是可以的 mysql> select deptno,avg(sal) from emp group by deptno; +--------+-------------+ | deptno | avg(sal) | +--------+-------------+ | 20 | 2175.000000 | | 30 | 1566.666667 | | 10 | 2916.666667 | +--------+-------------+ 3 rows in set (0.00 sec)统计各个岗位的平均工资 mysql> select job,avg(sal) from emp group by job; +-----------+-------------+ | job | avg(sal) | +-----------+-------------+ | CLERK | 1037.500000 | | SALESMAN | 1400.000000 | | MANAGER | 2758.333333 | | ANALYST | 3000.000000 | | PRESIDENT | 5000.000000 | +-----------+-------------+ 5 rows in set (0.00 sec)3.使用having进行分组后的筛选使用having的条件:1 行已经被分组。使用了聚合函数。满足HAVING 子句中条件的分组将被显示。HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。统计各个部门的平均工资 ,只显示平均工资2000以上的 - 分组以后进行二次筛选 havingmysql> select deptno,avg(sal) from emp -> group by deptno -> having avg(sal) >2000; +--------+-------------+ | deptno | avg(sal) | +--------+-------------+ | 20 | 2175.000000 | | 10 | 2916.666667 | +--------+-------------+ 2 rows in set (0.01 sec) # 五、where和having的对比区别1:WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件;HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件。 这决定了,在需要对数据进行分组统计的时候,HAVING 可以完成 WHERE 不能完成的任务。这是因为,在查询语法结构中,WHERE 在 GROUP BY 之前,所以无法对分组结果进行筛选。HAVING 在 GROUP BY 之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选,这个功能是 WHERE 无法完成的。另外,WHERE排除的记录不再包括在分组中。区别2:如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接后筛选。 这一点,就决定了在关联查询中,WHERE 比 HAVING 更高效。因为 WHERE 可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。 小结如下:开发中的选择:WHERE 和 HAVING 也不是互相排斥的,我们可以在一个查询里面同时使用 WHERE 和 HAVING。包含分组统计函数的条件用 HAVING,普通条件用 WHERE。这样,我们就既利用了 WHERE 条件的高效快速,又发挥了 HAVING 可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。六、select的执行过程1.关键字顺序SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...2.SELECT 语句的执行顺序FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT比如你写了一个 SQL 语句,那么它的关键字顺序和执行顺序是下面这样的:SELECT DISTINCT player_id, player_name, count(*) as num 顺序 5 FROM player JOIN team ON player.team_id = team.team_id 顺序 1 WHERE height > 1.80 顺序 2 GROUP BY player.team_id 顺序 3 HAVING num > 2 顺序 4 ORDER BY num DESC 顺序 6 LIMIT 2 顺序 73.SQL的执行原理(先了解)SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2。然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4。当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段。首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1 和 vt5-2。当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6。最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7。当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。下一篇事务问题写在同专栏上面
SessionSession用来实现用户会话Session对应类名:HttpSession(jarkata.servlet.http.HttpSession)Session是JSP内置的对象会话的理解用户打开浏览器,客户端和服务器之间发生的一系列连续的请求和响应,最后把浏览器关闭,这个过程叫做一次会话。会话在服务器端有对应的Java对象---Session以我自己对会话的理解来说,会话可以类比打电话,打一次电话是一次会话,电话挂断代表会话结束,那么同样的打开浏览器进行一系列操作,直到浏览器关闭才代表会话结束一次会话对应N个请求对于会话有了基本的理解以后,我们来看一下,它的作用是什么Session的作用保存会话状态。(用户登录成功了,这是一种登录成功的状态,你怎么把登录成功的状态一直保存下来呢?使用session对象可以保留会话状态。)为什么不用request或者ServletContest来保存会话状态request.setAttribute()存,request.getAttribute()取,ServletContext也有这个方法。request是请求域。ServletContext是应用域。ServletContext对象是服务器启动的时候创建,服务器关闭的时候销毁,这个ServletContext对象只有一个。request对象的生命周期太短了。以在淘宝买东西作为例子:我们要买东西,先把商品加入购物车,我们是不是想要每一次请求都放在同一个购物车里面,但是request对象是一次请求一个,如果使用它的话,你会发现,每次请求放的购物车都不一样,你是不是很郁闷,所以不能使用request对象存放会话状态,如果我们使用的是ServletContext的话,那么就会变成所有用户共享一个购物车,最后你会发现购物车一堆的商品,所以也不适合。既然Session是用来保存会话状态的,那么我们就会有一个疑惑,为什么要用它来保存会话状态。因为客户端发送请求以后,它和服务器的连接就断开了。这就涉及到HTTP 协议的无状态特点,相信大家可能也有困惑,所以在正式说明Session的原理以前,我先来给大家说一下,HTTP协议的无状态特点是什么意思HTTP协议的无状态特点服务器没有办法识别每一次请求是从哪一台电脑访问的,它能接收请求,但是它不知道这个请求是从哪里来的,不知道要响应给谁。比如说我们买东西,添加购物车,由于它无法识别是来自哪一个客户端的请求,它就可能把我们的请求发送给其他人,所以必须要有一种技术来让服务器知道请求来自哪里,这就是会话技术Session的实现原理(重点)Session对象是存储在服务器中的我在下面这个图对Session对象的创建做了很清楚的说明了,我相信大家看了应该都能挺清楚的,而不是含含糊糊的。由于下面的图片中,出现了Session超时机制,我现在就举个例子,给大家说明一下这个机制运用的地方。比如说你本来登录淘宝想买东西,结果这个时候,家里面来客人了,你就去招待客人了,很长一段时间里面,都没有再去碰过淘宝,也就是说明,在很长一段时间里面,你都没有发送请求给服务器,这个时候,服务器就会把Session对象销毁,由于Session对象存储登录信息,你就会发现当客人走了以后,当你想要去淘宝继续买东西的时候,还需要重新登录。因为这个系统会验证你是否曾经登录过,登录过的才可以继续访问,但是Session对象是存储登录信息的,Session对象都已经销毁了,当然无法访问。Session超时机制简单来说就是长时间没有发送请求,服务器会销毁Session对象大家看了上面的图应该已经有了一定的理解了吧。现在是否有一个疑惑,为什么我们每一次发送请求,它都能找到同一个Session对象,而不是找到其他对象呢。就比如说同一时间有多个人在访问淘宝网站,为什么不同的人发送请求每次都可以返回他们的Session对象,为什么不会拿到其他人的Session对象。大家可以想一下有什么方法可以很好的解决呢?其实很容易可以想到,就是标记,没错吧,通过标记,我们就可以知道要返回哪一个Session对象。我们可以给每一个Session对象生成一个编号,然后把这个编号发送给浏览器,在浏览器上面保存起来,那我们发送请求给服务器的时候,就能自动把编号发给服务器,服务器根据这个编号来找相应的Session对象。一个编号对应一个Session对象,这种存储方法是不是让你想到了Map集合,专业术语是Session列表会话状态:指的是服务器和浏览器在会话过程中产生的状态信息,借助于会话状态,服务器可以把属于同一次会话的一系列请求和响应关联起来--sessionid从一个终端发起的请求都会带有sessionid(Session对象的编号),这样我们通过id就可以标注它,服务器就知道要响应给谁,就比如说有好多人打电话给你,你手机没有来电显示,那我们是不是就得回拨电话回去,如果我们不知道电话号码不就不知道打电话给谁,但是如果显示电话号码,我们就知道打电话给谁了。HttpSession session = request.getSession(); --用来获取Session对象如果在同一个浏览器打开页面,他们的sessionid是一样的属于同一次会话的请求都有一个相同的标识符:sessionid其实sessionid是被包装成Cookie发送给浏览器的(后面会说,大家先有个印象) Session实现原理总结session的实现原理: 有缓存 JSESSIONID=xxxxxx 这个是以Cookie的形式保存在浏览器的内存中的。浏览器只要关闭。这个cookie就没有了。 session列表是一个Map,map的key是sessionid,map的value是session对象。 用户第一次请求,服务器生成session对象,同时生成id,将id发送给浏览器。 用户第二次请求,自动将浏览器内存中的id发送给服务器,服务器根据id查找session对象。关闭浏览器,内存消失,cookie消失,sessionid消失,会话等同于结束。Session常用方法:Session在实际开发中是用来记录我们的用户信息的,我们不需要每一次访问都输入用户名和密码,如果登录过一次,后面可以不用再输入,但是这是有一个周期的,不可能一直存着的,默认失效时间为1800秒方法作用String getId()获取sessionIDvoid setMaxInactiveInterval(int interval)设置session的失效时间,时间单位为秒int getMaxInactiveInterval()获取当前session的失效时间void invalidate()设置session立即失效,也就是销毁session对象(比如说登录一个网站以后,点击退出,我们就可以用这个方法)void setAttribute(String key,Object value)通过键值对的形式存储数据(这个方法类似于map的put方法,可以用来存储数据,也可以用来修改数据,如果用来修改数据的话,前后两次的key要相同)Object getAttribute(String key)通过键来获取对应的数据void removeAttribute(String key)通过键来删除对应的数据<%-- Created by IntelliJ IDEA. User: 17614 Date: 2022-03-20 Time: 11:20 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <% String id = session.getId(); int interval = session.getMaxInactiveInterval(); %> <%="sessionid=" + id%><br> <%="失效时间:" + interval%> </body> </html> 还有一种方式可以查明Session的默认失效时间我们可以找到当时Tomcat的安装目录,点击config,找到web.xml发现里面有这样的代码,说明Session默认失效时间是30分钟 <session-config> <session-timeout>30</session-timeout> </session-config> 实现会话的两种方式:cookie是浏览器提供用来存储用户信息,session是Java程序提供的,他们都可以存储信息,都可以描述一次会话,会话是客户端和服务端的交互,session作用于服务端,cookie作用于客户端那么我们接下来来讲一下CookieCookie基本介绍session的实现原理中,每一个session对象都会关联一个sessionid,例如:JSESSIONID=3D537CF1206DCEEBD38E8287472C21C2以上的这个键值对数据其实就是cookie对象。对于session关联的cookie来说,这个cookie是被保存在浏览器的“运行内存”当中。只要浏览器不关闭,用户再次发送请求的时候,会自动将运行内存中的cookie发送给服务器。例如,这个Cookie: JSESSIONID=3D537CF1206DCEEBD38E8287472C21C2就会再次发送给服务器。服务器就是根据3D537CF1206DCEEBD38E8287472C21C2这个值来找到对应的session对象的。那么cookie怎么生成?cookie保存在什么地方?cookie有啥用?浏览器什么时候会发送cookie,发送哪些cookie给服务器?cookie最终是保存在浏览器客户端上的。可以保存在运行内存中。(浏览器只要关闭cookie就消失了。)也可以保存在硬盘文件中。(永久保存。)Cookie(Java的一个类)存储数据的形式和map差不多,以键值对的形式存储数据Cookie在客户端存储,Session在服务端存储Cookie是服务端在HTTP响应中附带传给浏览器的一个小文本文件,一旦浏览器保存了某一个Cookie,在之后的请求和响应过程中,会将此Cookie来回传递,这样就可以通过Cookie这个载体完成客户端和服务端的数据交互,归根到底来说,之所以要有Cookie和Session机制,是因为HTTP协议无状态无连接协议。经典案例京东商城,在未登录的情况下,向购物车中放几件商品。然后关闭商城,再次打开浏览器,访问京东商城的时候,购物车中的商品还在,这是怎么做的?我没有登录,为什么购物车中还有商品呢?将购物车中的商品编号放到cookie当中,cookie保存在硬盘文件当中。这样即使关闭浏览器。硬盘上的cookie还在。下一次再打开京东商城的时候,查看购物车的时候,会自动读取本地硬盘中存储的cookie,拿到商品编号,动态展示购物车中的商品。京东存储购物车中商品的cookie可能是这样的:productIds=xxxxx,yyyy,zzz,kkkk注意:cookie如果清除掉,购物车中的商品就消失了。126邮箱中有一个功能:十天内免登录这个功能也是需要cookie来实现的。怎么实现的呢?用户输入正确的用户名和密码,并且同时选择十天内免登录。登录成功后。浏览器客户端会保存一个cookie,这个cookie中保存了用户名和密码等信息,这个cookie是保存在硬盘文件当中的,十天有效。在十天内用户再次访问126的时候,浏览器自动提交126的关联的cookie给服务器,服务器接收到cookie之后,获取用户名和密码,验证,通过之后,自动登录成功。怎么让cookie失效?十天过后自动失效。或者改密码。或者在客户端浏览器上清除cookie。cookie机制和session机制其实都不属于java中的机制,实际上cookie机制和session机制都是HTTP协议的一部分。php开发中也有cookie和session机制,只要是你是做web开发,不管是什么编程语言,cookie和session机制都是需要的。HTTP协议中规定:任何一个cookie都是由name和value组成的。name和value都是字符串类型的。在java的servlet中,对cookie提供了哪些支持呢?提供了一个Cookie类来专门表示cookie数据。jakarta.servlet.http.Cookie;java程序怎么把cookie数据发送给浏览器呢?response.addCookie(cookie);在HTTP协议中是这样规定的:当浏览器发送请求的时候,会自动携带该path下的cookie数据给服务器。(URL。)创建cookie Cookie cookie=new Cookie(String name,String value);response.addCookie(cookie); 每一个浏览器都有不同的cookie<% // public Cookie(String name, String value) Cookie cookie=new Cookie("name","张三"); response.addCookie(cookie); // 读取cookie Cookie[] cookies = request.getCookies(); for (Cookie cookie1 :cookies) { out.write(cookie1.getName()+": "+cookie1.getValue()+"<br>"); } %>Cookie常用方法方法用途void setMaxAge(int age)设置Cookie的有效时间,单位为秒int getMaxAge()获取Cookie的有效时间String getName/()获取Cookie的nameString getValue()获取Cookie的valueSession和Cookie的区别Session:保存在服务器,Session是一个对象保存在Java虚拟机中 保存的数据是Object 随着会话的结束而销毁 保存重要信息Cookie:保存在浏览器只能保存String类型,类似于文本文件,存放的都是数据,而不是对象可以长期保存在浏览器,与会话无关保存不重要信息存储用户信息:Session:setAttribute(name,"admin") 存getAttribute(name) 取生命周期:服务端:只要WEB应用重启或者销毁 客户端:只要浏览器关闭就销毁 退出登录:session.invalidate();Cookie: Cookie cookie=new Cookie(name,"admin"); response.addCookie(cookie); 存 取数据 Cookie[] cookies=request.getCookie(); for(Cookie cookie:cookies){ if(cookie.getName().equals("name"){ out.write("欢迎回来"+cookie.getValue()); } }生命周期:不会随着服务端的重启而销毁,客户端:默认是只要关闭浏览器就会销毁,我们通过setMaxAge()方法来设置有效期,一旦设置了有效期,就不会随着浏览器的关闭而销毁,而是由设置的时间来决定退出登录:setMaxAge(0)Cookie是浏览器提供的一种技术,通过服务器的程序能把一些只须保存在客户端,或者在客户端进行处理的数据放在本地计算机上,不需要通过网络传送,因此提高网页处理效率,并且可以减少服务器的负载,但是因为Cookie是服务器端保存在客户端的信息,所以它的安全性也是很差的,例如:常见的记住密码就可以通过Cookie来实现如果想要把Cookie随着响应发送到客户端,需要先添加到response对象中cookie默认是关闭浏览器失效Cookie的有效时间取值负整数:表示不存储该cookiecookie的maxAge属性的默认值是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么cookie就会消失正整数:若大于0的整数,表示存储的秒数,表示cookie对象可存活的指定的秒数,当生命>0,浏览器会把cookie保存到硬盘,就算关闭浏览器,或者重启客户端电脑,cookie也会存活相应的时间若为0表示删除该cookie,cookie的生命=0是一个特殊值,表示cookie作废,也就是说如果原来浏览器已经保存了这个cookie,那么可以通过cookie的setMaxAge(0)来删除这个cookie,无论在浏览器中还是在客户端硬盘中都会删除这个Cookiecookie注意点1.cookie保存在当前浏览器,不能跨浏览器,更不用说换电脑了2.cookie存中文问题cookie不能存中文,如果有中文,则通过URLEncoder.encode()来进行编码通过URLDecoder.decode()进行解码3.同名cookie问题如果服务器发送重复的cookie,那么会覆盖原来的cookie4.cookie的数量不同浏览器对cookie有限定,cookie的存储是有上限的,cookie存储在客户端(浏览器)的,而且一般是由服务器创建和指定,后期结合Session来实现会话追踪Cookie的路径问题Cookie的setPath(可以设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie情景一:当前服务器下的任何项目的任意资源都可以获取Cookie对象 //当前项目路径s Cookie cookie=new Cookie("xxx","xxx"); //设置路径为"/",表示在当前项目下的任何项目都可以访问到cookie对象 cookie.setPath("/"); response.addCookie(cookie);情景二:当前项目下的资源都可获取Cookie对象(默认不设置Cookie的path)当前项目路径s Cookie cookie=new Cookie("xxx","xxx"); //设置路径为"/s",表示在当前项目下的任何项目都可以访问到cookie对象 //默认情况下可以不设置path的值 cookie.setPath("/s"); response.addCookie(cookie); 情景三:指定项目下的资源可获取Cookie对象当前项目路径s Cookie cookie=new Cookie("xxx","xxx"); //设置路径为"/s2",表示在s2项目下才可以访问到 cookie.setPath("/s2"); //只能在s2项目下获取cookie,就算cookie是s产生的,s也不能获取它 response.addCookie(cookie);情景四:指定目录下的资源可获取Cookie对象//当前项目路径s Cookie cookie=new Cookie("xxx","xxx"); //设置路径为/s/cook,表示在s1/cook目录下面才可以访问到cookie对象 cookie.setPath("/s/cook"); response.addCookie(cookie);Cookie禁用问题Cookie禁用了,session还能找到吗?cookie禁用就是说服务器正常发送cookie给浏览器,但是浏览器不要了。拒收了。并不是服务器不发了。 找不到了。每一次请求都会获取到新的session对象。 cookie禁用了,session机制还能实现吗? 可以。需要使用URL重写机制。http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16 URL重写机制会提高开发者的成本。开发人员在编写任何请求路径的时候,后面都要添加一个sessionid,给开发带来了很大的难度,很大的成本。所以大部分的网站都是这样设计的:如果禁用cookie,就别用了。
ResultType是默认映射,要求字段名要和属性名一致,但是如果我们数据库中的表的字段名和实体类的属性名不一致怎么办呢?这个时候就得用ResultMap来自定义映射前期准备先准备两张表,一张是员工表,一张部门表1.解决字段名和属性名不一致的情况1.1方法一 为字段起别名,保持属性名和字段名的一致当我们写的实体类的属性遵循驼峰,属性名和字段名不一致的话,如果我们使用sql语句的返回值类型用的是resultType的话,是差不多数据的<select id="getAllEmp" resultType="com.atguigu.mybatis.pojo.Emp"> select * from t_emp; </select>我们可以看到empName是没有数据的解决方法一,sql语句查询的时候使用别名,别名要和属性名一致> <select id="getAllEmp" resultType="com.atguigu.mybatis.pojo.Emp"> select eid,emp_name empName,age,sex,email from t_emp; </select>这样子可以查到数据1.2方法二 设置全局配置,把下划线映射为驼峰方法二 使用settings进行全局映射<!-- 设置全局属性值--> <settings> <!-- 把下划线映射为驼峰 emp_name映射成empName,em_pname映射成emPname--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>> <select id="getAllEmp" resultType="com.atguigu.mybatis.pojo.Emp"> -- select eid,emp_name empName,age,sex,email from t_emp; select * from t_emp; </select>1.3方法三 使用ResultMap进行自定义映射设置字段和属性的关系<!-- resultMap:设置自定义映射关系 id:唯一标识 type:设置映射关系中的实体类类型--> <resultMap id="empResultMap" type="Emp"> <!-- id设置主键的映射关系,result设置普通字段的映射关系 property设置映射关系中的属性名,必须是type属性所设置的实体类类型中的属性名 column:设置映射关系中的字段名,必须是sql语句查询出的字段名--> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </resultMap> <select id="getAllEmp" resultMap="empResultMap"> select * from t_emp; </select>我们都知道数据库中表和表是有关系的,那么它们所对应的实体类,我们要怎样才能使他们有关系呢2.多对一的映射关系比如说两张表emp,dept,emp表中有dept表中的部门编号属性,通过这个属性可以查询对应的信息,我们就可以在emp表所对应的实体类中创建对象dept,来表示当前员工所对应的部门对象,我们知道一个部门有多个员工,我们就可以在部门表对应的实体类中设置一个属性,表示员工的集合。但是这里有一个问题需要我们来解决,我们在mybatis中,通过字段名和属性进行映射,但是,举个例子:我们想要查询员工表的部门编号和部门表的部门编号相等的员工信息,员工表对应的实体类中属性是Dept dept,但是查询出来的结果中,会有部门名字和部门编号,这个时候就无法进行映射,那要怎么解决?接下来给出具体解决方法2.1方法一 级联属性赋值2.2方法二 通过assocation解决多对一问题<resultMap id="empAndDeptResultMapTwo" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <association property="dept" javaType="Dept"> <id property="deptName" column="dept_name"></id> <id property="did" column="did"></id> </association> </resultMap> <select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo"> SELECT * FROM t_emp LEFT JOIN t_dept ON t_emp.did=t_dept.did WHERE t_emp.did=2; </select>我们之前的方法都是通过一条sql语句查询多张表,把数据查询出来,那么我们能不能通过多条sql语句,一步一步查询出来呢?我们可以先把员工信息查询出来,然后根据员工信息的部门编号把对应的部门信息查询出来,然后把查询出来的部门信息赋值给我们的dept属性,这个方法就是分步查询2.3方法三 分步查询(用得比较多) <font size=4 color="bluefeafefgrbtnhyjythhrsgaeqwegq">**select**设置分步查询的sql的唯一标识(命名空间.SQLID 或mapper接口的全类名.方法名) **column**设置分步查询的条件,比如说我们想要查询部门信息,那么应该是根据部门编号来查询的 我们查询到了员工信息,那要怎么根据这个信息来查询部门信息呢,那就得根据它们共有的字段did属性也就是部门编号来查询 在这个sql语句里面我们查询的是员工信息,但是dept的信息要根据另外一条sql语句来查询 所以这里要通过select属性来说明我们是通过哪一个sql查询出来的 <resultMap id="empAndDeptByStepResultMapOne" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <!-- column设置分步查询的条件,比如说我们想要查询部门信息,那么应该是根据部门编号来查询的 我们查询到了员工信息,那要怎么根据这个信息来查询部门信息呢,那就得根据它们共有的字段did属性也就是部门编号来查询 在这个sql语句里面我们查询的是员工信息,但是dept的信息要根据另外一条sql语句来查询 所以这里要通过select属性来说明我们是通过哪一个sql查询出来的--> <association property="dept" select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did"></association> </resultMap> <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMapOne"> select * from t_emp where eid= #{eid}; </select> <select id="getEmpAndDeptByStepTwo" resultType="com.atguigu.mybatis.pojo.Dept"> select * from t_dept where did= #{did}; </select>测试结果这个时候大家可能就会有这样的疑惑,我们之前的方法明明可以通过一个SQL语句来解决问题,为什么非得用两个sql语句呢?这样子不是变得更加复杂了吗?其实我们可以发现这两个sql语句分开,各自都是一个功能分步查询的好处是延迟加载,在mybatis中,延迟加载默认是不开启延迟加载的,我们如果想要实现延迟加载,就必须在核心配置文件中设置全局配置信息(settings标签)。 <settings> <!-- 把下划线映射为驼峰 emp_name映射成empName,em_pname映射成emPname--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> </settings>lazyLoadingEnabled:延迟加载的全局开关,当开启时,所有关联对象(分步查询的第一步,第二步......)都会延迟加载开启延迟加载后,我们会发现只执行查询员工sql语句,没有执行dept的sql语句如果关闭延迟加载,我们会发现我们本来只是想要查询员工的名字,它还会执行查询部门的sql语句aggresslveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每一个属性会按需加载延迟加载指的是当我们访问哪一些信息,它就会去执行相应的sql,如果没有访问,则没有执行此时就可以实现按需加载,获取的数据是什么就只会执行相应的sql,此时可以通过assocation和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加载) | eager(立即加载)"select:设置分步查询的sql唯一标识column:设置分步查询的条件fetchtype:当开启了全局的延迟加载以后,可以通过这个属性手动控制延迟加载的效果,如果没有开启延迟加载,那么这个属性可以取的两个值都表示立即加载3.一对多的映射关系3.1方法一 通过collection <resultMap id="deptAndEmpResultMap" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <!-- collection:处理一对多的映射关系--> <!-- ofTypeL:表示该属性所对应的集合中存储数据的类型--> <collection property="emps" ofType="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </collection> </resultMap> <select id="getDeptAndEmp" resultMap="deptAndEmpResultMap"> select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did} </select> collection:处理一对多的映射关系--> ofType:表示该属性所对应的集合中存储数据的类型 3.2分步查询先根据部门编号did查询出部门信息,然后再查询出所有员工信息package com.atguigu.mybatis.mapper; import com.atguigu.mybatis.pojo.Dept; import org.apache.ibatis.annotations.Param; /** * @author zengyihong * @create 2022--04--03 14:40 */ public interface DeptMapper { Dept getDeptAndEmp(@Param("did") Integer did); /** * 分步查询部门以及部门中所有的员工信息 * 分步查询第一步:查询部门信息 */ Dept getDeptAndEmpByStepOne(@Param("did") Integer did); } package com.atguigu.mybatis.mapper; import com.atguigu.mybatis.pojo.Emp; import org.apache.ibatis.annotations.Param; import java.util.List; /** * @author zengyihong * @create 2022--04--03 14:40 */ public interface EmpMapper { /** * 分步查询部门以及部门中所有的员工信息 * 分步查询第二步:根据did查询员工信息 */ List<Emp> getDeptAndEmpByStepTwo(@Param("did")Integer did) } deptmapper.xml <resultMap id="deptAndEmpResultMap" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <!-- collection:处理一对多的映射关系--> <!-- ofType:表示该属性所对应的集合中存储数据的类型--> <collection property="emps" ofType="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </collection> </resultMap> <resultMap id="deptAndEmpByStepResultMap" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <collection property="emps" select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" column="did"> </collection> </resultMap> <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepResultMap"> select * from t_dept where did = #{did}; </select>empmapper.xml <select id="getDeptAndEmpByStepTwo" resultType="Emp"> select * from t_emp where did = #{did}; </select>
一.物理层基本概念先强调一下,物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体,传输媒体不属于计算机网络体系结构的任何一层。1.物理层的作用和意义①物理层是为了解决在各种传输媒体种的0和1问题②物理层的作用是尽可能的屏蔽掉不同传输媒体和通信手段的差异,让物理层上面的数据链路层感受不到差异,这样就可以使数据链路层只要考虑怎么完成本层的协议和服务,不需要考虑网络具体的传输媒体和通信手段是什么大家可以了解一下,这里的一个概念,物理层规程:就是用在物理层的协议2.物理层的主要任务物理层的主要任务是确认和传输媒体的接口有关的一些特性,接下来我们来看看都有哪一些特性。① 机械特性指明接口所用接线器的性质和尺寸、引脚数目和排列、固定和锁定装置等 ②电气特性指明在接口电缆的各条线上出现的电压范围③功能特性指明某条线上出现的某一电平的电压的意义④过程特性指明对于不同功能的各种可能事件的出现顺序二、数据通信的基础知识1.数据通信的模型数据通信系统可以分成三大部分:①源系统(或发送端,发送方) ②传输系统(或传输网络) ③目的系统(或接收端、接收方)大家先对上面图片的过程有个了解,接下来我们来看看里面的具体部分源系统包含两个部分:①源点(信源,源站):顾名思义就是信号的来源,数据的来源,源点设备产生要传输的数据,比如我们用键盘输入数字,计算机产生输出的数字比特流②发送器:调制器是典型的发送器,源点生成的数字比特流通常要通过发送器编码后才能传输目的系统一般来说也包含两个部分①接收器:接收信号,把它转换成可以被目的设备处理的信息,典型的接收器是解调器②终点(目的站,信宿):把从接收器获取的数字比特流输出,比如:把汉字在计算机屏幕显示接下来介绍一下常用术语消息: 语言,文字,视频,音频等都是数据:运送消息的实体,数据是使用特定方式表示的信息,通常是用有意义的符号序列信号:数据的电磁或电气表现根据信号代表消息的参数的取值方式不同,可以分成两类这里我们需要对码元有一定的了解,接下来我们来看看。在使用时间域的波形表示数字信号的时候,代表不同的离散数值的基本波形就是码元,简单来说,码元是构成信号的一般波形2.信道的几个基本概念信道通俗来说就是信号传输的媒介既然都已经说到了信号传输了,接下来我给大家来说一下几种传输方式我们先来看看串行传输和并行传输2.1串行传输和并行传输串行传输和并行传输2.2同步传输和异步传输同步传输和异步传输同步传输:数据块以稳定的比特流的形式传输,字节之间没有间隔接收端在每一个比特信号的中间时刻进行检测,用来判断接收的是比特0或比特1由于不同设备的时钟频率存在差异,不可能完全相同,传输大量数据中,所产生的判别时刻的累计误差,会导致接收端对比特信号的判别错位。不过这种情况也不是说无法解决,毕竟出现问题,我们肯定要想办法解决,我们可以让收发双方的时钟同步收发双方时钟同步的方法:外同步: 在收发双方之间添加一条单独的时钟信号线,发送端在发送数据信号的同时,另外发送一路时钟同步信号,接收端按照时钟同步信息的节奏接收数据。内同步: 发送端把时钟同步信号编码到发送数据中一起传输(比如:曼彻斯特编码)曼彻斯特玛在下文会具体介绍讲完同步传输,接下来,我们来说说异步传输异步传输:采用异步传输的方式时,以字节为独立的传输单位,字节之间的间隔不是固定的接收端仅仅在每一个字节的起始处对字节内的比特实现同步 因此,通常要在每一个字节的前和后分别加上起始位和结束位2.3通信双方的信息交互方式从通信双方的信息交互的方式来看,可以有三种基本方式:①单向通信:又称为单工通信,这是一种单方向的通信没有反方向的交互比如:电视广播,有线电广播②双向交替通信:又称为半双工通信,通信双方都可以发送信息,但是不能同时发送或同时接收,只能是一方发送,一方接收,比如:对讲机③双向同时通信:又称为全双工通信,通信双方可以同时发送和接收信息,比如:电话刚刚说了一下几种传输方式以后,我们再来看看其他的一些概念基带信号:信源发出的信号,计算机输出的各种文字,图像等都是基带信号调制:为了解决一些信道不能传输基带信号低频分量或直流分量3.常用编码方式(十分重要)接下来我就对各种编码方式进行详细的介绍3.1 不归零制(存在同步问题)正电平代表1,负电平代表0,整个码元时间内,电平不会出现零电平接收端如果想要判断是几个码元,就得需要额外的一根传输线来传输时钟信号,使得发送方和接收方同步,但是对于计算机网络,宁愿利用这根传输线来传输数据信号,而不是用来传输时钟信号。所以计算机网络不采用这种编码。3.2归零制(自同步,编码效率低)正脉冲代表1,负脉冲代表0归零制相当于把时钟信号用归零方式编码在数据之内,这称为自同步信号归零制:每一个码元传输结束以后都要归零,接收方只需要在信号归零以后进行采样,不需要单独的时钟信号,不过,归零制中大部分数据带宽都用来传输归零而浪费掉,所以效率低3.3曼彻斯特编码每一个码元的中间时刻,信号都会发生跳变,码元中间时刻的跳变即表示时钟又表示数据,传统的以太网就是采用曼彻斯特编码.我们可以规定正跳变表示1,也可以规定正跳变表示03.4差分曼彻斯特编码1.在每一个码元的中间时刻,信号都会发生跳变不过,差分曼彻斯特编码的跳变仅表示时钟,不表示数据2.码元开始处电平是否发生变化表示数据,开始边界有跳变表示0,没跳变表示13.5基本的带通调制方法使用基本的调制方法,一个码元只能包含一个比特信息,那怎样才能让一个码元包含多个比特信息呢?我们可以使用混合调制的方法相位和振幅通常可以结合起来一起调制使用QAM-16可以有12种相位,每种相位有或2种振幅可选4、信道的极限容量信号在信道上的传输不可避免都会出现失真码元的传输速率越高,信号传输的距离越远,噪声干扰越大或传输媒体质量越差,在接收端的波形的失真就越严重码间串扰:接收端收到的信号波形失去了码元之间的清晰界限1.奈氏准则在假定的理想条件下,为了避免码间串扰,码元传输速率是有上限的理想低通信道的最高码元传输速率=2W Baud=2W码元/秒W:信道带宽(单位HZ)Baud:波特,即码元/秒2.区分易混淆概念码元传输速率:单位时间内数字通信系统所传输的码元个数(也可称为脉冲个数或信号变化的次数)又称为波特率,调制速率,波形速率或符号速率,它和比特率有一定关系码元可以是多进制的,也可以是二进制,但是码元速率和进制数无关信息传输速率:别名信息速率,比特率,表示单位时间内数字通信系统传输的二进制码元个数(比特数)单位是比特/秒带宽:表示在单位时间内从网络的某一点到另外一点所能通过的最高数据率,经常用来表示网络的通信线路所能传输数据的能力3.香农公式三、物理层下面的传输媒体传输媒体也叫传输媒介,它分成两类,导引型传输媒体和非导引型传输媒体这里有太多的概念,书上介绍得很详细,我这里只是把一些比较重要的列举出来,具体的还是要看书1.导引型传输媒体在这里我来重点介绍一下光纤光纤是光纤通信的传输媒体,在发送端有光源,可以采用发光二极管或半导体激光器,它们能在电脉冲的作用下产生出光脉冲,在接收端利用光电二极管做成光检测器,在检测到光脉冲时可以还原出电脉冲多模光纤:存在多条不同角度入射的光线在一条光纤中传输,这种光纤就是多模光纤多模光纤对光源要求不高,可以用发光二极管来发送光源由于色散(模式、材料、波导色散),光在多模光纤传输一定距离以后,必然会产生信号失真(脉冲展宽)---多模光纤只适合近距离传输2.非导引型传输媒体四、信道复用技术(重要)这里既然说到了信道复用,那么我们肯定要先知道什么是复用复用:是通信技术上的一个重要概念,复用就是通过一条物理线路来同时传输多路用户的信号当网络中传输媒体的传输容量大于多条单一信道传输的总通信量的适合,就可以利用复用技术在一条物理线路上建立多条通信信道来充分利用传输媒体的带宽。常见的信道复用有:频分复用FDM,时分复用TDM,统计时分复用STDM,波分复用WDM,码分复用CDM,最基本的复用是频分复用和时分复用。1.频分复用FDM把传输线路的频带资源划分为多个子频道,形成多个子信道,各个子信道之间要留出隔离带,防止造成子信道之间的干扰。当多路信号输入一个多路复用器的时候,这一个复用器会把每一路信号调制到不同频率的载波上,接收端由响应的分用器通过滤波把各路信号分开,把合成的多路信号恢复成原始的多路信号频分复用的各路信号在同样的时间里面占用不同的带宽资源(这里的带宽是频率带宽而不是数据的发送速率)2.时分复用TDM把时间划分成为一个个的时隙,时分复用技术把传输线路的带宽资源按照时隙轮流分配给不同的用户,每对用户只在所分配的时隙里面使用线路传输数据下面的图中,我们可以看成每个时分复用帧有四个时隙 时分复用技术把时间划分成一段段等长的时分复用帧 每一个时分复用的用户在每一个时分用户帧中占用固定序号的时隙,每一个用户所占用的时隙是周期性出现的,它的周期就是时分复用帧的长度时分复用的所有用户在不同的时间占用同样的频带宽度我们这里来看一下,时分复用存在什么问题 我们知道时分复用的所有用户在不同的时间是占用相同的频带宽度的,但是如果用户在这段时间里面没有传输数据呢?那么这个时候这个已经分配到的信道就会处于空闲状态,其他想要发送信息的用户无法利用这个空闲的信道发送信息,这样就会导致信道的利用率不高。那么有什么办法可以解决这个问题吗?答案是肯定的,那就是接下来要讲的统计时分复用3.统计时分复用(STDM)统计时分复用不是固定分配资源,而是按需动态分配资源,科提高线路的利用率。统计时分复用使用STDM帧来传送复用的数据,每个STDM帧时隙数小于用户数,各用户有数据以后就随时发往集中器的输入缓存,集中器按照顺序依次扫描输入缓存,把存入的数据放到STDM帧。没有缓存的数据就直接跳过,一个帧的数据满了,就发送出去。注意:因为STDM帧中的数据不是固定分配给某一个用户的,所以在每一个时隙中,还必须要有用户的地址信息 每一个用户占用的时隙不是周期性出现,所以统计时分复用又叫异步时分复用,一般的时分复用叫同步时分复用4.波分复用WDM波分复用其实就是光的频分复用,使用一根光纤来同时传输多个频率很接近的光载波信号我们知道对于光来说,一旦波长确定,它的频率也就确定了我们可以把多数不同的光放在同一个线路上面,这种线路一般是多模线路,它可以发送多个线路进来码分复用CDM(重要)
计算机组成原理的相关笔记是我根据老师上课所讲,以及课后查找资料所写一、计算机系统概述1. 定义计算机是由硬件和软件组成的一种能够按照事先存储程序自动,高速进行大量数值运算以及各种信息处理的现代化智能电子设备看得见摸得着的是硬件,软件是虚的概念。注意:软件并不是仅仅只包含我们写的那些程序,还包括文档,这些文档可能是软件开发前的分析,以及开发后 进行维护等的文档,因为软件开发不是一个人的事,还要被其他人看,如果没有文档的话,别人单纯看程序的话,很有可能就不知道你在干什么。2.分类1.计算机按照处理信息的方式不同来划分数字计算机和模拟计算机的主要区别比较内容数字计算机模拟计算机数据表示方式0和1电压计算方式数字技术电压组合和测量值控制方式程序控制盘上连线精度高低数据存储量大小逻辑判断能力强无2.计算机按照通用性差异分类3.按照计算机性能分3.计算机系统的抽象层次1.从编程语言角度机器语言--->汇编语言--->高级语言2.语言虚拟机抽象3.从软硬件功能转换角度4.计算机硬件的逻辑等价性计算机中的许多功能,可以直接由专门硬件实现,也可以在基础硬件支持下依靠软件实现,对用户而言在功能上是等价的举例:以电风扇的开关为例子,我们可以通过一个按钮,通过按钮可以控制电流大小来控制风扇的开关,也可以是一个程序来控制风扇的开关,这对于用户来说是等价的,因为他们看到的效果是一样的,就是风扇的开或关计算机性能的好坏取决于"软 "硬“件功能的总和软件技术发展,硬件功能软件化——降低造价集成电路技术发展,软件功能硬件化——提高性能二、计算机系统的组成在接下来的举例中,主存和内存是一样的1.计算机系统的硬件组成1.1冯.诺依曼计算机(重点)世界上第一台计算机是ENIAC,冯.诺依曼是当时的顾问缺点:ENIAC:每一步的操作要执行什么指令,都要程序员手动接线去告诉计算机,ENIAC的运算速度就被手工操作的耗时给抵消了1945年,美国数学家冯·诺依曼提出“存储程序”概念,可以很好解决当时出现的问题,以此为基础研制的计算机叫冯·诺依曼机存储程序:将指令以二进制代码的形式事先输入计算机的主存储器(内存),然后按其在存储器的首地址执行程序的第一条指令,以后就按照该程序的规定顺序执行其他指令,直到程序执行结束第一台采用冯诺依曼结构的计算机EDVAC计算机是用来处理数据的,所以我们应该要有一个输入设备把数据输入到计算机中,存储器里面的数据包含我们要处理的数据,也包含指令。控制器通过电信号来协调其他器件相互配合工作,控制器也用来解析存储器中存储的程序指令。比如说,控制器从存储器中读取加法指令,那么就可以控制运算器进行相应的加法操作。对于一个功能来说,可以用软件来实现,也可以用硬件来实现,只不过是用软件来实现的成本比较低,不过效率也比较低。输入设备和输出设备统称为IO设备冯诺依曼计算机特点:ps:看到这么大的字大家也知道这个很重要吧,冯诺依曼结构也叫普林斯顿结构,因为他是普林斯顿的教授1.计算机由五大部分组成2.指令和数据以同等地位存在存储器,可以按照地址来访问3.指令和数据用二进制来表示,可以很方便的用0,1表示电信号的两种状态,同等地位存放于存储器,按地址访问4.指令由操作码和地址码来组成,在存储器中按顺序存放,默认顺序执行操作码:指明要进行什么操作,比如加减乘除。地址码:我们要操作的数据存放在哪一个位置。5.存储程序--提前把指令和数据存储到存储器中6.以运算器为中心--输入设备和输出设备与存储器之间的数据传送通过运算器来完成,会影响计算机的效率1.2现代计算机结构存储器分成了主存储器(内存)和辅助存储器(外存),系统以主存储器为中心合并运算器和控制器,构成中央处理器CPU——CPU和主存储器构成主机输入设备和输出设备变得多样化和复杂化,合称外部设备或I/O设备主存储器和CPU统称为主机主存就是主存储器,也是我们大家熟悉的内存辅存是辅助存储器,在电脑里面就是机械硬盘,固态硬盘主机包含主存,没有包含辅存,辅存应该看成IO设备手机里面安装的app放在辅存,app需要启动运行的时候,才会把app里面的相关数据读到主存,辅存应该看成IO设备运行内存指的是主存机身存储是辅存1.3哈佛结构将指令和数据分开存储的一种存储器并行的组成结构特点 1)使用两个独立的存储器,分别用于存储指令和数据 2)使用两套独立的总线,作为CPU与每个存储器间专用通信线路 应用——在嵌入式系统设计中广泛使用### 1.4各个组成部件的功能2.认识各个硬件部件1.主存储器的基本组成主存储器中用来存放数据的叫做存储体,存储体由一系列存储元件组成,存放二进制的0和1MAR:存储地址寄存器MDR:存储数据寄存器ps:寄存器也是用来存储数据的我们取菜鸟驿站那快递的时候,我那么告诉店员包裹所在位置,店员就可以根据包裹位置为我们找到包裹。同样的道理,我们想要取出数据,一应该要告诉主存储器,我们要取的信息存放在哪一个位置,就可以根据这个位置找到相应的信息。 类比Java中的数组,我们把数据存储到数组中,通过下标来获得数据,其实就是告诉程序,我们要从哪一个位置取数据2.运算器3.控制器3.计算机系统的工作过程1.基本的计算机系统模型地址总线:传输数据在主存里面的存放位置计算机工作过程2.工作过程在了解工作过程之前,要先知道指令是什么指令的执行过程控制器将某个存储地址送往存储器,从对应的存储单元取回指令,控制器根据指令内容的含义,发出相应的操作命令,各部件根据操作命令,进行相应的操作,完成指令对应的功能再来复习一下,控制器,存储器的相关内容控制器可以协调各个器件的运行存储器是存储数据和程序的以我自己的理解来说:一条指令想要运行,我们应该先把它存储起来, 所以第一步把存储地址送到存储器--相当于存储数据想要执行操作,肯定要发布命令,就比如说,在家里面,你妈妈说,大家干活了,要大扫除了。 所以要取回指令。有了指令以后,我们是不是还得告诉具体的部件要做什么操作,还是以家里大扫除为例。如果没有进行分工,只是说干活,就会导致分工不明确,我们人当然是可以做相应的事情。但是要注意的是,计算机需要的是明确的指令操作,也就是说每一步要做什么事情,必须是明确的,不能是含糊的,因为计算机没有我们人那样聪明,如果没有具体明确的要求,各个部件瞬间就蒙圈了,他们不知道自己要做什么事情后面学习相关的内容以后,理解得会更加的透彻,上面那个只是我个人的一个理解。指令执行过程的阶段划分下面的划分可以有不同的情况,不是固定的,就像语文划分自然段一样,每一个人都会有不一样的理解,划分出来的也就不一样了。两阶段——取指、执行三阶段——取指、分析、执行五阶段——取指、译码/读寄存器、执行/计算有效地址(数据存放的地址)、访存取数、结果写回3.指令执行过程示例ADD R1, (R2)实现10+18=28的加法计算。假定10存放在通用寄存器R1,18存放在存储器中,其存放地址2n-1存放在通用寄存器R2,计算结果存放到通用寄存器R118存储在存储器中,就有存放位置,也就是它的地址,我们又把存放的地址存储在通用寄存器R2,数本身存储在主存里面通用寄存器可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果三、计算机硬件的性能指标1.字长定义:能够一次性处理的二进制数的位数分类:根据对象的不同,分为机器字长、存储字长、指令字长、数据字长接下来,我以其中的存储字长来说一下我自己的理解,其他概念也是类似的。字长直接反映了计算机的计算精度,也影响计算速度和硬件成本为协调计算精度和硬件成本间的制约关系,很多计算机采用变字长设计各类字长可能各不相同,但必须是字节的整数倍2.存储容量定义:计算机存储器中所能存储信息的总量分为主存容量和辅存容量单位位(bit)——最小单位,1个0或1占1b字节(Byte)——基本单位,1B = 8b主存容量的表示主存容量 = 存储单元数×存储字长3.运算速度周期T和频率f计算机系统时钟就是一个典型的频率相当精确和稳定的脉冲信号发生器计算机中为什么要有系统时钟?计算机工作时,各个设备必须相互配合,但每个设备工作都有各自的工作速度(工作时钟),为使整个系统能协调运行,就需要有统一的系统时钟对各设备作同步,各设备的工作时钟都基于系统时钟产生。CPU主频和时钟周期时钟——按一定电压幅度和时间间隔连续发出的脉冲信号公式性能指标描述计算公式CPU执行时间表示CPU执行一般程序所占用的CPU时间CPU执行时间=CPU时钟周期数×CPU时钟周期CPI执行一条指令需要的平均时钟周期数CPI=执行某段程序需要的CPU时钟周期数➗程序包含的指令条数MIPS表示平均每秒执行多少百万条定点指令数MIPS=指令数➗(程序执行时间×10^6)FLOPS平均每秒执行的浮点操作次数,用来衡量机器浮点操作的性能FLOPS=程序中的浮点操作次数➗程序执行时间(s)4.计算机性能评测方法四、计算机的发展
一、数据的表示这一部分会讲解数值型数据和字符型数据1.数值型数据的表示(重点难点)计算机采用二进制来表示数据,但是用二进制来表示数据的话,它的位数就会很多,所以就需要八进制,十六进制等其他进制位来表示,可以简化表示。1.1数值型数据的表示--进位制数值型表示的三个方面进位制进位制也就是进位计数制 对于任何一种进制---X进制,就表示每一位上的数运算时都是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,X进制就是逢X进位。 我们可以用若干位的组合表示一个数,形成一串符号序列,如: 比如说十进制的65,我们其实是这样算出来的: 十进制代表它的基数是10 它表示的大小为6×10^1 +5×10^0 =65计算机中采用进位制进位制符号基数可以用来表示的数符二进制B20,1八进制O80到7十进制D100到9十六进制H160到9,A到F,A表示10,F表示15BCD码简单来说就是把各个位置的数字拆开,不看成一个整体,比如说10,用BCD码来表示的话,它就会分别表示1和0,就像我们在电脑上用键盘输入10的时候,是先敲出1,再敲出0,然后再把他们结合起来成为10各种进位制之间的转换转换的首要原则——整数部分与小数部分分开转换   例题解析其他也是类似的,这里就不再多说,大家可以自己试试。1.2数值型数据表示-码制符号的数字化:用0表示+,用1表示-,因为计算机是用二进制来存储数据的计算机里面是没有小数点的,我们看到的那些用小数点表示的数,只是我们为了方便看而自己写的小数点移码通常用来表示浮点数的阶码,阶码是k位整数,题目:假定编译器规定 int 和 short 类型长度占 32 位和 16 位,执行下列 C 语言语句后得到 y 的机器数为( B )unsigned short x = 65530; unsigned int y = x;A. 0000 7FFA B. 0000 FFFA C. FFFF 7FFA D. FFFF FFFA题目:由3个“1”和5个“0”组成的8位补码,能表示的最小整数是( B) A. -126 B. -125 C. -32 D. -3**在选择计算机的数的表示方式的时候,要考虑下面的几个因素①要表示的数的类型(小数,整数,实数和复数)②可能的数的范围③数值精确度④数据存储和处理所需要的硬件代价**1.3数值型数据的表示——定点数定点格式约束机器中所有数据的小数点位置固定不变,这个位置在计算机电路设计的时候定好了,因为已经约定好了在固定的位置,小数点就不再用记号.来表示小数点位置固定在哪里都可以,但是通常把数据表示成纯整数或纯小数1.4数值型数据的表示——浮点数浮点数是指小数点的位置可以根据需要移动的数表示格式一台机器的阶码要么是移码要么是补码,不可能两个都用。注意:浮点数的数值部分的绝对值<1注:说明一下上面那个表示的最小的数:不管多少位的补码,它能表示的数最小是-1,一位是符号位,补码是反码+1,反码绝对值最大是11111111...1,再加1就变成10000000000000...0,但是需要注意的是,浮点数的数值部分是小数,所以补码绝对值最大是1我们一般是用定点整数表示阶码,定点小数表示尾数1.5浮点数表示的IEEE 754标准早期,各个计算机系统的浮点数使用不同的机器码来表示阶和尾数,这样就导致数据的交换和比较很麻烦,因为它们的表示不一样,所以当前计算机都用统一的IEEE 754标准中的格式来表示浮点数。基数2是固定常数,所以不需要显示表示它ps:移码是无符号数,所以阶码阶符一起在IEEE 754标准中,阶码是用移码来表示的移码的定义:移码=真值+偏置值,偏置值是2的k次方。在IEEE 754标准中,偏置值为+127之前说过对于浮点数来说,它的尾数如果是用原码表示,我们希望它的最高有效位是1,我们就可以隐藏表示最高位1移码=真值+偏置值阶码真值=移码-偏置值2.字符型数据表示二、数据的运算方法1.基本概念2.定点四则运算计算机中定点加减运算都采用补码进行3.溢出判断数据的值超过了机器数表示的范围对于加法:只有两个符号相同的数相加,才会溢出对于减法:只有符号相反相减,才会溢出4. 舍入处理注意:原码是0舍1入,补码是1舍0入5.定点乘法运算在做乘法运算的时候,我们从小学开始,就是一位一位的相乘,然后把每一步的乘积相加就是最后的结果,但是用计算机来实现的时候,是会出现问题的1.原码一位乘法符号单独处理,两个操作数绝对值相乘,每次把一位乘数对应的部分积和原来部分积的累加和进行相加,并右移。2.补码一位乘法3.原码两位乘法4.定点除法运算6.浮点四则运算三、数据的校验1.奇偶校验码校验是为了检测我们读取的信息是否正确,当信息符号某种规律的时候,我们就可以验证信息是正确的。码距是用来判断一个码制的冗余度,评估其差错和纠错能力。冗余校验在信息写入时,增加部分代码(校验位/冗余位),将有效信息与校验位一起按约定的校验规律进行编码(校验码)存储;读出时,对读到的校验码进行校验(译码),看是否仍符合约定的校验规律校验规律选择得当,不仅能够查错,而且可以纠错比如说,根据上面的图片,第一种方案,码距为1,当信息某些位发生错误,由于码距为1,所以获得的信息也是正确的,我们就无法判断之前的信息是什么。方案2,码距为2,如果信息只有一位发生改变,那么得到的信息就不符号我们的校验规则,我们就能判断这个信息是错误的,所以码距可以用来评估纠错能力。 奇校验——使完整的校验码(有效位+校验位)中“1”的个数为奇数个 偶校验——使完整的校验码(有效位+校验位)中“1”的个数为偶数个奇偶校验的缺陷:如果数据中有偶数个代码位错误,那么我们是无法检测出错误的,奇偶校验只能检测出奇数位错误,没有纠错能力。校验位只能携带2种状态信息,只能反馈是对或错两种状态,而不能反馈是哪一位错误校验位的形成在数电中有学过,就是通过异或运算, 把所有有效位进行异或运算,得到的数据就是校验位。(不清楚的可以去看看数电)2.海明校验码海明校验方法采用多重奇偶校验的思想将有效位按照一定规律组织成若干组,分组进行奇偶校验,每一个分组对应一个校验位各组校验位组成一个指示错误的字,用于检测是否出错并能纠正一位错误。这种方法除了能够反馈对错这样的状态信息,甚至还能指明是哪一个位置发生错误。如果异或之后,得到的数据是010,说明出错的位置是2,也就是H2,如果得到的数据是110,说明出错的位置是6,也就是H6这个位置3.循环冗余校验码(CRC)思想:让校验码能被某一约定的代码除尽。若能除尽,则校验码正确,否则,余数指明出错位置注:模2减也就是异或运算最后就是检错了
今天在学习mybatis的时候,遇到了这样的错误:1.Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。解决方法:file-settings-file encodings把project encodings改成utf-8注意:我们修改完以后,要clean一下,再编译,否则还会报错然后在mybatis构建中,我还遇到下面的错误**2.java.lang.NullPointerExceptionat com.kuang.utils.MybatisUtils.getSqlSession** 报的是空指针异常,后面我查看的时候 下面这个是我出错的代码,我发现,本来已经定义好了一个静态变量sqlSessionFactory,结果我却又新建了一个,静态变量随着类加载而加载,所以报空指针异常 private static SqlSessionFactory sqlSessionFactory; static { try { //读取配置文件 //下面三句话是固定的 //使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } 解决方法,不要创建两个对象 private static SqlSessionFactory sqlSessionFactory; static { try { //读取配置文件 //下面三句话是固定的 //使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } 3.使用mybatis连接数据库时报错:Error querying database. Cause: java.sql.SQLException: The server time zone value**方案:使用mysql 8.0以上的驱动需要配置时区才能正常使用在url中添加:serverTimezone=Asia/Shanghai**jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-84.Error querying database. Cause: java.sql.SQLSyntaxErrorException: Table 'mybatis.user' doesn't exist这个错误是由于我的粗心导致的,我在学习mybatis的时候,创建一个用来测试的数据库mybatis,里面创建的表是t_user,但是我忘记了,以为表是user,所以写的sql语句肯定也是错误的
@TOC一、简介以Mybatis3.5.7为例环境:jdk8.0Mysql8.0maven3.8.4IDEA回顾:JDBCMysqlJava基础MavenJunit1.初识MybatisMyBatis 是一款优秀的持久层框架它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。所以要找Mybatis得去Github怎么获得Mybatis?maven仓库<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency>搜搜jar包的坐标网站https://mvnrepository.com/Github: https://github.com/mybatis/mybatis-3/releasesMybatis中文文档:https://mybatis.net.cn/index.html2.持久化数据持久化持久化就是把程序的数据在持久状态和瞬时状态转化的过程内存:断电后数据就消失了数据库(jdbc),IO文件持久化为什么需要持久化内存断电后数据就丢失了,有一些对象我们不能让他丢掉内存太贵了3.持久层Dao层,Service层,Controller层完成持久化工作的代码块层的界限十分明显每个层的任务很明确,各司其职关于三层架构,大家可以看这篇博客,讲解得很清楚SSM,三层结构,MVC三者的说明及关系(很全面)4.为什么需要Mybatis帮助程序员把数据存入到数据库中方便传统的JDBC代码太复杂了,框架可以简化操作,自动化。不使用Mybatis也可以,更容易上手。优点:sql和代码分类,提高可维护性提供映射标签,支持对象与数据库的orm字段关系映射ORM是对象关系映射的意思O ------ object就是 java对象R -------relational 关系型数据M -------mappering 映射提供哦那个对象关系映射标签,支持对象关系组建维护提供xml标签,支持编写动态sql二、第一个Mybatis程序思路:搭建环境-->导入Mybatis-->编写代码-->测试1.搭建环境先提供一个数据库和用来操作的表 新建项目1.File->new Project 新建maven项目下面这一堆代码是构建maven项目以后,自带的<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kuang</groupId> <artifactId>Mybatis-study</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>2.删除src,这样就可以当成父工程了3.导入maven依赖第一次加入依赖的时候,项目会爆红,记得重新加载项目,这样就不会爆红了<!--导入依赖--> <dependencies> <!-- mysql驱动--> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!-- mybatis驱动--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- junit驱动--> <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> </dependencies>2.创建一个新模块父项目的pox.xml就会多出来这样的代码 <modules> <module>mybatis-01</module> <module>mybatis-01</module> <module>mybatis-01</module> </modules>编写mybatis核心配置文件把下面的代码拷贝到刚刚新建的xml文件<?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代表核心配置文件--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>上面那一个从官网复制过来的,相当于是一个模板,但是里面具体的信息需要我们自己去设置,比如说我们要连接的数据库的用户名,密码等信息需要手动配置<?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代表核心配置文件--> <configuration> <!-- 环境可以有多个,我们用默认的环境--> <!-- 配置连接数据库的环境--> <environments default="development"> <environment id="development"> <!-- 事务管理器,类型是JDBC--> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&amp;useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="a87684009."/> <!-- 数据源就是连接是连接数据库的信息--> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <!-- type=POOLED表示使用数据库连接池--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!-- 引入映射文件--> <!-- 每一个Mapper.xml都需要在Mybatis的核心配置文件中注册--> <mappers> <mapper resource="com/kuang/dao/UserMapper.xml"/> </mappers> </configuration>3.编写代码编写mybatis工具类//工厂模式 //sqlSessionFactory---->sqlSession public class MybatisUtils { public static SqlSessionFactory sqlSessionFactory; static { try { //读取配置文件 //下面三句话是固定的 //使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * 既然有了SqlSessionFactory,顾名思义,我们就可以从中获取SqlSession的实例了 * SqlSession完全包含了面向数据库执行sql命令所需要的所有方法 */ public static SqlSession getSqlSession() { SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession; } } 实体类(domain,pojo,domain)每一个类都和数据库中的一张表相关联package com.kuang.pojo; /**实体类 * @author zengyihong * @create 2022--03--28 17:14 */ public class User { private int id; private String username; private String password; public User() { } public User(int id, String username, String password) { this.id = id; this.username = username; this.password = password; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } } DAO接口(对数据库表的CRUD操作,对user表的操作就对应一个userDao)package com.kuang.dao; import com.kuang.pojo.User; import java.util.List; /**这个类用来操作数据库中对象的实体 * @author zengyihong * @create 2022--03--28 17:16 */ //dao等价于mapper public interface UserDao { List<User> getUserList(); } 接口实现类由原来的UserDaoImpl转换成为一个Mapper配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--命名空间要绑定一个对应的DAO/Mapper接口--> <mapper namespace="com.kuang.dao.UserDao"> <!-- select查询语句 id对应原来的方法名字--> <select id="getUserList" resultType="com.kuang.pojo.User"> select * from mybatis.user </select> </mapper> 可能大家看到上面那些有点懵,那我们就来复习一下 ,具体的大家可以看我博客中JDBC的内容,里面有关于分层的讲解。4.Junit测试package com.kuang.dao; import com.kuang.pojo.User; import com.kuang.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.jupiter.api.Test; import java.util.List; /** * @author zengyihong * @create 2022--03--29 9:55 */ public class UserDaoTest { @Test public void test(){ // SqlSession // 第一步:获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方式一:执行SQL getMapper UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); for (User user:userList){ System.out.println(user); } //关闭SqlSession sqlSession.close(); } } 三、可能遇到的问题1.配置文件没有注册 2.绑定接口错误 3.方法名错误4.返回类型错误5.maven导出资源错误在pox.xml中,加入下面代码即可 <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
@TOC Mybatis获取参数值的两种方式:${},#{} ${}本质:字符串拼接,注意:单引号要加上 #{}:本质:占位符赋值# 一、 Mybatis获取参数值的各种情况1.mapper接口方法的参数为单个字面量的类型 /** * 根据用户名来查询信息 * @return */ User getUsername(String username);既然传入的参数有多个,那么肯定也是可以传入多个参数的,接下来我们来看一下获取参数的第二种情况2.mapper接口方法的参数有多个 /** * 通过用户名和密码进行验证登录 */ User checkLogin(String username,String password); @Test public void testCheckLogin() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.checkLogin("李四", "123859"); System.out.println(user); }下面这个是报错信息org.apache.ibatis.exceptions.PersistenceException: Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2] Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]Mybatis检测到我们的参数有多个的时候,它会自动把参数放到Map集合中如果想要获得数据,就应该通过下面的方法 这里有一个注意点:刚刚的报错信息Available parameters are [arg1, arg0, param1, param2]这就说明了,arg1,param1它们是存储在一起的,所以我们其实是可以混合使用者两个的使用${}来获取参数值的使用方式是一样的,这里就不演示了,大家可以自己试试,不过再次强调:\${}本质是字符串拼接,我们使用的时候记得加上单引号总结:第二中方式就是通过mybatis自定义的key来访问数据,下面的第三种方式我们可以自定义key来访问3.手动把参数放在map集合中如果mapper接口中的方法的参数有多个的时候,我们可以手动把参数放在map集合中存储mapper接口中定义的方法 User checkLoginByMap(Map<String,Object> map);测试程序InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); Map<String, Object> map = new HashMap<>(); map.put("username","张三"); map.put("password","123456"); User user=mapper.checkLoginByMap(map); System.out.println(user);总结:第三种方式:我们可以自定义key来访问,技巧:mapper接口的方法参数是map集合,我们在测试程序的时候,存储数据的时候,可以指定存储的key的值还有一种获取参数值的形式就是,前端通过表单把完整数据传给后端,我们可以获取数据,并且把数据存到对应的实体类中, 这个时候,当我们调用service方法,那么传输过来的数据应该就是实体类对象。实体类对象是属性名=值这样来存储数据,map集合是key=value的形式来存储数据,这两种方式存储数据其实是很像的4.mapper接口方法的参数是实体类型的参数 /** * 添加用户信息 */ int insertUser(User user); @Test public void testInsertUser() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user=new User(); user.setId(15); user.setUsername("钱七"); user.setPassword("5613"); int i = mapper.insertUser(user); System.out.println(i); }总结:通过#{}或&dollar;{}以属性的方式来访问属性值,再次强调&dollar;{}的单引号问题,需要注意。如果我们#{}括号里面的值不是属性名那么就会报错5.通过@Param注解命名参数(比较常用)使用@Param注解来命名参数,此时Mybatis会把这些参数放在一个map集合中,以两种方式来进行存储①@Param注解的值为键,参数为值②以param1,param2...作为键,以参数为值我们只需要通过#{}和&dollar;{} 以键的形式来访问值就可以了测试username,@Param("password") String password);--> <select id="checkLoginByParam" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where username=#{param1} and password= #{password}; </select> 注意点:这个时候,存储的key,value形式有两种,一种形式的key是我们职工时候通过注解来命名的参数的名字,还有一种就是以param1,param2……作为key,所以这两种形式都可以用来取数据,不可以用arg0,arg1的形式来获取数据,要和情况2区分开二、总结上面讲的五种情况,可以归结为两种情况,一种是参数类型为实体类型,第二种类型是通过@Param注解来命名参数三、Param源码分析由于我也是刚学习Mybatis的相关内容,所以理解可能不够到位,这里我推荐大家可以看这篇文章MyBatis源码解读 - @Param注解。以后,我还会回头来更新Mybatis源码相关的内容,初学者的话,大家先以熟练使用为主,然后再慢慢的理解其中底层的原理,不要一下子就钻进源码中,这样可能导致畏难心理,这是我的一点体会。
@TOC前言使用Mybatis进行查询的时候,查询出来的数据可能有一条数据,也可能是多条,我们就得根据返回数据的条数来指定返回类型,如果记录是多条,那么返回值类型就不能是实体类。查询的记录有多条如果查询出来有多条记录,但是我们方法的返回类型是实体类,就会报下面的错误org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne()因为,当我们的方法返回值类型是实体类,底层会调用selectOne方法,这个方法的返回数据只能有一个总结:查询出来的记录有一条,返回值类型可以是实体类,也可以是集合,如果记录有多条,返回值类型可以设置成List集合,也可以是Map集合,但是一定不能设置成实体类类型,不然报错TooManyResultsException查询二 SQL语句使用了聚合函数我们有的时候,希望能够查询出来有多少条记录,那么SQL语句就使用了分组函数,那么查询出来的结果应该是单行单列 /** * 查询用户信息的总记录数 * @param * @return */ Integer getCount();大家有没有感觉很神奇,为什么会是这个样子的呢?原因是因为mybatis设置了类型别名MyBatis 已经为Java类型取好的别名(自己起别名时小心重复) MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。查询三 返回值类型为map当返回数据是一条如果我们查询出来的结果没有一个实体类与之相对应,我们就可以把返回值类型设置成为Map集合看到上面查询记录为一条的时候,我们用map可以查询出记录,我们再来看看如果记录是多条,用刚刚的方法能不能查询出来 Map<String,Object> getUserMap();解决方法:把多个map放在list集合中List<Map<String,Object>> getUserMapToList();难道说map集合一定要放在list集合中,才可以把查询到的多条记录输出吗?答案是no,我们其实是有办法的,通过注解就可以了 @MapKey("id") Map<String,Object> getUserMap(); 这个是官方解释 在这里我们推荐一篇比较详细的介绍,大家可以看看:Mybatis源码分析:@Mapkey的使用 总结:如果查询出来的记录有多条,我们可以有下面几种解决方法①通过实体类类型的List集合接收②通过map类型的List集合接收③在mapper接口的方法上添加@MapKey注解,这个时候就可以把每条数据转换的map集合作为值,以某一个字段的值作为键,放在同一个map集合中
@TOC1.模糊查询大家应该还记得sql语句的模糊查询怎么写吧,那就是使用关键字like,并且有相应的通配符一起使用,%表示多个字符,_表示一个字符,比如说现在有一张表user,查询name中第一个字母是l的人select * from user where name like '_l';如果是查询name中有 l 的人select * from user where name like '%l%';模糊查询不可以使用#{},接下来通过案例来说明一下提供一个mapper接口用来测试,里面定义了操作数据库的各种方法 /** * 根据用户名模糊查询用户信息 */ List<User> getUserByLike(@Param("username") String username); 在映射文件中写上我们要进行执行的sql语句<select id="getUserByLike" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where username like '%#{username}%'; </select> <font size=4 color="blue"> 然后进行测试 解决方法1SQL语句中的#{}换成&dollar;{} <select id="getUserByLike" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where username like '%${username}%'; </select> 解决方法2使用字符串拼接 <select id="getUserByLike" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where username like concat('%',#{username},'%'); </select>解决方法3(建议使用这种方式) <select id="getUserByLike" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where username like "%"#{username}"%"; </select>2.批量删除delete from 表名 where 筛选条件 int delete(@Param("ids") String ids);在映射文件中写下面的代码 <delete id="delete" > delete from t_user where id in(#{ids}); </delete>那我们要怎么解决呢?可以使用&dollar;{} <delete id="delete" > delete from t_user where id in(#{ids}); </delete>总结: 在这里批量删除不能使用#{},因为它会自动加上' '进行字符串拼接 ,而我们的数据库中,字段id的属性是int类型的,由于#{}会自动加上单引号所以不可以,是不正确的,但是如果id字段的属性是varchar,我们就得用#{],而不是${}3.动态设置表名也就是说我们查询的时候,能不能不要把表名给写死,我们可以传入表的名字,然后根据表名来查询数据 /** * 查询表名来查询数据 */ List<User> getUserByTableName(@Param("tableName") String table); <select id="getUserByTableName" resultType="com.atguigu.mybatis.pojo.User"> select * from ${tableName} </select>注意点:我们以前在学习MySQL的时候,比如说写一个查询语句select * from user;我们这里的表名不能加引号对吧,同样的道理我们在映射文件写的sql语句,表名也不能加引号,那样就变成字符串了,所以动态设置表名应该使用${}4. 添加功能获取自增的主键t_clazz(clazz_id,clazz_name)t_student(student_id,student_name,clazz_id) 1.添加班级信息2.获取新添加的班级id3.为班级分配学生,就是说把某一个学生的班级id修改成新添加的班级id/** 添加用户信息 useGeneratedKeys:设置使用自增的主键 keyProperty:因为增删改有统一的返回值是受影响的行数, 因此只能将获取的自增的主键放在传输的参数user对象的某个属性中 */ int insertUser(Useruser);<! --int insertUser(Useruser); --> <insert id="insertUser"useGeneratedKeys="true"keyProperty="id"> insert into t_user values(null,#{username},#{password} ) </insert>
@TOC一.物理层基本概念先强调一下,物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体,传输媒体不属于计算机网络体系结构的任何一层。1.物理层的作用和意义①物理层是为了解决在各种传输媒体种的0和1问题②物理层的作用是尽可能的屏蔽掉不同传输媒体和通信手段的差异,让物理层上面的数据链路层感受不到差异,这样就可以使数据链路层只要考虑怎么完成本层的协议和服务,不需要考虑网络具体的传输媒体和通信手段是什么大家可以了解一下,这里的一个概念,物理层规程:就是用在物理层的协议2.物理层的主要任务物理层的主要任务是确认和传输媒体的接口有关的一些特性,接下来我们来看看都有哪一些特性。① 机械特性指明接口所用接线器的性质和尺寸、引脚数目和排列、固定和锁定装置等 ②电气特性指明在接口电缆的各条线上出现的电压范围③功能特性指明某条线上出现的某一电平的电压的意义④过程特性指明对于不同功能的各种可能事件的出现顺序二、数据通信的基础知识1.数据通信的模型数据通信系统可以分成三大部分:①源系统(或发送端,发送方) ②传输系统(或传输网络) ③目的系统(或接收端、接收方)大家先对上面图片的过程有个了解,接下来我们来看看里面的具体部分源系统包含两个部分:①源点(信源,源站):顾名思义就是信号的来源,数据的来源,源点设备产生要传输的数据,比如我们用键盘输入数字,计算机产生输出的数字比特流②发送器:调制器是典型的发送器,源点生成的数字比特流通常要通过发送器编码后才能传输目的系统一般来说也包含两个部分①接收器:接收信号,把它转换成可以被目的设备处理的信息,典型的接收器是解调器②终点(目的站,信宿):把从接收器获取的数字比特流输出,比如:把汉字在计算机屏幕显示接下来介绍一下常用术语消息: 语言,文字,视频,音频等都是数据:运送消息的实体,数据是使用特定方式表示的信息,通常是用有意义的符号序列信号:数据的电磁或电气表现根据信号代表消息的参数的取值方式不同,可以分成两类这里我们需要对码元有一定的了解,接下来我们来看看。在使用时间域的波形表示数字信号的时候,代表不同的离散数值的基本波形就是码元,简单来说,码元是构成信号的一般波形2.信道的几个基本概念信道通俗来说就是信号传输的媒介既然都已经说到了信号传输了,接下来我给大家来说一下几种传输方式我们先来看看串行传输和并行传输2.1串行传输和并行传输串行传输和并行传输2.2同步传输和异步传输同步传输和异步传输同步传输:数据块以稳定的比特流的形式传输,字节之间没有间隔接收端在每一个比特信号的中间时刻进行检测,用来判断接收的是比特0或比特1由于不同设备的时钟频率存在差异,不可能完全相同,传输大量数据中,所产生的判别时刻的累计误差,会导致接收端对比特信号的判别错位。不过这种情况也不是说无法解决,毕竟出现问题,我们肯定要想办法解决,我们可以让收发双方的时钟同步收发双方时钟同步的方法:外同步: 在收发双方之间添加一条单独的时钟信号线,发送端在发送数据信号的同时,另外发送一路时钟同步信号,接收端按照时钟同步信息的节奏接收数据。内同步: 发送端把时钟同步信号编码到发送数据中一起传输(比如:曼彻斯特编码)曼彻斯特玛在下文会具体介绍讲完同步传输,接下来,我们来说说异步传输异步传输:采用异步传输的方式时,以字节为独立的传输单位,字节之间的间隔不是固定的接收端仅仅在每一个字节的起始处对字节内的比特实现同步 因此,通常要在每一个字节的前和后分别加上起始位和结束位2.3通信双方的信息交互方式从通信双方的信息交互的方式来看,可以有三种基本方式:①单向通信:又称为单工通信,这是一种单方向的通信没有反方向的交互比如:电视广播,有线电广播②双向交替通信:又称为半双工通信,通信双方都可以发送信息,但是不能同时发送或同时接收,只能是一方发送,一方接收,比如:对讲机③双向同时通信:又称为全双工通信,通信双方可以同时发送和接收信息,比如:电话刚刚说了一下几种传输方式以后,我们再来看看其他的一些概念基带信号:信源发出的信号,计算机输出的各种文字,图像等都是基带信号调制:为了解决一些信道不能传输基带信号低频分量或直流分量3.常用编码方式(十分重要)接下来我就对各种编码方式进行详细的介绍3.1 不归零制(存在同步问题)正电平代表1,负电平代表0,整个码元时间内,电平不会出现零电平接收端如果想要判断是几个码元,就得需要额外的一根传输线来传输时钟信号,使得发送方和接收方同步,但是对于计算机网络,宁愿利用这根传输线来传输数据信号,而不是用来传输时钟信号。所以计算机网络不采用这种编码。3.2归零制(自同步,编码效率低)正脉冲代表1,负脉冲代表0归零制相当于把时钟信号用归零方式编码在数据之内,这称为自同步信号归零制:每一个码元传输结束以后都要归零,接收方只需要在信号归零以后进行采样,不需要单独的时钟信号,不过,归零制中大部分数据带宽都用来传输归零而浪费掉,所以效率低3.3曼彻斯特编码每一个码元的中间时刻,信号都会发生跳变,码元中间时刻的跳变即表示时钟又表示数据,传统的以太网就是采用曼彻斯特编码.我们可以规定正跳变表示1,也可以规定正跳变表示03.4差分曼彻斯特编码1.在每一个码元的中间时刻,信号都会发生跳变不过,差分曼彻斯特编码的跳变仅表示时钟,不表示数据2.码元开始处电平是否发生变化表示数据,开始边界有跳变表示0,没跳变表示13.5基本的带通调制方法使用基本的调制方法,一个码元只能包含一个比特信息,那怎样才能让一个码元包含多个比特信息呢?我们可以使用混合调制的方法相位和振幅通常可以结合起来一起调制使用QAM-16可以有12种相位,每种相位有或2种振幅可选4、信道的极限容量信号在信道上的传输不可避免都会出现失真码元的传输速率越高,信号传输的距离越远,噪声干扰越大或传输媒体质量越差,在接收端的波形的失真就越严重码间串扰:接收端收到的信号波形失去了码元之间的清晰界限1.奈氏准则在假定的理想条件下,为了避免码间串扰,码元传输速率是有上限的理想低通信道的最高码元传输速率=2W Baud=2W码元/秒W:信道带宽(单位HZ)Baud:波特,即码元/秒2.区分易混淆概念码元传输速率:单位时间内数字通信系统所传输的码元个数(也可称为脉冲个数或信号变化的次数)又称为波特率,调制速率,波形速率或符号速率,它和比特率有一定关系码元可以是多进制的,也可以是二进制,但是码元速率和进制数无关信息传输速率:别名信息速率,比特率,表示单位时间内数字通信系统传输的二进制码元个数(比特数)单位是比特/秒带宽:表示在单位时间内从网络的某一点到另外一点所能通过的最高数据率,经常用来表示网络的通信线路所能传输数据的能力3.香农公式三、物理层下面的传输媒体传输媒体也叫传输媒介,它分成两类,导引型传输媒体和非导引型传输媒体这里有太多的概念,书上介绍得很详细,我这里只是把一些比较重要的列举出来,具体的还是要看书1.导引型传输媒体在这里我来重点介绍一下光纤光纤是光纤通信的传输媒体,在发送端有光源,可以采用发光二极管或半导体激光器,它们能在电脉冲的作用下产生出光脉冲,在接收端利用光电二极管做成光检测器,在检测到光脉冲时可以还原出电脉冲多模光纤:存在多条不同角度入射的光线在一条光纤中传输,这种光纤就是多模光纤多模光纤对光源要求不高,可以用发光二极管来发送光源由于色散(模式、材料、波导色散),光在多模光纤传输一定距离以后,必然会产生信号失真(脉冲展宽)---多模光纤只适合近距离传输2.非导引型传输媒体四、信道复用技术(重要)这里既然说到了信道复用,那么我们肯定要先知道什么是复用复用:是通信技术上的一个重要概念,复用就是通过一条物理线路来同时传输多路用户的信号当网络中传输媒体的传输容量大于多条单一信道传输的总通信量的适合,就可以利用复用技术在一条物理线路上建立多条通信信道来充分利用传输媒体的带宽。常见的信道复用有:频分复用FDM,时分复用TDM,统计时分复用STDM,波分复用WDM,码分复用CDM,最基本的复用是频分复用和时分复用。1.频分复用FDM把传输线路的频带资源划分为多个子频道,形成多个子信道,各个子信道之间要留出隔离带,防止造成子信道之间的干扰。当多路信号输入一个多路复用器的时候,这一个复用器会把每一路信号调制到不同频率的载波上,接收端由响应的分用器通过滤波把各路信号分开,把合成的多路信号恢复成原始的多路信号频分复用的各路信号在同样的时间里面占用不同的带宽资源(这里的带宽是频率带宽而不是数据的发送速率)2.时分复用TDM把时间划分成为一个个的时隙,时分复用技术把传输线路的带宽资源按照时隙轮流分配给不同的用户,每对用户只在所分配的时隙里面使用线路传输数据下面的图中,我们可以看成每个时分复用帧有四个时隙 时分复用技术把时间划分成一段段等长的时分复用帧 每一个时分复用的用户在每一个时分用户帧中占用固定序号的时隙,每一个用户所占用的时隙是周期性出现的,它的周期就是时分复用帧的长度时分复用的所有用户在不同的时间占用同样的频带宽度我们这里来看一下,时分复用存在什么问题 我们知道时分复用的所有用户在不同的时间是占用相同的频带宽度的,但是如果用户在这段时间里面没有传输数据呢?那么这个时候这个已经分配到的信道就会处于空闲状态,其他想要发送信息的用户无法利用这个空闲的信道发送信息,这样就会导致信道的利用率不高。那么有什么办法可以解决这个问题吗?答案是肯定的,那就是接下来要讲的统计时分复用3.统计时分复用(STDM)统计时分复用不是固定分配资源,而是按需动态分配资源,科提高线路的利用率。统计时分复用使用STDM帧来传送复用的数据,每个STDM帧时隙数小于用户数,各用户有数据以后就随时发往集中器的输入缓存,集中器按照顺序依次扫描输入缓存,把存入的数据放到STDM帧。没有缓存的数据就直接跳过,一个帧的数据满了,就发送出去。注意:因为STDM帧中的数据不是固定分配给某一个用户的,所以在每一个时隙中,还必须要有用户的地址信息 每一个用户占用的时隙不是周期性出现,所以统计时分复用又叫异步时分复用,一般的时分复用叫同步时分复用4.波分复用WDM波分复用其实就是光的频分复用,使用一根光纤来同时传输多个频率很接近的光载波信号我们知道对于光来说,一旦波长确定,它的频率也就确定了我们可以把多数不同的光放在同一个线路上面,这种线路一般是多模线路,它可以发送多个线路进来码分复用CDM(重要)
2022年08月
2022年07月
2022年04月