微服务基础知识点学习笔记(持续更新)

微服务基础知识点学习笔记(持续更新)

Conrtoller层

整体包括:HTTP协议,JavaWeb三大组件(filter、servlet、listener)、SpringMVC(SpringMVC的执行流程、常用配置、SpringMVC注解的使用)、OpenFeign。

HTTP协议(Hyper Text Transfer Protocol:超文本传输协议)

在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP。而HTTP协议又是一个基于TCP/IP协议来传输数据。

常用的HTTP Header

HTTP头以Header: Value形式表示的,常用的有:

  • Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
  • User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似Mozilla/5.0 ... Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko
  • Accept:表示浏览器能接收的资源类型,如text/*image/*或者*/*表示所有;
  • Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
  • Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate, br
HTTP常见的响应码

其中2xx表示成功,3xx表示重定向,4xx表示客户端引发的错误,5xx表示服务器端引发的错误。数字是给程序识别,文本则是给开发者调试使用的。常见的响应代码有:

  • 200 OK:表示成功;
  • 301 Moved Permanently:表示该URL已经永久重定向;
  • 302 Found:表示该URL需要临时重定向;
  • 304 Not Modified:表示该资源没有修改,客户端可以使用本地缓存的版本;
  • 400 Bad Request:表示客户端发送了一个错误的请求,例如参数无效;
  • 401 Unauthorized:表示客户端因为身份未验证而不允许访问该URL;
  • 403 Forbidden:表示服务器因为权限问题拒绝了客户端的请求;
  • 404 Not Found:表示客户端请求了一个不存在的资源;
  • 500 Internal Server Error:表示服务器处理时内部出错,例如因为无法连接数据库;
  • 503 Service Unavailable:表示服务器此刻暂时无法处理请求。
服务器经常返回的HTTP Header
  • Content-Type:表示该响应内容的类型,例如text/htmlimage/jpeg
  • Content-Length:表示该响应内容的长度(字节数);
  • Content-Encoding:表示该响应压缩算法,例如gzip
  • Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒。
通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。HTTP是以客户端的身份去请求服务器资源,以服务器的身份响应客户端请求,同时编写服务器程序来处理客户端请求通常就称之为Web开发。

Servlet

简介

在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能:

                 ┌───────────┐
                 │My Servlet │
                 ├───────────┤
                 │Servlet API│
┌───────┐  HTTP  ├───────────┤
│Browser│<──────>│Web Server │
└───────┘        └───────────┘

一个Servlet总是继承自HttpServlet,然后覆写doGet()doPost()方法。注意到doGet()方法传入了HttpServletRequestHttpServletResponse两个对象,分别代表HTTP请求和响应。我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequestHttpServletResponse就已经封装好了请求和响应。

同时,我们编写的Servlet并不是直接运行,而是由Web服务器加载后创建实例运行,所以,类似Tomcat这样的Web服务器也称为Servlet容器

在Servlet容器中运行的Servlet具有如下特点:

  • 无法在代码中直接通过new创建Servlet实例,必须由Servlet容器自动创建Servlet实例;
  • Servlet容器只会给每个Servlet类创建唯一实例;
  • Servlet容器会使用多线程执行doGet()doPost()方法。

结合多线程可得出:

  • 在Servlet中定义的实例变量会被多个线程同时访问,要注意线程安全;
  • HttpServletRequestHttpServletResponse实例是由Servlet容器传入的局部变量,它们只能被当前线程访问,不存在多个线程访问的问题;
  • doGet()doPost()方法中,如果使用了ThreadLocal,但没有清理,那么它的状态很可能会影响到下次的某个请求,因为Servlet容器很可能用线程池实现线程复用。

因此编写Servlet必须考虑多线程同步,需要同步的必须要同步。

多servlet的WebApp
  • 一个WebApp是由多个Servlet组成,每个servlet类似于MVC中的控制器可以通过注解来表示自己可以处理的请求路径:

    @WebServlet(urlPatterns = "/hello")
    public class HelloServlet extends HttpServlet {
        ...
    }
    

    上述HelloServlet能处理/hello这个路径的请求。

  • 因为浏览器发送请求的时候,还会有请求方法(HTTP Method):即GET、POST、PUT等不同类型的请求。因此,要处理GET请求,我们要覆写doGet()方法:

    @WebServlet(urlPatterns = "/hello")
    public class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ...
        }
    }
    

    类似的,要处理POST请求,就需要覆写doPost()方法。否则就会直接返回报错。

HttpServletRequest

HttpServletRequest封装了一个HTTP请求,它实际上是从ServletRequest继承而来。最早设计Servlet时,设计者希望Servlet不仅能处理HTTP,也能处理类似SMTP等其他协议,因此,单独抽出了ServletRequest接口,但实际上除了HTTP外,并没有其他协议会用Servlet处理,所以这是一个过度设计。

我们通过HttpServletRequest提供的接口方法可以拿到HTTP请求的几乎全部信息,常用的方法有:

  • getMethod():返回请求方法,例如,"GET""POST"
  • getRequestURI():返回请求路径,但不包括请求参数,例如,"/hello"
  • getQueryString():返回请求参数,例如,"name=Bob&a=1&b=2"
  • getParameter(name):返回请求参数,GET请求从URL读取参数,POST请求从Body中读取参数;
  • getContentType():获取请求Body的类型,例如,"application/x-www-form-urlencoded"
  • getContextPath():获取当前Webapp挂载的路径,对于ROOT来说,总是返回空字符串""
  • getCookies():返回请求携带的所有Cookie;
  • getHeader(name):获取指定的Header,对Header名称不区分大小写;
  • getHeaderNames():返回所有Header名称;
  • getInputStream():如果该请求带有HTTP Body,该方法将打开一个输入流用于读取Body;
  • getReader():和getInputStream()类似,但打开的是Reader;
  • getRemoteAddr():返回客户端的IP地址;
  • getScheme():返回协议类型,例如,"http""https"

此外,HttpServletRequest还有两个方法:setAttribute()getAttribute(),可以给当前HttpServletRequest对象附加多个Key-Value,相当于把HttpServletRequest当作一个Map<String, Object>使用。

调用HttpServletRequest的方法时,注意务必阅读接口方法的文档说明,因为有的方法会返回null,例如getQueryString()的文档就写了:

... This method returns null if the URL does not have a query string...
HttpServletResponse

HttpServletResponse封装了一个HTTP响应。由于HTTP响应必须先发送Header,再发送Body,所以,操作HttpServletResponse对象时,必须先调用设置Header的方法,最后调用发送Body的方法。

常用的设置Header的方法有:

  • setStatus(sc):设置响应代码,默认是200
  • setContentType(type):设置Body的类型,例如,"text/html"
  • setCharacterEncoding(charset):设置字符编码,例如,"UTF-8"
  • setHeader(name, value):设置一个Header的值;
  • addCookie(cookie):给响应添加一个Cookie;
  • addHeader(name, value):给响应添加一个Header,因为HTTP协议允许有多个相同的Header;

写入响应时,需要通过getOutputStream()获取写入流,或者通过getWriter()获取字符流,二者只能获取其中一个。

写入响应前,无需设置setContentLength(),因为底层服务器会根据写入的字节数自动设置,如果写入的数据量很小,实际上会先写入缓冲区,如果写入的数据量很大,服务器会自动采用Chunked编码让浏览器能识别数据结束符而不需要设置Content-Length头。

但是,写入完毕后调用flush()却是必须的,因为大部分Web服务器都基于HTTP/1.1协议,会复用TCP连接。如果没有调用flush(),将导致缓冲区的内容无法及时发送到客户端。此外,写入完毕后千万不要调用close(),原因同样是因为会复用TCP连接,如果关闭写入流,将关闭TCP连接,使得Web服务器无法复用此TCP连接。

写入完毕后对输出流调用flush()而不是close()方法!

有了HttpServletRequestHttpServletResponse这两个高级接口,我们就不需要直接处理HTTP协议。注意到具体的实现类是由各服务器提供的,而我们编写的Web应用程序只关心接口方法,并不需要关心具体实现的子类。

Servlet多线程模型

一个Servlet类在服务器中只有一个实例,但对于每个HTTP请求,Web服务器会使用多线程执行请求。因此,一个Servlet的doGet()doPost()等处理请求的方法是多线程并发执行的。如果Servlet中定义了字段,要注意多线程并发访问的问题:

public class HelloServlet extends HttpServlet {
    private Map<String, String> map = new ConcurrentHashMap<>();

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 注意读写map字段是多线程并发的:
        this.map.put(key, value);
    }
}

对于每个请求,Web服务器会创建唯一的HttpServletRequestHttpServletResponse实例,因此,HttpServletRequestHttpServletResponse实例只有在当前处理线程中有效,它们总是局部变量,不存在多线程共享的问题。

重定向与转发

重定向Redirect:

重定向是指浏览器请求一个URL时,服务器返回一个重定向指令,需要浏览器用新的URL重新发出请求。这个过程就是302响应。如图所示:

┌───────┐   GET /hi     ┌───────────────┐
│Browser│ ────────────> │RedirectServlet│          第一次发出GET /hi请求,服务器返回重定向302相应并给予新的URL。
│       │ <──────────── │               │
└───────┘   302         └───────────────┘


┌───────┐  GET /hello   ┌───────────────┐
│Browser│ ────────────> │ HelloServlet  │          第二次请求使用新的GET /hello请求,成功获取<html>资源。
│       │ <──────────── │               │
└───────┘   200 <html>  └───────────────┘

转发Forward:

转发是指在服务器内部进行重新调度。如图所示:

                          ┌────────────────────────┐
                          │      ┌───────────────┐ │
                          │ ────>│ForwardServlet │ │
┌───────┐  GET /morning   │      └───────────────┘ │
│Browser│ ──────────────> │              │         │
│       │ <────────────── │              ▼         │
└───────┘    200 <html>   │      ┌───────────────┐ │
                          │ <────│ HelloServlet  │ │
                          │      └───────────────┘ │
                          │       Web Server       │
                          └────────────────────────┘

ServletContext

什么是ServletContext

要理解ServletContext就必须和Cookie、Session做一个对比,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfdFZfOb-1599554344579)(C:\Users\guan.zhi\Desktop\笔记文档\20160513225142387.png)]

你可以把它想象成一个公用的空间,可以被所有的客户访问,也就是说A客户端可以访问D,B客户端可以访问D,C客户端也可以访问D。

总结可知:

  • WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用。并且它被所有客户端共享。
  • ServletContext对象可以通过ServletConfig.getServletContext()方法获得对ServletContext对象的引用,也可以通过this.getServletContext()方法获得其对象的引用。
  • 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
  • 当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁
使用ServletContext

(1) 如何得到ServletContext对象

this.getServletContext();
this.getServletConfig().getServletContext();

(2) 你可以把它想象成一张表,这个和Session非常相似:每一行就是一个属性,如下:

名字(String)值(Object)
  • 添加属性:setAttribute(String name, Object obj);
  • 得到值: getAttribute(String name),这个方法返回Object
  • 删除属性:removeAttribute(String name)

(3) 生命周期

ServletContext中的属性的生命周期从创建开始,到服务器关闭结束。

ServletContext是存在于服务器内存中的一个公共空间,除非关闭或重启服务器才会失去其中的数据。

使用Session和Cookie

因为HTTP是无状态协议,Web应用程序无法识别两次HTTP请求是否为同一个浏览器发出。所以需要使用Session和Cookie实现这一机制。

Session

浏览器第一次访问服务器的时候会被分配一个唯一的Session ID,如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。

我们可以在session里面保存用户登录的信息,再次访问servlet时可以尝试在session中找到已经登录过的用户,可以实现识别不同的HTTP请求是否为用一个浏览器发出。所有的servlet都在维护同一个session(相同的内存空间)如图:

           ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
           │      ┌───────────────┐                │
           | ┌───>│ IndexServlet  │<──────────┐    |
           │ │    └───────────────┘           ▼    │
┌───────┐  | │    ┌───────────────┐      ┌────────┐|
│Browser│──┼─┼───>│ SignInServlet │<────>│Sessions││
└───────┘  | │    └───────────────┘      └────────┘|
           │ │    ┌───────────────┐           ▲    │
           | └───>│SignOutServlet │<──────────┘    |
           │      └───────────────┘                │
           └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
Cookie

在当前浏览器请求的servlet第一次调用getSession()时,就会创建一个SessionID,然后通过JSESSIONID的Cookie发送给浏览器。

Cookie 是存储在客户端计算机上的文本文件,并保留了各种跟踪信息。Java Servlet 显然支持 HTTP Cookie。

识别返回用户包括三个步骤:

  • 服务器脚本向浏览器发送一组 Cookie。例如:姓名、年龄或识别号码等。
  • 浏览器将这些信息存储在本地计算机上,以备将来使用。
  • 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookie 信息发送到服务器,服务器将使用这些信息来识别用户。
Filter
在一个比较复杂的Web应用程序中,通常都有很多URL映射,对应的,也会有多个Servlet来处理URL。

为了把一些公用逻辑从各个Servlet中抽离出来,JavaEE的Servlet规范还提供了一种Filter组件,即过滤器,它的作用是,在HTTP请求到达Servlet之前,可以被一个或多个Filter预处理,类似打印日志、登录检查等逻辑,完全可以放到Filter中。
  • 编写方法:

编写Filter时,必须实现Filter接口,在doFilter()方法内部,要继续处理请求,必须调用chain.doFilter()。最后,用@WebFilter注解标注该Filter需要过滤的URL。这里的/*表示所有路径。如果一定要给每个Filter指定顺序,就必须在web.xml文件中对这些Filter再配置一遍。

例如,我们编写一个最简单的EncodingFilter,它强制把输入和输出的编码设置为UTF-8:

@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("EncodingFilter:doFilter");
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        chain.doFilter(request, response);
    }
}

再写一个针对特殊路径的过滤器:

@WebFilter("/user/*")
public class AuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("AuthFilter: check authentication");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (req.getSession().getAttribute("user") == null) {
            // 未登录,自动跳转到登录页:
            System.out.println("AuthFilter: not signin!");
            resp.sendRedirect("/signin");
        } else {
            // 已登录,继续处理:
            chain.doFilter(request, response);
        }
    }
}

​ 注意到AuthFilter只过滤以/user/开头的路径,因此:

  • 如果一个请求路径类似/user/profile,那么它会被上述3个Filter依次处理;
  • 如果一个请求路径类似/test,那么它会被上述2个Filter依次处理(不会被AuthFilter处理)。

再注意观察AuthFilter,当用户没有登录时,在AuthFilter内部,直接调用resp.sendRedirect()发送重定向,且没有调用chain.doFilter(),因此,当用户没有登录时,请求到达AuthFilter后,不再继续处理,即后续的Filter和任何Servlet都没有机会处理该请求了。

可见,Filter可以有针对性地拦截或者放行HTTP请求。

再次提醒! 如果Filter要使请求继续被处理,就一定要调用chain.doFilter()!


SpringMVC

具体内容转到我的Spring知识点总结博客 😃

OpenFegin

Ribbon、Feign和OpenFeign的区别
Ribbon

Ribbon 是Netflix开源的基于HTTP和TCP等协议负载均衡组件

Ribbon 可以用来做客户端负载均衡,调用注册中心的服务

Ribbon的使用需要代码里手动调用目标服务,请参考官方示例:https://github.com/Netflix/ribbon

Feign

Feign是Spring Cloud组件中的一个轻量级RESTfulHTTP服务客户端

Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。

Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务

Feign支持的注解和用法请参考官方文档:https://github.com/OpenFeign/feign

Feign本身不支持Spring MVC的注解,它有一套自己的注解

OpenFeign

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

// user-api 子项目
public interface SysUserResource {
	@GetMapping("/test")
	Object getUser();
}

// user-client 子项目 , 依赖了user-api 子项目
// 其他业务模块可以直接依赖此模块,通过调用接口即可完成服务的远程调用,open-feign会对此类做动态代理
// name = "user-center" 是被调用的服务实例名称
@FeignClient(name = "user-center")
public interface SysUserResourceClient extends SysUserResource {
}

// user-impl 子项目
@RestController
public interface SysUserResourceImpl implements SysUserResource  {
	@Override
	Object getUser(){
		// do something
	}
}

// role-impl 子项目 , 依赖了 user-client 子项目
@RestController
public interface SysRoleResourceImpl implements SysRoleResource  {
	@Resource
	private SysUserResource  sysUserResource;
	
	@Override
	Object test(){
		sysUserResource.getUser();
	}
}

在这里插入图片描述

需要注意,@RequesMapping不能在类名上与@FeignClient同时使用


Service层

service的主要内容都在Spring的基础知识点的文章中,请移步我的Spring基础知识点blog哦 😃 以下只是补充注解的内容。

Spring的主要注解

注解解释
@Controller组合注解(组合了@Component注解),应用在MVC层(控制层),DispatcherServlet会自动扫描注解了此注解的类,然后将web请求映射到注解了@RequestMapping的方法上。
@Service组合注解(组合了@Component注解),应用在service层(业务逻辑层)
@Reponsitory组合注解(组合了@Component注解),应用在dao层(数据访问层)
@Component表示当前被注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@AutowiredSpring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入。)
@ResourceJSR-250提供的注解
@InjectJSR-330提供的注解
@Configuration声明当前类是一个配置类(相当于一个Spring配置的xml文件)
@ComponentScan自动扫描指定包下所有使用@Service,@Component,@Controller,@Repository的类并注册
@Bean注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=“init”,destroyMethod=“destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Aspect声明一个切面(就是说这是一个额外功能)
@After后置建言(advice),在原方法前执行。
@Before前置建言(advice),在原方法后执行。
@Around环绕建言(advice),在原方法执行前执行,在原方法执行后再执行(@Around可以实现其他两种advice)
@PointCut声明切点,即定义拦截规则,确定有哪些方法会被切入
@Transactional声明事务(一般默认配置即可满足要求,当然也可以自定义)
@Cacheable声明数据缓存
@EnableAspectJAutoProxy开启Spring对AspectJ的支持
@Value值得注入。经常与Sping EL表达式语言一起使用,注入普通字符,系统属性,表达式运算结果,其他Bean的属性,文件内容,网址请求内容,配置文件属性值等等
@PropertySource指定文件地址。提供了一种方便的、声明性的机制,用于向Spring的环境添加PropertySource。与@configuration类一起使用。
@PostConstruct标注在方法上,该方法在构造函数执行完成之后执行。
@PreDestroy标注在方法上,该方法在对象销毁之前执行。
@Profile表示当一个或多个指定的文件是活动的时,一个组件是有资格注册的。使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。@Profile(“dev”)表示为dev时实例化。
@EnableAsync开启异步任务支持。注解在配置类上。
@Async注解在方法上标示这是一个异步方法,在类上标示这个类所有的方法都是异步方法。
@EnableScheduling注解在配置类上,开启对计划任务的支持。
@Scheduled注解在方法上,声明该方法是计划任务。支持多种类型的计划任务:cron,fixDelay,fixRate
@Conditional根据满足某一特定条件创建特定的Bean
@Enable*通过简单的@Enable来开启一项功能的支持。所有@Enable注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean(1.直接导入配置类2.依据条件选择配置类3.动态注册配置类)
@RunWith这个是Junit的注解,springboot集成了junit。一般在测试类里使用:@RunWith(SpringJUnit4ClassRunner.class) — SpringJUnit4ClassRunner在JUnit环境下提供Sprng TestContext Framework的功能
@ContextConfiguration用来加载配置ApplicationContext,其中classes属性用来加载配置类:@ContextConfiguration(classes = {TestConfig.class(自定义的一个配置类)})
@ActiveProfiles用来声明活动的profile–@ActiveProfiles(“prod”(这个prod定义在配置类中))
@EnableWebMvc用在配置类上,开启SpringMvc的Mvc的一些默认配置:如ViewResolver,MessageConverter等。同时在自己定制SpringMvc的相关配置时需要做到两点:1.配置类继承WebMvcConfigurerAdapter类2.就是必须使用这个@EnableWebMvc注解。
@RequestMapping用来映射web请求(访问路径和参数),处理类和方法的。可以注解在类和方法上,注解在方法上的@RequestMapping路径会继承注解在类上的路径。同时支持Serlvet的request和response作为参数,也支持对request和response的媒体类型进行配置。其中有value(路径),produces(定义返回的媒体类型和字符集),method(指定请求方式)等属性。
@GetMappingGET方式的@RequestMapping
@PostMappingPOST方式的@RequestMapping
@ResponseBody将返回值放在response体内。返回的是数据而不是页面
@RequestBody允许request的参数在request体中,而不是在直接链接在地址的后面。此注解放置在参数前。
@PathVariable放置在参数前,用来接受路径参数。
@RestController组合注解,组合了@Controller和@ResponseBody,当我们只开发一个和页面交互数据的控制层的时候可以使用此注解。
@ControllerAdvice用在类上,声明一个控制器建言,它也组合了@Component注解,会自动注册为Spring的Bean。
@ExceptionHandler用在方法上定义全局处理,通过他的value属性可以过滤拦截的条件:@ExceptionHandler(value=Exception.class)–表示拦截所有的Exception。
@ModelAttribute将键值对添加到全局,所有注解了@RequestMapping的方法可获得次键值对(就是在请求到达之前,往model里addAttribute一对name-value而已)。
@InitBinder通过@InitBinder注解定制WebDataBinder(用在方法上,方法有一个WebDataBinder作为参数,用WebDataBinder在方法内定制数据绑定,例如可以忽略request传过来的参数Id等)。
@WebAppConfiguration一般用在测试上,注解在类上,用来声明加载的ApplicationContext是一个WebApplicationContext。他的属性指定的是Web资源的位置,默认为src/main/webapp,我们可以修改为:@WebAppConfiguration(“src/main/resources”)。
@EnableAutoConfiguration此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。该注解组合了@Import注解,@Import注解导入了EnableAutoCofigurationImportSelector类,它使用SpringFactoriesLoader.loaderFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring.factories里声明了有哪些自动配置。
@SpingBootApplicationSpringBoot的核心注解,主要目的是开启自动配置。它也是一个组合注解,主要组合了@Configurer,@EnableAutoConfiguration(核心)和@ComponentScan。可以通过@SpringBootApplication(exclude={想要关闭的自动配置的类名.class})来关闭特定的自动配置。
@ImportResource虽然Spring提倡零配置,但是还是提供了对xml文件的支持,这个注解就是用来加载xml配置的。例:@ImportResource({"classpath
@ConfigurationProperties将properties属性与一个Bean及其属性相关联,从而实现类型安全的配置。例:@ConfigurationProperties(prefix=“authot”,locations={"classpath
@ConditionalOnBean条件注解。当容器里有指定Bean的条件下。
@ConditionalOnClass条件注解。当类路径下有指定的类的条件下。
@ConditionalOnExpression条件注解。基于SpEL表达式作为判断条件。
@ConditionalOnJava条件注解。基于JVM版本作为判断条件。
@ConditionalOnJndi条件注解。在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean条件注解。当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass条件注解。当类路径下没有指定的类的情况下。
@ConditionalOnNotWebApplication条件注解。当前项目不是web项目的条件下。
@ConditionalOnResource条件注解。类路径是否有指定的值。
@ConditionalOnSingleCandidate条件注解。当指定Bean在容器中只有一个,后者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplication条件注解。当前项目是web项目的情况下。
@EnableConfigurationProperties注解在类上,声明开启属性注入,使用@Autowired注入。例:@EnableConfigurationProperties(HttpEncodingProperties.class)。
@AutoConfigureAfter在指定的自动配置类之后再配置。例:@AutoConfigureAfter(WebMvcAutoConfiguration.class)

参考:

https://blog.csdn.net/weixin_37490221/article/details/78406810

https://juejin.im/post/6844903907173335047

https://juejin.im/post/6844903668400160776

https://www.cnblogs.com/aspirant/p/10287956.html

SpringBoot的注解

@SpringBootApplication

这个注解是Spring Boot最核心的注解,用在 Spring Boot的主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。实际上这个注解是**@Configuration,@EnableAutoConfiguration,@ComponentScan**三个注解的组合。由于这些注解一般都是一起使用,所以Spring Boot提供了一个统一的注解@SpringBootApplication。

@SpringBootApplication(exclude = {
        MongoAutoConfiguration.class,
        MongoDataAutoConfiguration.class,
        DataSourceAutoConfiguration.class,
        ValidationAutoConfiguration.class,
        MybatisAutoConfiguration.class,
        MailSenderAutoConfiguration.class,
})
public class API {
    public static void main(String[] args) {
        SpringApplication.run(API.class, args);
    }
}
@EnableAutoConfiguration

允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。

如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。

@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:

  • 从配置文件META-INF/spring.factories加载所有可能用到的自动配置类;
  • 去重,并将exclude和excludeName属性携带的类排除;
  • 过滤,将满足条件(@Conditional)的自动配置类返回;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage//导入AutoConfigurationImportSelector的子类@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
@SpringBootConfiguration

这个注解就是 @Configuration 注解的变体,只是用来修饰是 Spring Boot 配置而已,或者可利于 Spring Boot 后续的扩展。

@ConditionalOnBean

@ConditionalOnBean(A.class)仅仅在当前上下文中存在A对象时,才会实例化一个Bean,也就是说只有当A.class 在spring的applicationContext中存在时,这个当前的bean才能够创建。

@Bean
//当前环境上下文存在DefaultMQProducer实例时,才能创建RocketMQProducerLifecycle这个Bean
@ConditionalOnBean(DefaultMQProducer.class)
public RocketMQProducerLifecycle rocketMQLifecycle() {
     return new RocketMQProducerLifecycle();
}
@ConditionalOnMissingBean

组合@Conditional注解,和@ConditionalOnBean注解相反,仅仅在当前上下文中不存在A对象时,才会实例化一个Bean。

  @Bean
  //仅当当前环境上下文缺失RocketMQProducer对象时,才允许创建RocketMQProducer Bean对象
  @ConditionalOnMissingBean(RocketMQProducer.class)
  public RocketMQProducer mqProducer() {
      return new RocketMQProducer();
  }
@ConditionalOnClass

组合 @Conditional 注解,可以仅当某些类存在于classpath上时候才创建某个Bean。

  @Bean
  //当classpath中存在类HealthIndicator时,才创建HealthIndicator Bean对象
  @ConditionalOnClass(HealthIndicator.class)
  public HealthIndicator rocketMQProducerHealthIndicator(Map<String, DefaultMQProducer> producers) {
      if (producers.size() == 1) {
          return new RocketMQProducerHealthIndicator(producers.values().iterator().next());
      }
  }
@ConditionalOnMissingClass

组合@Conditional注解,和@ConditionalOnMissingClass注解相反,当classpath中没有指定的 Class才开启配置。

@ConditionalOnWebApplication

组合@Conditional 注解,当前项目类型是 WEB 项目才开启配置。当前项目有以下 3 种类型:ANY(任何Web项目都匹配)、SERVLET(仅但基础的Servelet项目才会匹配)、REACTIVE(只有基于响应的web应用程序才匹配)。

@ConditionalOnNotWebApplication

组合@Conditional注解,和@ConditionalOnWebApplication 注解相反,当前项目类型不是 WEB 项目才开启配置。

@ConditionalOnProperty

组合 @Conditional 注解,当指定的属性有指定的值时才开启配置。具体操作是通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。

 @Bean
 //匹配属性rocketmq.producer.enabled值是否为true
 @ConditionalOnProperty(value = "rocketmq.producer.enabled", havingValue = "true", matchIfMissing = true)
 public RocketMQProducer mqProducer() {
     return new RocketMQProducer();
 }
@ConditionalOnExpression

组合 @Conditional 注解,当 SpEL 表达式为 true 时才开启配置。

@Configuration
@ConditionalOnExpression("${enabled:false}")
public class BigpipeConfiguration {
    @Bean
    public OrderMessageMonitor orderMessageMonitor(ConfigContext configContext) {
        return new OrderMessageMonitor(configContext);
    }
}
@ConditionalOnJava

组合@Conditional 注解,当运行的 Java JVM 在指定的版本范围时才开启配置。

@ConditionalOnResource

组合 @Conditional 注解,当类路径下有指定的资源才开启配置。

@Bean
@ConditionalOnResource(resources="classpath:shiro.ini")
protected Realm iniClasspathRealm(){
  return new Realm();
}
@ConditionalOnJndi

组合 @Conditional 注解,当指定的 JNDI 存在时才开启配置。

@ConditionalOnCloudPlatform

组合 @Conditional 注解,当指定的云平台激活时才开启配置。

@ConditionalOnSingleCandidate

组合 @Conditional 注解,当指定的 class 在容器中只有一个 Bean,或者同时有多个但为首选时才开启配置。

@ConfigurationProperties

Spring Boot可使用注解的方式将自定义的properties文件映射到实体bean中,比如config.properties文件。

@Data
@ConfigurationProperties("rocketmq.consumer")
public class RocketMQConsumerProperties extends RocketMQProperties {
    private boolean enabled = true;

    private String consumerGroup;

    private MessageModel messageModel = MessageModel.CLUSTERING;

    private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;

    private int consumeThreadMin = 20;

    private int consumeThreadMax = 64;

    private int consumeConcurrentlyMaxSpan = 2000;

    private int pullThresholdForQueue = 1000;

    private int pullInterval = 0;

    private int consumeMessageBatchMaxSize = 1;

    private int pullBatchSize = 32;
}
@EnableConfigurationProperties

当@EnableConfigurationProperties注解应用到你的@Configuration时,任何被@ConfigurationProperties注解的beans将自动被Environment属性配置。 这种风格的配置特别适合与SpringApplication的外部YAML配置进行配合使用。

@Configuration
@EnableConfigurationProperties({
    RocketMQProducerProperties.class,
    RocketMQConsumerProperties.class,
})
@AutoConfigureOrder
public class RocketMQAutoConfiguration {
    @Value("${spring.application.name}")
    private String applicationName;
}
@AutoConfigureAfter

用在自动配置类上面,表示该自动配置类需要在另外指定的自动配置类配置完之后。

如 Mybatis 的自动配置类,需要在数据源自动配置类之后。

@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
}
@AutoConfigureBefore

这个和@AutoConfigureAfter注解使用相反,表示该自动配置类需要在另外指定的自动配置类配置之前。

@AutoConfigureOrder

Spring Boot 1.3.0中有一个新的注解@AutoConfigureOrder,用于确定配置加载的优先级顺序。

  @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 自动配置里面的最高优先级
  @Configuration
  @ConditionalOnWebApplication // 仅限于web应用
  @Import(BeanPostProcessorsRegistrar.class) // 导入内置容器的设置
  public class EmbeddedServletContainerAutoConfiguration {
      @Configuration
      @ConditionalOnClass({ Servlet.class, Tomcat.class })
      @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
      public static class EmbeddedTomcat {
         // ...
      }

      @Configuration
      @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
      @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
      public static class EmbeddedJetty {
         // ...
      }
}

DAO层

Spring事务

事务的基本概念
ACID
  • 事务是逻辑上的一组操作,要么都执行,要么都不执行。

  • 事务的ACID特性:

事务的特性

  • Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
隔离级别

根据SQL92标准,MySQL的InnoDB引擎提供四种隔离级别(即ACID中的I):读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE),InnoDB默认的隔离级别是REPEATABLE READ,其可避免脏读不可重复读,但不能避免幻读,需要指出的是,InnoDB引擎的多版本并发控制机制(MVCC)并没有完全避免幻读,关于该问题以及隔离级别说明,可参考MySQL的InnoDB的幻读问题

传播机制

Spring针对方法嵌套调用时事务的创建行为定义了七种事务传播机制,分别是PROPAGATION_REQUIRED、PROPAGATION_SUPPORT、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER以及PROPAGATION_NESTED,基本上从字面意思就能知道每种传播机制各自的行为表现,Spring默认的事务传播机制是PROPAGATION_REQUIRED,即如果当前存在事务,则使用当前事务,否则创建新的事务。详情可参考Spring事务传播行为

事务行为

事务的行为包括事务开启、事务提交和事务回滚。InnoDB所有的用户SQL执行都在事务控制之内,在默认情况下,autocommit设置为true,单条SQL执行成功后,MySQL会自动提交事务,或者如果SQL执行出错,则根据异常类型执行事务提交或者回滚。可以使用START TRANSACTION(SQL标准)或者BEGIN开启事务,使用COMMITROLLBACK提交和回滚事务;也可以通过设置autocommit属性来控制事务行为,当设置autocommit为false时,其后执行的多条SQL语句将在一个事务内,直到执行COMMIT或者ROLLBACK事务才会提交或者回滚。

AOP增强

Spring使用AOP(面向切面编程)来实现声明式事务,后续在讲Spring事务具体实现的时候会详细说明,关于AOP的概念可参考Spring AOP概念理解(通俗易懂),这里不再细说。说下动态代理AOP增强

动态代理是Spring实现AOP的默认方式,分为两种:JDK动态代理CGLIB动态代理。JDK动态代理面向接口,通过反射生成目标代理接口的匿名实现类;CGLIB动态代理则通过继承,使用字节码增强技术(或者objenesis类库)为目标代理类生成代理子类。Spring默认对接口实现使用JDK动态代理,对具体类使用CGLIB,同时也支持配置全局使用CGLIB来生成代理对象。

我们在切面配置中会使用到@Aspect注解,这里用到了Aspectj的切面表达式。Aspectj是java语言实现的一个AOP框架,使用静态代理模式,拥有完善的AOP功能,与Spring AOP互为补充。Spring采用了Aspectj强大的切面表达式定义方式,但是默认情况下仍然使用动态代理方式,并未使用Aspectj的编译器和织入器,当然也支持配置使用Aspectj静态代理替代动态代理方式。Aspectj功能更强大,比方说它支持对字段、POJO类进行增强,与之相对,Spring只支持对Bean方法级别进行增强。

Spring对方法的增强有五种方式:

  • 前置增强(org.springframework.aop.BeforeAdvice):在目标方法执行之前进行增强;
  • 后置增强(org.springframework.aop.AfterReturningAdvice):在目标方法执行之后进行增强;
  • 环绕增强(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后都执行增强;
  • 异常抛出增强(org.springframework.aop.ThrowsAdvice):在目标方法抛出异常后执行增强;
  • 引介增强(org.springframework.aop.IntroductionInterceptor):为目标类添加新的方法和属性。
编程式 vs 声明式

Spring 支持两种类型的事务管理:

  • 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
  • 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注解XML 配置来管理事务。

声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用Spring AOP 框架声明式事务管理。

Spring 事务抽象

Spring事务管理接口:

  • PlatformTransactionManager: (平台)事务管理器
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

**所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。**下图是Spring事务抽象的核心类图:

img

Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示:

 public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
 }

TransactionStatus getTransaction(TransactionDefinition definition):根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。

void commit(TransactionStatus status):该方法提交给定的事务和关于它的状态。

void rollback(TransactionStatus status):该方法执行一个给定事务的回滚。

部分Spring包含的对PlatformTransactionManager的实现类如下图所示:

img

AbstractPlatformTransactionManager抽象类实现了Spring事务的标准流程,其子类DataSourceTransactionManager是我们使用较多的JDBC单数据源事务管理器,而JtaTransactionManager是JTA(Java Transaction API)规范的实现类,另外两个则分别是JavaEE容器WebLogicWebSphere的JTA事务管理器的具体实现。

TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:

 public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    String getName();
    int getTimeout();
    boolean isReadOnly();
 }

int getPropagationBehavior():该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。

int getIsolationLevel():该方法返回该事务独立于其他事务的工作的程度。

String getName():该方法返回该事务的名称。

int getTimeout():该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。

boolean isReadOnly():该方法返回该事务是否是只读的。

下面是隔离级别的可能值:

TransactionDefinition.ISOLATION_DEFAULT:这是默认的隔离级别。

TransactionDefinition.ISOLATION_READ_COMMITTED:表明能够阻止误读;可以发生不可重复读和虚读。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED:表明可以发生误读、不可重复读和虚读。

TransactionDefinition.ISOLATION_REPEATABLE_READ:表明能够阻止误读和不可重复读;可以发生虚读。

TransactionDefinition.ISOLATION_SERIALIZABLE:表明能够阻止误读、不可重复读和虚读。

下面是传播类型的可能值:

TransactionDefinition.PROPAGATION_MANDATORY:支持当前事务;如果不存在当前事务,则抛出一个异常。

TransactionDefinition.PROPAGATION_NESTED:如果存在当前事务,则在一个嵌套的事务中执行。

TransactionDefinition.PROPAGATION_NEVER:不支持当前事务;如果存在当前事务,则抛出一个异常。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED:不支持当前事务;而总是执行非事务性。

TransactionDefinition.PROPAGATION_REQUIRED:支持当前事务;如果不存在事务,则创建一个新的事务。

TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新事务,如果存在一个事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_SUPPORTS:支持当前事务;如果不存在,则执行非事务性。

TransactionDefinition.TIMEOUT_DEFAULT:使用默认超时的底层事务系统,或者如果不支持超时则没有。

TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。

 public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
 }

boolean hasSavepoint():该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。

boolean isCompleted():该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。

boolean isNewTransaction():在当前事务是新的情况下,该方法返回 true。

boolean isRollbackOnly():该方法返回该事务是否已标记为 rollback-only。

void setRollbackOnly():该方法设置该事务为 rollback-only 标记。

Spring 编程式事务管理

编程式事务管理方法允许你在对你的源代码编程的帮助下管理事务。这给了你极大地灵活性,但是它很难维护。

在我们开始之前,至少要有两个数据库表,在事务的帮助下我们可以执行多种 CRUD 操作。以 Student 表为例,用下述 DDL 可以在 MySQL TEST 数据库中创建该表:

 CREATE TABLE Student(
    ID   INT NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(20) NOT NULL,
    AGE  INT NOT NULL,
    PRIMARY KEY (ID)
 );

第二个表是 Marks,用来存储基于年份的学生的标记。这里 SID 是 Student 表的外键。

 CREATE TABLE Marks(
    SID INT NOT NULL,
    MARKS  INT NOT NULL,
    YEAR   INT NOT NULL
 );

让我们直接使用 PlatformTransactionManager 来实现编程式方法从而实现事务。要开始一个新事务,你需要有一个带有适当的 transaction 属性的 TransactionDefinition 的实例。这个例子中,我们使用默认的 transaction 属性简单的创建了 DefaultTransactionDefinition 的一个实例。

当 TransactionDefinition 创建后,你可以通过调用 getTransaction() 方法来开始你的事务,该方法会返回 TransactionStatus 的一个实例。 TransactionStatus 对象帮助追踪当前的事务状态,并且最终,如果一切运行顺利,你可以使用 PlatformTransactionManagercommit() 方法来提交这个事务,否则的话,你可以使用 rollback() 方法来回滚整个操作。

现在让我们编写我们的 Spring JDBC 应用程序,它能够在 Student 和 Mark 表中实现简单的操作。让我们适当的使用 Eclipse IDE,并按照如下所示的步骤来创建一个 Spring 应用程序:

步骤一:创建一个名为 SpringExample 的项目,并在创建的项目中的 src 文件夹下创建包com.tutorialspoint

步骤二:使用 Add JARs 选项添加必需的 Spring 库。

步骤三:在项目中添加 Spring JDBC 指定的最新的库 mysql-connector-java.jar,org.springframework.jdbc.jar 和 org.springframework.transaction.jar。如果没有这些库,你可以下载它们。

步骤四:创建 DAO 接口 StudentDAO 并列出所有需要的方法。尽管它不是必需的并且你可以直接编写 StudentJDBCTemplate 类,但是作为一个好的实践,我们还是做吧。

步骤五:在com.tutorialspoint包下创建其他必需的 Java 类 StudentMarksStudentMarksMapperStudentJDBCTemplateMainApp。如果需要的话,你可以创建其他的 POJO 类。

步骤六:确保你已经在 TEST 数据库中创建了 Student 和 Marks 表。还要确保你的 MySQL 服务器运行正常并且你使用给出的用户名和密码可以读/写访问数据库。

步骤七:在 src 文件夹下创建 Beans 配置文件 Beans.xml

最后一步:最后一步是创建所有 Java 文件和 Bean 配置文件的内容并按照如下所示的方法运行应用程序。

下面是数据访问对象接口文件 StudentDAO.java 的内容:

  package com.tutorialspoint;
  import java.util.List;
  import javax.sql.DataSource;
  public interface StudentDAO {
     /** 
      * This is the method to be used to initialize
      * database resources ie. connection.
      */
     public void setDataSource(DataSource ds);
    /** 
     * This is the method to be used to create
     * a record in the Student and Marks tables.
     */
    public void create(String name, Integer age, Integer marks, Integer year);
    /** 
     * This is the method to be used to list down
     * all the records from the Student and Marks tables.
     */
    public List<StudentMarks> listStudents();
 }

下面是 StudentMarks.java 文件的内容:

  package com.tutorialspoint;
  public class StudentMarks {
     private Integer age;
     private String name;
     private Integer id;
     private Integer marks;
     private Integer year;
     private Integer sid;
     public void setAge(Integer age) {
       this.age = age;
    }
    public Integer getAge() {
       return age;
    }
    public void setName(String name) {
       this.name = name;
    }
    public String getName() {
       return name;
    }
    public void setId(Integer id) {
       this.id = id;
    }
    public Integer getId() {
       return id;
    }
    public void setMarks(Integer marks) {
       this.marks = marks;
    }
    public Integer getMarks() {
       return marks;
    }
    public void setYear(Integer year) {
       this.year = year;
    }
    public Integer getYear() {
       return year;
    }
    public void setSid(Integer sid) {
       this.sid = sid;
    }
    public Integer getSid() {
       return sid;
    }
 }

以下是 StudentMarksMapper.java 文件的内容:

  package com.tutorialspoint;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import org.springframework.jdbc.core.RowMapper;
  public class StudentMarksMapper implements RowMapper<StudentMarks> {
     public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
        StudentMarks studentMarks = new StudentMarks();
        studentMarks.setId(rs.getInt("id"));
        studentMarks.setName(rs.getString("name"));
       	studentMarks.setAge(rs.getInt("age"));
       	studentMarks.setSid(rs.getInt("sid"));
       	studentMarks.setMarks(rs.getInt("marks"));
       	studentMarks.setYear(rs.getInt("year"));
       	return studentMarks;
    }
 }

下面是定义的 DAO 接口 StudentDAO 实现类文件 StudentJDBCTemplate.java:

package com.tutorialspoint;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class StudentJDBCTemplate implements StudentDAO {
   private DataSource dataSource;
   private JdbcTemplate jdbcTemplateObject;
   private PlatformTransactionManager transactionManager;
   public void setDataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      this.jdbcTemplateObject = new JdbcTemplate(dataSource);
   }
   public void setTransactionManager(
      PlatformTransactionManager transactionManager) {
      this.transactionManager = transactionManager;
   }
   public void create(String name, Integer age, Integer marks, Integer year){
      TransactionDefinition def = new DefaultTransactionDefinition();
      TransactionStatus status = transactionManager.getTransaction(def);
      try {
         String SQL1 = "insert into Student (name, age) values (?, ?)";
         jdbcTemplateObject.update( SQL1, name, age);
         // Get the latest student id to be used in Marks table
         String SQL2 = "select max(id) from Student";
         int sid = jdbcTemplateObject.queryForInt( SQL2 );
         String SQL3 = "insert into Marks(sid, marks, year) " +
                       "values (?, ?, ?)";
         jdbcTemplateObject.update( SQL3, sid, marks, year);
         System.out.println("Created Name = " + name + ", Age = " + age);
         transactionManager.commit(status);
      } catch (DataAccessException e) {
         System.out.println("Error in creating record, rolling back");
         transactionManager.rollback(status);
         throw e;
      }
      return;
   }
   public List<StudentMarks> listStudents() {
      String SQL = "select * from Student, Marks where Student.id=Marks.sid";
      List <StudentMarks> studentMarks = jdbcTemplateObject.query(SQL,
                                         new StudentMarksMapper());
      return studentMarks;
   }
}

现在让我们改变主应用程序文件 MainApp.java,如下所示:

package com.tutorialspoint;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.tutorialspoint.StudentJDBCTemplate;
public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context =
             new ClassPathXmlApplicationContext("Beans.xml");
      StudentJDBCTemplate studentJDBCTemplate =
      (StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
      System.out.println("------Records creation--------" );
      studentJDBCTemplate.create("Zara", 11, 99, 2010);
      studentJDBCTemplate.create("Nuha", 20, 97, 2010);
      studentJDBCTemplate.create("Ayan", 25, 100, 2011);
      System.out.println("------Listing all the records--------" );
      List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
      for (StudentMarks record : studentMarks) {
         System.out.print("ID : " + record.getId() );
         System.out.print(", Name : " + record.getName() );
         System.out.print(", Marks : " + record.getMarks());
         System.out.print(", Year : " + record.getYear());
         System.out.println(", Age : " + record.getAge());
      }
   }
}

下面是配置文件 Beans.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-3.0.xsd ">

   <!-- Initialization for data source -->
   <bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
      <property name="username" value="root"/>
      <property name="password" value="password"/>
   </bean>

   <!-- Initialization for TransactionManager -->
   <bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource"  ref="dataSource" />
   </bean>

   <!-- Definition for studentJDBCTemplate bean -->
   <bean id="studentJDBCTemplate"
      class="com.tutorialspoint.StudentJDBCTemplate">
      <property name="dataSource"  ref="dataSource" />
      <property name="transactionManager"  ref="transactionManager" />
   </bean>

</beans>

当你完成了创建源和 bean 配置文件后,让我们运行应用程序。如果你的应用程序运行顺利的话,那么将会输出如下所示的消息:

------Records creation--------
Created Name = Zara, Age = 11
Created Name = Nuha, Age = 20
Created Name = Ayan, Age = 25
------Listing all the records--------
ID : 1, Name : Zara, Marks : 99, Year : 2010, Age : 11
ID : 2, Name : Nuha, Marks : 97, Year : 2010, Age : 20
ID : 3, Name : Ayan, Marks : 100, Year : 2011, Age : 25
Spring 声明式事务管理

声明式通过配置或注解方式将事务管理与业务逻辑代码独立,开发人员只需在配置文件中做相应的事务规则声明或注解,便可以将事务规则通过AOP横切应用到业务逻辑中。支持TransactionInterceptor、TransactionProxyFactoryBean多种配置方式,Spring 2.x 引入了 命名空间和基于 Annotation 的方式,使得事务规则的声明配置更为简单方便。

基于 命名空间的声明式事务管理

清楚StudentJDBCTemplate.java中的编程式事务管理代码,修改之前例子中的配置文件 Beans.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:tx="http://www.springframework.org/schema/tx"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

   <!-- Initialization for data source -->
   <bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
      <property name="username" value="root"/>
      <property name="password" value="cohondob"/>
   </bean>

   <tx:advice id="txAdvice"  transaction-manager="transactionManager">
      <tx:attributes>
      <tx:method name="create"/>
      </tx:attributes>
   </tx:advice>

   <aop:config>
      <aop:pointcut id="createOperation"
      expression="execution(* com.tutorialspoint.StudentJDBCTemplate.create(..))"/>
      <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
   </aop:config>

   <!-- Initialization for TransactionManager -->
   <bean id="transactionManager"
   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource"  ref="dataSource" />
   </bean>

   <!-- Definition for studentJDBCTemplate bean -->
   <bean id="studentJDBCTemplate"
   class="com.tutorialspoint.StudentJDBCTemplate">
      <property name="dataSource"  ref="dataSource" />
   </bean>

</beans>
基于 @Transactional 的声明式事务管理

声明式事务的实现就是通过环绕增强的方式,在目标方法执行之前开启事务,在目标方法执行之后提交或者回滚事务,事务拦截器的继承关系图可以体现这一点:

img

通过在业务处理接口、接口方法、类以及类方法上添加@Transactional注解便可实现事务配置。

img

在配置文件中仅仅需要开启注解开关

img

这两种方式各有优点:基于 命名空间的方式,可通过切点表达式批量匹配多个方法;基于@Transactional 注解方式更为简单明了。

MySQL基础知识


不做赘述,网上资料很多。谷歌、百度搜索MySQL教程。推荐书籍:《MySQL必知必会》多读,理解清楚~

MySQL知识点扫盲


MySQL一对一多对多等关系的处理

首先需要知道,我们可以通过外键的方式实现表与表之间的关联关系。

1 foreign key2
-- 则表1的多条记录对应表2的一条记录,即多对一

利用foreign key的原理我们可以制作两张表的多对多,一对一关系

-- 多对多:1的多条记录可以对应表2的一条记录
    表2的多条记录也可以对应表1的一条记录

-- 一对一:1的一条记录唯一对应表2的一条记录,反之亦然

分析时,我们先从按照上面的基本原理去套,然后再翻译成真实的意义,就很好理解了

先确定表与表之间有哪些关系

  • 一对一

  • 一对多

  • 多对多

找到多的一方,把关联字段写在多的一方

一对多

多对一或者一对多(左边表的多条记录对应右边表的唯一一条记录)

需要注意的:

  • 1.先建被关联的表,保证被关联表的字段必须唯一。
  • 2.在创建关联表,关联字段一定保证是要有重复的。
示例:

这是一个书和出版社的一个例子,书要关联出版社(多个书可以是一个出版社,一个出版社也可以有好多书)。

谁关联谁就是谁要按照谁的标准。

  • 创建表

    书要关联出版社
    被关联的表
    create  table press(`id` int primary key auto_increment, `name`char(20));
    
    关联的表
    create table `book`(
    `book_id` int primary key auto_increment,
    `book_name` varchar(20),
    `book_price` int,
    `press_id` int,
    constraint `Fk_pressid_id` foreign key(`press_id`) references press(`id`)
    on delete cascade
    on update cascade
    ); 
    
  • 插入数据

    insert into press(name) values('新华出版社'), ('海燕出版社'), ('摆渡出版社'), ('大众出版社');
    
    
    insert into book(book_name,book_price,press_id) values('Python爬虫',100,1), ('Linux',80,1), ('操作系统',70,2), ('数学',50,2), ('英语',103,3), ('网页设计',22,3);
    
一对一
示例一:

用户和管理员(只有管理员才可以登录,一个管理员对应一个用户)

管理员关联用户

  • 创建表

    先建被关联的表
    create table `user`(
    `id`int primary key auto_increment, #主键自增
    `name` char(10)
    );
    
    再建关联表
    create table `admin`(
    `id` int primary key auto_increment,
    `user_id` int unique,
    `password` varchar(16),
    foreign key(`user_id`) references user(`id`)
    on delete cascade
    on update cascade
    );
    
  • 插入数据

    insert into user(name) values('susan1'),('susan2'),('susan3'),('susan4')('susan5'),('susan6');
    
    insert into admin(user_id,password) values(4,'sds156'),(2,'531561'),(6,'f3swe');
    
示例二:

学生表和客户表

  • 创建表

    create table `customer`(
    `id` int primary key auto_increment,
    `name` varchar(10),
    `qq` int unique,
    `phone` int unique
    );
    
    create table `student1`(
    `sid` int primary key auto_increment,
    `course` char(20),
    `class_time` time,
    `cid` int unique,
    foreign key(`cid`) references customer(`id`)
    on delete cascade
    on update cascade
    );
    
  • 插入数据

    insert into customer(name,qq,phone) values('小小',13564521,11111111),('嘻哈',14758254,22222222),('王维',44545522,33333333),('胡军',545875212,4444444),('李希',145578543,5555555),('李迪',754254653,8888888),('艾哈',74545145,8712547),('啧啧',11147752,7777777);
    
    insert into student1(course,class_time,cid) values('python','08:30:00',3),('python','08:30:00',4),('linux','08:30:00',1),('linux','08:30:00',7);
    
多对多

书和作者(我们可以再创建一张表,用来存book和author两张表的关系)

要把book_id和author_id设置成联合唯一

联合唯一:unique(book_id,author_id)

联合主键:alter table t1 add primary key(id,avg)

多对多:一个作者可以写多本书,一本书也可以有多个作者,双向的一对多,即多对

关联方式:foreign key+一张新的表

示例:
  • 创建表

    ========书和作者,另外在建一张表来存书和作者的关系
    #被关联的
    create table `book1`(
    `id` int primary key auto_increment,
    `name` varchar(10),
    `price` float(3,2)
    );
    
    #========被关联的
    create table `author`(
    `id` int primary key auto_increment,
    `name` char(5)
    );
    
    #========关联的
    create table `author2book`(
    `id` int primary key auto_increment,
    `book_id` int not null,
    `author_id` int not null,
    unique(`book_id`,`author_id`),
    foreign key(`book_id`) references `book1`(`id`)
    on delete cascade
    on update cascade,
    foreign key(`author_id`) references `author`(`id`)
    on delete cascade
    on update cascade
    );
    
  • 插入数据

    insert into book1(name,price) values('九阳神功',9.9), ('葵花宝典',9.5), ('辟邪剑谱',5),  ('降龙十巴掌',7.3);
    
    insert into author(name) values('egon'),('e1'),('e2'),('e3'),('e4');
    
    insert into author2book(book_id,author_id) values(1,1),(1,4),(2,1),(2,5),(3,2),(3,3),(3,4),(4,5);
    

拓展:配合MyBatis实现

DML语句

DML(Data Maniplution Language):数据操作语句。

主要指数据库对表记录的操作,包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select)等。

新建数据表举例:

+----------+---------------+------+-----+---------+-------+
| Field    | Type          | Null | Key | Default | Extra |
+----------+---------------+------+-----+---------+-------+
| ename    | varchar(20)   | YES  |     | NULL    |       |
| hiredate | date          | YES  |     | NULL    |       |
| sal      | decimal(10,2) | YES  |     | NULL    |       |
| deptno   | int(2)        | YES  |     | NULL    |       |
+----------+---------------+------+-----+---------+-------+
插入记录

插入记录的sql语句语法:

INSERT INTO tablename(field1,field2,……fieldn) VALUES(value1,value2,……valuesn);

要点:

  1. 需要在表明后的括号内指定列名,而且需要values和列名顺序对应。

    insert into emp(ename,hiredate,sal,deptno) values('zzx1','2000-01-01','2000',1);
    
  2. 如果不指定列名,values需要和数据表字段顺序一致。

    insert into empvalues('lisa','2003-02-01','3000',2);
    
  3. 对于含可空字段、非空但是含有默认值的字段、自增字段,可以不用在 insert 后的字段列表里面出现,values 后面只写对应字段名称的 value,这些没写的字段可以自动设置为 NULL、默认值、自增的下一个数字,这样在某些情况下可以大大缩短 SQL 语句的复杂性。

    insert into emp(ename,sal) values('dony',1000);
    
  4. 如果我们想同时插入多列,来节省数据库访问次数,提高插入效率,可用以下语法:

    INSERT INTO tablename(field1, field2,……fieldn)
    VALUES
    (record1_value1, record1_value2,……record1_valuesn),
    (record2_value1, record2_value2,……record2_valuesn),
    ……
    (recordn_value1, recordn_value2,……recordn_valuesn)
    ;
    
    #举例
    insert into emp(ename,deptno) values ('a',10),('b',11),('c',12);
    
更新记录

更新记录的sql语句语法:

UPDATE tablename SET field1=value1,field2.=value2,……fieldn=valuen [WHERE CONDITION];
#如果不指定条件,会对表中所有记录进行更新:
UPDATE emp SET sal = 1000;

要点:

  1. 如果只想更新特定列,需要通过where语句指定:

    update emp set sal = 2000 where deptno >= 10;
    
  2. 如果想更新多个属性列,用逗号隔开,更新属性列时,还可以使用当前属性列中的内容:

    update emp set sal = 2000,ename=deptno where deptno >= 10;
    
删除记录

语法为:

DELETE FROM tablename [WHERE CONDITION]

尝试把hiredate为null的不合法语句进行删除,注意属性为null的where条件检索写法:

delete from emp where hiredate is null;
查询记录

select语句的语法非常复杂,我们稍微把一些常用的罗列出来,不包括连接查询,如下所示:

SELECT
    [ DISTINCT]
    column1,column2
  /*[FROM table_references
    [WHERE where_definition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC]
    [HAVING where_definition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
  */

最常用的,我们通过column,即列名来查询出我们需要的数据,如果我们想查询所有列,可以用“*”来代替所有列名,而where则给出了我们的查询条件,mysql会根据我们的查询条件查询出特定列数据。

下面我们来看看其它常用选项的用法:

  1. distinct是指选择出不重复的,举例如下:

    select sal from emp;
    

    ±--------+

    | sal |

    ±--------+

    | 1000.00 |

    | 1000.00 |

    ±--------+

    select distinct sal from emp;
    

    ±--------+

    | sal |

    ±--------+

    | 1000.00 |

    ±--------+

    select distinct sal,ename from emp;
    

    ±--------±------+

    | sal | ename |

    ±--------±------+

    | 1000.00 | zzx1 |

    | 1000.00 | lisa |

    ±--------±------+

    可以看出distinct是选出所有列都不重复的数据

  2. group by :按特定条件分类,举例如下:

    select * from emp;
    

    ±------±-----------±--------±-------+

    | ename | hiredate | sal | deptno |

    ±------±-----------±--------±-------+

    | zzx1 | 2000-01-01 | 1000.00 | 1 |

    | lisa | 2003-02-01 | 1000.00 | 2 |

    | a | NULL | 2000.00 | NULL |

    | b | NULL | 2000.00 | NULL |

    ±------±-----------±--------±-------+

    select count(*),sal from emp group by sal;
    

    ±---------±--------+

    | count(*) | sal |

    ±---------±--------+

    | 2 | 1000.00 |

    | 2 | 2000.00 |

    ±---------±--------+

    可见我们根据sal将记录分成了两组,并查询出每组的人数。

  3. order by 根据特定列进行排序

    我们根据sal来排序,默认升序asc降序desc

    select * from emp order by sal;
    

    ±------±-----------±--------±-------+

    | ename | hiredate | sal | deptno |

    ±------±-----------±--------±-------+

    | zzx1 | 2000-01-01 | 1000.00 | 1 |

    | lisa | 2003-02-01 | 1000.00 | 2 |

    | a | NULL | 2000.00 | NULL |

    | b | NULL | 2000.00 | NULL |

    ±------±-----------±--------±-------+

    select * from emp order by sal desc;
    

    ±------±-----------±--------±-------+

    | ename | hiredate | sal | deptno |

    ±------±-----------±--------±-------+

    | a | NULL | 2000.00 | NULL |

    | b | NULL | 2000.00 | NULL |

    | zzx1 | 2000-01-01 | 1000.00 | 1 |

    | lisa | 2003-02-01 | 1000.00 | 2 |

    ±------±-----------±--------±-------+

  4. having condition 用于辅助查询的条件过滤:

    select * from emp where sal = 1000 having deptno = 2;
    

    ±------±-----------±--------±-------+

    | ename | hiredate | sal | deptno |

    ±------±-----------±--------±-------+

    | lisa | 2003-02-01 | 1000.00 | 2 |

    ±------±-----------±--------±-------+

  5. limit,限制查询数据:

    索引从0开始,查询出1到3条

    select * from emp limit 1,3;
    

    ±------±-----------±--------±-------+

    | ename | hiredate | sal | deptno |

    ±------±-----------±--------±-------+

    | lisa | 2003-02-01 | 1000.00 | 2 |

    | a | NULL | 2000.00 | NULL |

    | b | NULL | 2000.00 | NULL |

    ±------±-----------±--------±-------+

MySQL的执行过程(原文章

MySQL整体的执行过程如下图所示:

参考图片

文字表述+图如下:

参考图片

连接器

连接器的主要职责就是:

①负责与客户端的通信,是半双工模式,这就意味着某一固定时刻只能由客户端向服务器请求或者服务器向客户端发送数据,而不能同时进行,其中MySQL在与客户端连接TCP/IP的

②验证请求用户的账户和密码是否正确,如果账户和密码错误,会报错:Access denied for user ‘root’@‘localhost’ (using password: YES)

③如果用户的账户和密码验证通过,会在MySQL自带的权限表中查询当前用户的权限:

MySQL中存在4个控制权限的表,分别为user表,db表,tables_priv表,columns_priv表,MySQL权限表的验证过程为:

1:User表:存放用户账户信息以及全局级别(所有数据库)权限,决定了来自哪些主机的哪些用户可以访问数据库实例

Db表:存放数据库级别的权限,决定了来自哪些主机的哪些用户可以访问此数据库

Tables_priv表:存放表级别的权限,决定了来自哪些主机的哪些用户可以访问数据库的这个表

Columns_priv表:存放列级别的权限,决定了来自哪些主机的哪些用户可以访问数据库表的这个字段

Procs_priv表:存放存储过程和函数级别的权限

2:先从user表中的Host,User,Password这3个字段中判断连接的ip、用户名、密码是否存在,存在则通过验证。

3:通过身份认证后,进行权限分配,按照user,db,tables_priv,columns_priv的顺序进行验证。即先检查全局权限表user,如果user中对应的权限为Y,则此用户对所有数据库的权限都为Y,将不再检查db, tables_priv,columns_priv;如果为N,则到db表中检查此用户对应的具体数据库,并得到db中为Y的权限;如果db中为N,则检查tables_priv中此数据库对应的具体表,取得表中的权限Y,以此类推

4:如果在任何一个过程中权限验证不通过,都会报错

缓存

MySQL的缓存主要的作用是为了提升查询的效率,缓存以key和value的哈希表形式存储,key是具体的sql语句,value是结果的集合。如果无法命中缓存,就继续走到分析器的的一步,如果命中缓存就直接返回给客户端 。不过需要注意的是在MySQL的8.0版本以后,缓存被官方删除掉了。之所以删除掉,是因为查询缓存的失效非常频繁,如果在一个写多读少的环境中,缓存会频繁的新增和失效。对于某些更新压力大的数据库来说,查询缓存的命中率会非常低,MySQL为了维护缓存可能会出现一定的伸缩性的问题,目前在5.6的版本中已经默认关闭了,比较推荐的一种做法是将缓存放在客户端,性能大概会提升5倍左右

分析器

分析器的主要作用是将客户端发过来的sql语句进行分析,这将包括预处理与解析过程,在这个阶段会解析sql语句的语义,并进行关键词和非关键词进行提取、解析,并组成一个解析树。具体的关键词包括不限定于以下:select/update/delete/or/in/where/group by/having/count/limit等.如果分析到语法错误,会直接给客户端抛出异常:ERROR:You have an error in your SQL syntax.

比如:select * from user where userId =1234;

在分析器中就通过语义规则器将select from where这些关键词提取和匹配出来,MySQL会自动判断关键词和非关键词,将用户的匹配字段和自定义语句识别出来。这个阶段也会做一些校验:比如校验当前数据库是否存在user表,同时假如User表中不存在userId这个字段同样会报错:unknown column in field list.

优化器

能够进入到优化器阶段表示sql是符合MySQL的标准语义规则的并且可以执行的,此阶段主要是进行sql语句的优化,会根据执行计划进行最优的选择,匹配合适的索引,选择最佳的执行方案。比如一个典型的例子是这样的:

表T,对A、B、C列建立联合索引,在进行查询的时候,当sql查询到的结果是:select xx where B=x and A=x and C=x.很多人会以为是用不到索引的,但其实会用到,虽然索引必须符合最左原则才能使用,但是本质上,优化器会自动将这条sql优化为:where A=x and B=x and C=X,这种优化会为了底层能够匹配到索引,同时在这个阶段是自动按照执行计划进行预处理,MySQL会计算各个执行方法的最佳时间,最终确定一条执行的sql交给最后的执行器

执行器

在执行器的阶段,此时会调用存储引擎的API,API会调用存储引擎,主要有一下存储的引擎,不过常用的还是myisam和innodb:

参考图片

引擎以前的名字叫做:表处理器(其实这个名字我觉得更能表达它存在的意义)负责对具体的数据文件进行操作,对sql的语义比如select或者update进行分析,执行具体的操作。在执行完以后会将具体的操作记录到binlog中,需要注意的一点是:select不会记录到binlog中,只有update/delete/insert才会记录到binlog中。而update会采用两阶段提交的方式,记录都redolog中

执行的状态

可以通过命令:show full processlist,展示所有的处理进程,主要包含了以下的状态,表示服务器处理客户端的状态,状态包含了从客户端发起请求到后台服务器处理的过程,包括加锁的过程、统计存储引擎的信息,排序数据、搜索中间表、发送数据等。囊括了所有的MySQL的所有状态,其中具体的含义如下图:

参考图片
SQL的执行顺序

事实上,sql并不是按照我们的书写顺序来从前往后、左往右依次执行的,它是按照固定的顺序解析的,主要的作用就是从上一个阶段的执行返回结果来提供给下一阶段使用,sql在执行的过程中会有不同的临时中间表,一般是按照如下顺序:

例子: select distinct s.id from T t join S s on t.id=s.id where t.name=“Yrion” group by t.mobile having count(*)>2 order by s.create_time limit 5;

1. from

第一步就是选择出from关键词后面跟的表,这也是sql执行的第一步:表示要从数据库中执行哪张表。

实例说明:在这个例子中就是首先从数据库中找到表T

2. join on

join是表示要关联的表,on是连接的条件。通过from和join on选择出需要执行的数据库表T和S,产生笛卡尔积,生成T和S合并的临时中间表Temp1。on:确定表的绑定关系,通过on产生临时中间表Temp2。

实例说明:找到表S,生成临时中间表Temp1,然后找到表T的id和S的id相同的部分组成成表Temp2,Temp2里面包含着T和S的id相等的所有数据。

3. where

where表示筛选,根据where后面的条件进行过滤,按照指定的字段的值(如果有and连接符会进行联合筛选)从临时中间表Temp2中筛选需要的数据。注意如果在此阶段找不到数据,会直接返回客户端,不会往下进行。这个过程会生成一个临时中间表Temp3。注意在where中不可以使用聚合函数,聚合函数主要是(min\max\count\sum等函数)。

实例说明:在temp2临时表集合中找到T表的name="Yrion"的数据,找到数据后会成临时中间表Temp3,temp3里包含name列为"Yrion"的所有表数据。

4. group by

group by是进行分组,对where条件过滤后的临时表Temp3按照固定的字段进行分组,产生临时中间表Temp4,这个过程只是数据的顺序发生改变,而数据总量不会变化,表中的数据以组的形式存在

实例说明:在temp3表数据中对mobile进行分组,查找出mobile一样的数据,然后放到一起,产生temp4临时表。

5. Having

对临时中间表Temp4进行聚合,这里可以为count等计数,然后产生中间表Temp5,在此阶段可以使用select中的别名。

实例说明:在temp4临时表中找出条数大于2的数据,如果小于2直接被舍弃掉,然后生成临时中间表temp5。

6. select

对分组聚合完的表挑选出需要查询的数据,如果为*会解析为所有数据,此时会产生中间表Temp6。

实例说明:在此阶段就是对temp5临时聚合表中S表中的id进行筛选产生Temp6,此时temp6就只包含有s表的id列数据,并且name=“Yrion”,通过mobile分组数量大于2的数据。

7. Distinct

distinct对所有的数据进行去重,此时如果有min、max函数会执行字段函数计算,然后产生临时表Temp7

实例说明:此阶段对temp5中的数据进行去重,引擎API会调用去重函数进行数据的过滤,最终只保留id第一次出现的那条数据,然后产生临时中间表temp7

8. order by

会根据Temp7进行顺序排列或者逆序排列,然后插入临时中间表Temp8,这个过程比较耗费资源

实例说明:这段会将所有temp7临时表中的数据按照创建时间(create_time)进行排序,这个过程也不会有列或者行损失

9. limit

limit对中间表Temp8进行分页,产生临时中间表Temp9,返回给客户端。

实例说明:在temp7中排好序的数据,然后取前五条插入到Temp9这个临时表中,最终返回给客户端

ps:实际上这个过程也并不是绝对这样的,中间MySQL会有部分的优化以达到最佳的优化效果,比如在select筛选出找到的数据集

MySQL事务

ACID原则:

  • Aotmicity:原子性。
  • Consistency:一致性。
  • Isolation:隔离性。
  • Durability:持久性。

详见Spring事务中的ACID原则描述。

优质博客参考

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值