Spring 系列文章
文章目录
- Spring 系列文章
- 前言
- 一、Spring 介绍
- 二、Spring 架构特征
- 三、Spring 优势
- 四、Spring 体系结构
- 五、IOC 控制反转
- 1. 概念引入
- 2. 原理分析
- 六、Bean 管理
- 1. 介绍
- 2. 管理的内容
- 3. Bean 管理方式
- 1. XML实现DI 赋值
- 2. Bean生命周期
- 1. 测试生命周期
- 2. 后置处理器
- 3. bean 自动装配
- 3. 注解方式管理Bean
- 4. 代理模式
- 1. 介绍
- 2. 静态代理
- 3. 动态代理
- 1. Proxy 动态代理
- 2. Cglib 动态代理
- 4. 代理模式总结
- 七、AOP 面向切面编程
- 1. **简介**
- 2. **主要功能**
- 3. **主要意图**
- 4. **实现**
- 5. AOP中的术语辨析
- 6. AOP注解方式实现
- 1. 注解实现
- 2. XML配置方式实现
- 八、JdbcTemplate
- 1. JdbcTemplate介绍
- 2. JdbcTemplate实现
- 1. 导入包
- 2. 准备JDBC.properties
- 3. 准备applicationContext.xml
- 4. 准备实体类
- 5. 准备service层接口和实现类
- 6. 准备dao层接口和实现类
- 7. 测试代码
- 3. 批量实现(增删改查)
- 1. 实体类
- 2. service
- 3. dao
- 4. 测试代码
- 九、事务
- 1. 事务概念
- 2. 事务特性
- 1. 原子性
- 2. 一致性
- 3. 隔离性
- 4. 持久性
- 3. 事务并发
- 1. 脏读(Dirty read)
- 2. 不可重复读(Unrepeatableread)
- 3.幻读(Phantom read)
- 4. 不可重复度和幻读区别
- 5. 事务隔离级别
- 4. 注解方式
- 1. 在applicationContext.xml配置事务相关的配置
- 2.在Service层中添加事务的注解
- 3. @Transactional 注解
- 4. isolation 事务的隔离级别
- 5. xml 方式配置
- 总结
前言
- Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC
一、Spring 介绍
-
Spring是Java EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。 [2] Spring是一个开源容器框架,它集成各类型的工具,通过核心的Bean factory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。 [3] Spring是独特的,因为若干个原因:
-
它定位的领域是许多其他流行的framework没有的。Spring致力于提供一种方法管理你的业务对象。
-
Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。
-
它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的framework。
-
Spring对你的工程来说,它不需要一个以上的framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他framework没有考虑到的内容。
二、Spring 架构特征
- 轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
- 控制反转——Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。它的底层设计模式采用了工厂模式,所有的 Bean 都需要注册到Bean工厂中,将其初始化和生命周期的监控交由工厂实现管理。程序员只需要按照规定的格式进行Bean开发,然后利用XML文件进行bean 的定义和参数配置,其他的动态生成和监控就不需要调用者完成,而是统一交给了平台进行管理。 [4] 控制反转是软件设计大师 Martin Fowler在 2004 年发表的”Inversion of Control Containers and the Dependency Injection pattern”提出的。这篇文章系统阐述了控制反转的思想,提出了控制反转有依赖查找和依赖注入实现方式。控制反转意味着在系统开发过程中,设计的类将交由容器去控制,而不是在类的内部去控制,类与类之间的关系将交由容器处理,一个类在需要调用另一个类时,只要调用另一个类在容器中注册的名字就可以得到这个类的实例,与传统的编程方式有了很大的不同,“不用你找,我来提供给你”,这就是控制反转的含义。
- 面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
- 容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
- 框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
- MVC——Spring的作用是整合,但不仅仅限于整合,Spring 框架可以被看做是一个企业解决方案级别的框架。客户端发送请求,服务器控制器(由DispatcherServlet实现的)完成请求的转发,控制器调用一个用于映射的类HandlerMapping,该类用于将请求映射到对应的处理器来处理请求。HandlerMapping 将请求映射到对应的处理器Controller(相当于Action)在Spring 当中如果写一些处理器组件,一般实现Controller 接口,在Controller 中就可以调用一些Service 或DAO 来进行数据操作 ModelAndView 用于存放从DAO 中取出的数据,还可以存放响应视图的一些数据。 如果想将处理结果返回给用户,那么在Spring 框架中还提供一个视图组件ViewResolver,该组件根据Controller 返回的标示,找到对应的视图,将响应response 返回给用户。
- 所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
三、Spring 优势
-
方便解耦,简化开发:Spring通过容器,将对象的创建从代码中剥离出来,交给Spring控制,避免直接编码造成模块之间的耦合度高,用户也不必自己编码处理对象的单例和多例控制,主要关注接口功能即可,不用关注具体使用哪个实现类和实现细节问题。
-
AOP切面编程:AOP切面编程是程序设计的一种概念,Spring对该概念实现的比较好,通过切面编程我们可以在不修改原有代码的情况下实现功能的增加,通常用于 事务控制,日志记录,性能检测,权限控制等等。
-
声明式事务:事务的控制可以托管给Spring,我们通过注解或者配置文件声明事务的处理方式即可,不用我们自己去编码处理。
-
整合JUNIT,方便测试:spring整合JUNIT单元测试,对于项目的功能都可以进行轻松快速的测试,便于我们调试程序。
-
方便整合各种优秀的框架: SSM> Spring+SpringMVC +MyBatis 、SSH> Spring+Hibernate +Strust各种其他框架。
-
丰富的功能封装:spring对JAVAEE(JDBC ,JAVAMail,)都进行了一系列的封装,简化我们对于API的使用,提高程序的开发效率。
-
规范的源码学习样本:spring的源码设计巧妙,结构清晰,大量使用了设计模式,是java代码规范编写的典范,也是高级程序员面试中经常会问到的源码。
四、Spring 体系结构
- Data Access/Integration(数据访问/集成)
- 数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
- JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
- ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
- OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
- JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
- Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。
- Web 模块
- Spring 的 Web 层包括 Web、Servlet、Struts 和 Portlet 组件,具体介绍如下。
- Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
- Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。
- Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
- Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。
- Core Container(核心容器)
- Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。
- Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
- Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
- Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
- Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
- 其他模块
- Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。
- AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
- Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
- Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
- Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。
五、IOC 控制反转
1. 概念引入
-
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
-
简单的说就是,创建对象的权利,或者是控制的位置,由JAVA代码转移到spring容器,由spring的容器控制对象的创建,就是控制反转,spring创建对象时,会读取配置文件中的信息,然后使用反射给我们创建好对象之后在容器中存储起来,当我们需要某个对象时,通过id获取对象即可,不需要我们自己去new.
-
一句话:创建对象交给容器
2. 原理分析
- IOC 原理图
-
通过XML解析技术读取配置文件
<bean id="empDao" class="com.msb.dao.impl.EmpDaoImpl"></bean>
-
反射技术实例化对象,放到容器中
//获得类的字节码 Class clazz =Class.forName("com.msb.dao.impl.EmpDaoImpl") //通过字节码实例化对象 Object obj = clazz.newInstance(); //对象放到一个map集合中 map.put("empDao",obj)
-
工厂模式返回 Bean 对象 getBean 方法
public Object getBean(String name){ Object obj =map.get(name); return obj; }
-
IOC接口
- BeanFactory接口: IOC容器基本功能接口,是spring内部使用的接口,我们在处理业务时一般不直接使用该接口
- ApplicationContext 接口: BeanFactory的子接口,提供更多更强大的功能,研发人员一般使用的接口
六、Bean 管理
1. 介绍
- spring中的Bean的管理:
Bean(汉译咖啡豆). 又称JAVABean.其实就是JAVA程序程序中的一个个对象,所以Bean的管理其实就是spring对于JAVA程序中的对象的管理
2. 管理的内容
- 对象的创建 IOC
- IOC:叫做控制反转,就是Spring给我们创建对象,然后我们直接用,不用自己NEW,前面已经解释过,IOC处理的是对象如何创建的问题
- 属性的赋值 DI
- DI :Dependency Injection,即“依赖注入” 就是创建属性时给对象属性赋值,对象功能的实现往往要依赖属性的值,那么给对象属性赋值就可以说成是依赖注入,由于对象属性不仅仅是基本数据类型,还可能是其他类,或者引用类型,那么依赖注入将会把更多的对象之间的关系整理到一起,可以行程一个庞大的依赖关系,DI处理的是对象的属性赋值和互相依赖的关系。
3. Bean 管理方式
- 基于XML方式的Bean管理
- 基于注解方式的Bean管理
1. XML实现DI 赋值
-
通过 set 赋值给对象赋值
<!--property 就是在使用set方法实现依赖注入--> <bean id="user1" class="com.msb.bean.User"> <property name="userid" value="1"></property> <property name="username" value="张三"></property> <property name="password1" value="abcdefg"></property> </bean>
-
通过有参构造给对象属性赋值
<!-- constructor-arg 就是在使用构造方法实现依赖注入 constructor-arg 的个数必须和某个构造方法的参数个数向对应 name指的是参数名 index指的是参数的索引 value指的是参数值 --> <bean id="user2" class="com.msb.bean.User"> <constructor-arg name="userid" value="2"></constructor-arg> <constructor-arg name="username" value="小明"></constructor-arg> <constructor-arg name="password1" value="123456789"></constructor-arg> </bean> <bean id="user3" class="com.msb.bean.User"> <constructor-arg index="0" value="3"></constructor-arg> <constructor-arg index="1" value="小黑"></constructor-arg> <constructor-arg index="2" value="987654321"></constructor-arg> </bean>
-
通过p名称空间和c名称空间给对象属性赋值
<?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" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
配置对象:
<!--p名称空间,就是对property的简化处理--> <bean id="user4" class="com.msb.bean.User" p:userid="4" p:username="小东" p:password2="111111" ></bean> <!--c名称空间,就是对constructor-arg的简化--> <bean id="user5" class="com.msb.bean.User" c:userid="5" c:username="小西" c:password1="222222" ></bean>
2. Bean生命周期
- bean从创建到销毁经历的各个阶段以及每个阶段所调用的方法
- 通过构造器创建bean实例:执行构造器
- 为bean属性赋值:执行set方法
- 初始化bean:调用bean的初始化方法,需要配置指定调用的方法
- bean的获取:容器对象 getBean方法
- 容器关闭销毁bean:调用销毁方法,需要配置指定调用的方法
1. 测试生命周期
-
准备bean
package com.msb.bean; /** * @Author: bingwoo */ public class User { private Integer userid; private String username; private String password1; public void initUser(){ System.out.println("第三步:User初始化"); } public User() { System.out.println("第一步:User构造"); } public void destoryUser(){ System.out.println("第五步:User销毁"); } @Override public String toString() { return "User{" + "userid=" + userid + ", username='" + username + '\'' + ", password1='" + password1 + '\'' + '}'; } public User(Integer userid, String username, String password1) { this.userid = userid; this.username = username; this.password1 = password1; } public void setUserid(Integer userid) { System.out.println("setUserid"); this.userid = userid; } public void setUsername(String username) { System.out.println("第二步:User属性赋值"); this.username = username; } public void setPassword1(String password1) { this.password1 = password1; } }
-
配置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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.msb.bean.User" init-method="initUser" destroy-method="destoryUser"> <property name="username" value="xiaoming"></property> </bean> </beans>
2. 后置处理器
- 后置步骤
- 通过构造器创建bean实例:执行构造器
- 为bean属性赋值:执行set方法
- 把bean实例传递给bean的后置处理器的方法
- 初始化bean:调用bean的初始化方法,需要配置指定调用的方法
- 把bean实例传递给bean的后置处理器的方法
- bean的获取:容器对象 getBean方法
- 容器关闭销毁bean:调用销毁方法,需要配置指定调用的方法
-
创建后置处理器 实现 BeanPostProcesser 重写两个方法
package com.msb.beanProcesser; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * @Author: bingwoo */ public class MyBeanProcesser implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //Object bean 实例化的bean //String beanName bean的id System.out.println("bean:初始化方法之前"); return bean;// 这里必须return bean } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("bean:初始化方法之后"); return bean;// 这里必须returnbean } }
-
配置后置处理器,对容器中的所有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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.msb.bean.User" init-method="initUser" destroy-method="destoryUser"> <property name="username" value="xiaoming"></property> </bean> <bean id="myBeanProcesser" class="com.msb.beanProcesser.MyBeanProcesser"></bean> </beans>
-
BeanPostProcessor接口作用
- 如果我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。
- 接口中的两个方法都要将传入的bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标。
- ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法
3. bean 自动装配
- 通过property标签可以手动指定给属性进行注入
- 我们也可以通过自动转配,完成属性的自动注入,就是自动装配,可以简化DI的配置
3. 注解方式管理Bean
- 注解方式创建对象IOC
-
@Component 放在类上,用于标记,告诉spring当前类需要由容器实例化bean并放入容器中,该注解有三个子注解:
- @Controller 用于实例化controller层bean
- @Service 用于实例化service层bean
- @Repository 用于实例化持久层bean
-
当不确定是哪一层,就用Component
-
这几个注解互相混用其实也可以,但是不推荐
-
-
第一步:在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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" 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 http://www.springframework.org/schema/context/spring-context.xsd "> <!--添加注解扫描,扫描指定的包,将包中的所有有注解的类实例化 base-package 后面放要扫描的包 如果有多个包需要扫描,可以使用逗号隔开 com.msb.bean,com.msb.service 或者可以写上一层包路径 com.msb 可以通过注解指定bean的id@Component("user1") 如果不指定,则id默认是 类名首字母小写 --> <context:component-scan base-package="com.msb.bean"></context:component-scan> </beans>
-
第二步:在类上添加注解,让spring容器给我们创建bean实例并存储于容器中
package com.msb.bean; import org.springframework.stereotype.Component; /** * @Author: Ma HaiYang * @Description: MircoMessage:Mark_7001 */ @Component(value = "user1") public class User { }
-
组件扫描配置注解识别
<!-- use-default-filters="false" 默认值为true 代表使用默认的扫描过滤器 默认的扫描过滤器会识别并包含 @Component @Controller @Service @Repository 四个注解 不使用默认的filter,使用我们自己的filter --> <!--控制只扫描Controller注解--> <context:component-scan base-package="com.msb" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--控制不扫描Controller注解--> <context:component-scan base-package="com.msb" use-default-filters="true"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
- 注解方式实现依赖注入DI
- @Autowired 根据属性数据类型自动装配
- @Qualifier 根据属性名称注入依赖
- @Resources 可以根据类型,也可以根据名称注入
- @Value 注入普通数据类型(8+String)
package com.msb.service.impl; import com.msb.dao.UserDao; import com.msb.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @Author: bignwoo */ @Service public class UserServiceImpl implements UserService { /* *@Autowired * 根据类型到容器中去寻找对应的对象,找到后给当前属性赋值 * 不需要依赖 set方法 * 属性类型可以是接口,会自动匹配对应的实现类对象 * @Autowired配合 @Qualifier,可以通过名称指定注入的对象 * * @Resource 如果不配置name 那么就是根据类型注入 * @Resource(name="userDaoImplB") 配置name,就是根据名称注入 * * * @Resource 是JDK中javax包的注解 * @Autowired 和 @Qualifier 是spring中的注解 * * @Value 可以个普通属性赋值 * @Value 可以使用${}这种表达式获取系统的变量值 * 或者是.properties属性配置文件中的值 * * */ //@Autowired //@Qualifier("userDaoImplA") //@Qualifier("userDaoImplB") //private UserDao userDao ; @Resource(name="userDaoImplB") private UserDao userDao ; @Value("${username}") private String sname; @Value("boy") private String sgender; @Value("${age}") private Integer sage; @Override public void add() { System.out.println("userServiceImpl add ... ... "); System.out.println(sname); System.out.println(sgender); System.out.println(sage); userDao.add(); } }
4. 代理模式
1. 介绍
- 代理模式:是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
例如:房产中介代替业主卖房:
2. 静态代理
-
静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。
package com.msb.test; /** * @Author: bingwoo */ public class Test1 { public static void main(String[] args) { Person person =new Person("张三"); Court court=new Lawyer(person); court.doCourt(); } } // 接口 interface Court{ void doCourt(); } // 代理类 class Lawyer implements Court{ private Person person; public Lawyer(Person person) { this.person = person; } @Override public void doCourt() { System.out.println("律师取证:视频证明张三当时正在旅游,不在案发现场"); System.out.println("律师总结:张三不可能去杀人"); person.doCourt(); } } // 被代理的类 class Person implements Court{ private String name; public Person(String name) { this.name = name; } @Override public void doCourt() { System.out.println(name+"说:我没有杀人"); } }
3. 动态代理
- 动态代理可以针对于一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的
-
- Proxy 动态代理 JDK动态代理 面向接口
-
- cglib 动态代理 第三方动态代理 面向父类
1. Proxy 动态代理
package com.msb.testProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @Author: bingwoo
*/
public class Test1 {
public static void main(String[] args) {
Dinner dinner=new Person("张三");
// 通过Porxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强
//ClassLoader loader,被代理的对象的类加载器
ClassLoader classLoader = dinner.getClass().getClassLoader();
//Class<?>[] interfaces,被代理对象所实现的所有接口
Class[] interaces= dinner.getClass().getInterfaces();
//InvocationHandler h,执行处理器对象,专门用于定义增强的规则
InvocationHandler handler = new InvocationHandler(){
// invoke 当我们让代理对象调用任何方法时,都会触发invoke方法的执行
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Object proxy, 代理对象
//Method method,被代理的方法
//Object[] args,被代理方法运行时的实参
Object res = null;
if(method.getName().equals("eat")){
System.out.println("饭前洗手");
// 让原有的eat的方法去运行
res =method.invoke(dinner, args);
System.out.println("饭后刷碗");
}else{
// 如果是其他方法,那么正常执行就可以了
res =method.invoke(dinner, args);
}
return res;
}
};
Dinner dinnerProxy =(Dinner) Proxy.newProxyInstance(classLoader,interaces,handler);
//dinnerProxy.eat("包子");
dinnerProxy.drink();
}
}
interface Dinner{
void eat(String foodName);
void drink();
}
class Person implements Dinner{
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void eat(String foodName) {
System.out.println(name+"正在吃"+foodName);
}
@Override
public void drink( ) {
System.out.println(name+"正在喝茶");
}
}
class Student implements Dinner{
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void eat(String foodName) {
System.out.println(name+"正在食堂吃"+foodName);
}
@Override
public void drink( ) {
System.out.println(name+"正在喝可乐");
}
}
- 使用代理技术 获得代理对象 代替张三 增强打官司的方法
2. Cglib 动态代理
- 面向父类和接口没有直接关系
- 不仅仅可以增强接口定义中的方法,还可以增强一个其他的类
- 可以读取父类中方法的所有注解
-
代码示例:
package com.msb.testCglib; import org.junit.Test; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Author: bingwoo */ public class Test1 { @Test public void testCglib(){ Person person =new Person(); // 获取一个Person的代理对象 // 1 获得一个Enhancer对象 Enhancer enhancer=new Enhancer(); // 2 设置父类字节码 enhancer.setSuperclass(person.getClass()); // 3 获取MethodIntercepter对象 用于定义增强规则 MethodInterceptor methodInterceptor=new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { /*Object o, 生成之后的代理对象 personProxy Method method, 父类中原本要执行的方法 Person>>> eat() Object[] objects, 方法在调用时传入的实参数组 MethodProxy methodProxy 子类中重写父类的方法 personProxy >>> eat() */ Object res =null; if(method.getName().equals("eat")){ // 如果是eat方法 则增强并运行 System.out.println("饭前洗手"); res=methodProxy.invokeSuper(o,objects); System.out.println("饭后刷碗"); }else{ // 如果是其他方法 不增强运行 res=methodProxy.invokeSuper(o,objects); // 子类对象方法在执行,默认会调用父类对应被重写的方法 } return res; } }; // 4 设置methodInterceptor enhancer.setCallback(methodInterceptor); // 5 获得代理对象 Person personProxy = (Person)enhancer.create(); // 6 使用代理对象完成功能 personProxy.eat("包子"); } } class Person { public Person( ) { } public void eat(String foodName) { System.out.println("张三正在吃"+foodName); } }
4. 代理模式总结
- 在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能,进而达到拓展功能的目的。
- JDK Proxy 动态代理面向接口的动态代理 一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法 。
- 生成的代理对象只能转换成 接口的不能转换成 被代理类。
- 代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的。
- 代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解。
七、AOP 面向切面编程
1. 简介
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
- AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2. 主要功能
- 日志记录,性能统计,安全控制,事务处理,异常处理等等。
3. 主要意图
- 将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
4. 实现
-
AOP的实现原理其实就是动态代理,使用JDK动态代理。没有接口的情况下使用cglib动态代理。
-
为Dao层所有的add方法添加一个性能记录功能
5. AOP中的术语辨析
- 连接点 Joint point:类里面那些可以被增强的方法,这些方法称之为连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 - 切入点 Pointcut:实际被增强的方法,称之为切入点,表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- 通知 Advice:实际增强的逻辑部分称为通知 (增加的功能),Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。通知类型: 1 前置通知 2 后置通知 3 环绕通知 4 异常通知 5 最终通知。
- 目标对象 Target:被增强功能的对象(被代理的对象):织入 Advice 的目标对象。
- 切面Aspect: 表现为功能相关的一些advice方法放在一起声明成的一个Java类Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- 织入 Weaving:创建代理对象并实现功能增强的声明并运行过程,将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
6. AOP注解方式实现
1. 注解实现
- 本身并不是spring框架中的组成部分, 是一个独立的AOP框架,一般把AspectJ和Spring框架的AOP依赖一起使用,所以要导入一个独立的依赖,实现的两种方式:
- 基于注解方式实现 (熟练)
- 基于XML配置方式 (了解)
-
导入依赖
<dependencies> <!--spring核心容器包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.5</version> </dependency> <!--spring切面包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.5</version> </dependency> <!--织入包 spring-aspects 已经导入该包,这里可以不导入--> <!--<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>--> <!--aop联盟包--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!--Apache Commons日志包--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!--德鲁伊连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!--Junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> </dependencies>
-
切入点表达式:
- 通过一个表达式来确定AOP要增强的是哪个或者那些方法
- 语法结构:execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )
- 示例:
execution(* com.msb.dao.UserDaoImpl.add(..)) //指定切点为UserDaoImpl.add方法 execution(* com.msb.dao.UserDaoImpl.*(..)) //指定切点为UserDaoImpl.所有的方法 execution(* com.msb.dao.*.*(..)) //指定切点为dao包下所有的类中的所有的方法 execution(* com.msb.dao.*.add(..)) //指定切点为dao包下所有的类中的add的方法 execution(* com.msb.dao.*.add*(..)) //指定切点为dao包下所有的类中的add开头的方法
-
开启注解扫描和AOP切面编程自动生成代理对象配置
<?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" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!--spring 包扫描 --> <context:component-scan base-package="com.msb"/> <!--aop autoProxy 自动生成代理对象 --> <aop:aspectj-autoproxy/> </beans>
-
准备接口:UserDao和EmpDao
package com.msb.dao; /** * @Author: bingwoo */ public interface EmpDao { int addEmp(Integer empno,String ename,String job); }
package com.msb.dao; /** * @Author: bingwoo */ public interface UserDao { int addUser(Integer userid,String username); }
-
接口实现
package com.msb.dao.impl; import com.msb.dao.UserDao; import org.springframework.stereotype.Repository; /** * @Author: bingwoo */ @Repository public class UserDaoImpl implements UserDao { public int addUser(Integer userid,String username){ System.out.println("userdao add ... ..."); //int i =1/0; return 1; } }
package com.msb.dao.impl; import com.msb.dao.EmpDao; import com.msb.dao.UserDao; import org.springframework.stereotype.Repository; /** * @Author: bingwoo */ @Repository public class EmpDaoImpl implements EmpDao { public int addEmp(Integer empno,String ename,String job){ System.out.println("empDao add ... ..."); return 1; } }
-
准备切面
package com.msb.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @Author: bingwoo */ @Component @Aspect public class DaoAspect { //定义公共切点 @Pointcut("execution(* com.msb.dao.*.add*(..))") public void addPointCut(){} /* * 前置通知: 切点方法执行之前先执行的功能 * 参数列表可以用JoinPoint接收切点对象 * 可以获取方法执行的参数 * */ @Before("addPointCut()") public void methodBefore(JoinPoint joinPoint){ System.out.println("Before invoked"); } /* * 后置通知:方法执行之后要增强的功能 * 无论切点方法是否出现异常都会执行的方法 * 参数列表可以用JoinPoint接收切点对象 * */ @After("addPointCut()") public void methodAfter(JoinPoint joinPoint){ System.out.println("After invoked"); } /* * 返回通知:切点方法正常运行结束后增强的功能 * 如果方法运行过程中出现异常,则该功能不运行 * 参数列表可以用 JoinPoint joinPoint接收切点对象 * 可以用Object res接收方法返回值,需要用returning指定返回值名称 * */ @AfterReturning( value = "addPointCut()",returning = "res") public void methodAfterReturning(JoinPoint joinPoint,Object res){ System.out.println("AfterReturning invoked"); } /* * 异常通知:切点方法出现异常时运行的增强功能 * 如果方法运行没有出现异常,则该功能不运行 * 参数列表可以用Exception ex接收异常对象 需要通过throwing指定异常名称 * */ @AfterThrowing( value = "addPointCut()",throwing = "ex") public void methodAfterThrowing(Exception ex){ System.out.println("AfterThrowing invoked"); } /*环绕通知:在切点方法之前和之后都进行功能的增强 * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能 * 方法列表可以通过ProceedingJoinPoint获取执行的切点 * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置 * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理 * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值 * */ @Around("addPointCut()") public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("aroundA invoked"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("aroundB invoked"); return proceed; } }
-
测试代码
@Test public void test1(){ ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = context.getBean( UserDao.class); int add = userDao.addUser(10,"晓明"); }
- 有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级
- 数字越小,优先级越高
- 数字越小,其代理位置越靠近注入位置
@Component @Aspect @Order(1) public class userTest{ } @Component @Aspect @Order(2) public class userTest{ }
- 全注解开发
-
示例代码
package com.msb.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @Author: bingwoo */ @Configuration @ComponentScan(basePackages = "com.msb") @EnableAspectJAutoProxy(proxyTargetClass = true) public class SpringConfig { }
-
测试代码
@Test public void test2(){ ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class); UserDao userDao = context.getBean( UserDao.class); int add = userDao.addUser(10,"晓明"); }
2. XML配置方式实现
-
创建两个类,增强类和被增强类,创建方法
-
在spring配置文件中创建两个类对象
<!--创建对象--> <bean id="userDao" class="com.com.msb.UserDaoImpl"></bean> <bean id="daoAspect" class="com.com.aspect.DaoAspect"></bean>
-
在spring配置文件中配置切入点
<!--配置aop增强--> <aop:config> <!--切入点--> <aop:pointcut id="pointCutAdd" expression="execution(* com.msb.dao.UserDao.add*(..))"/> <!--配置切面--> <aop:aspect ref="daoAspect"> <!--增强作用在具体的方法上--> <aop:before method="methodBefore" pointcut-ref="pointCutAdd"/> <aop:after method="methodAfter" pointcut-ref="pointCutAdd"/> <aop:around method="methodAround" pointcut-ref="pointCutAdd"/> <aop:after-returning method="methodAfterReturning" pointcut-ref="pointCutAdd" returning="res"/> <aop:after-throwing method="methodAfterThrowing" pointcut-ref="pointCutAdd" throwing="ex"/> </aop:aspect> </aop:config>
八、JdbcTemplate
1. JdbcTemplate介绍
- JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。
2. JdbcTemplate实现
1. 导入包
<dependencies>
<!--spring核心容器包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring切面包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.5</version>
</dependency>
<!--aop联盟包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--springJDBC包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring事务控制包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring orm 映射依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.5</version>
</dependency>
<!--Apache Commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--Junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
2. 准备JDBC.properties
jdbc_username=root
jdbc_password1=root
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
3. 准备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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--spring 注解扫描-->
<context:component-scan base-package="com.msb"/>
<!--读取jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"></property>
<property name="password1" value="${jdbc_password1}"></property>
<property name="url" value="${jdbc_url}"></property>
<property name="driverClassName" value="${jdbc_driver}"></property>
</bean>
<!--配置JDBCTemplate对象,并向里面注入DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--通过set方法注入连接池-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
4. 准备实体类
package com.msb.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @Author: bingwoo
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Emp implements Serializable{
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
}
5. 准备service层接口和实现类
package com.msb.service;
import com.msb.pojo.Emp;
import java.util.List;
/**
* @Author: bingwoo
*/
public interface EmpService {
int findEmpCount();
Emp findByEmpno(int empno);
List<Emp> findByDeptno(int deptno);
int addEmp(Emp emp);
int updateEmp(Emp emp);
int deleteEmp( int empno);
}
package com.msb.service.impl;
import com.msb.dao.EmpDao;
import com.msb.pojo.Emp;
import com.msb.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* bingwoo
*/
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpDao empDao;
@Override
public int findEmpCount() {
return empDao.findEmpCount();
}
@Override
public Emp findByEmpno(int empno) {
return empDao.findByEmpno( empno);
}
@Override
public List<Emp> findByDeptno(int deptno) {
return empDao.findByDeptno( deptno);
}
@Override
public int addEmp(Emp emp) {
return empDao.addEmp(emp);
}
@Override
public int updateEmp(Emp emp) {
return empDao.updateEmp(emp);
}
@Override
public int deleteEmp(int empno) {
return empDao.deleteEmp(empno);
}
}
6. 准备dao层接口和实现类
package com.msb.dao;
import com.msb.pojo.Emp;
import java.util.List;
/**
* @Author: bingwoo
*/
public interface EmpDao {
int findEmpCount();
Emp findByEmpno(int empno);
List<Emp> findByDeptno(int deptno);
int addEmp(Emp emp);
int updateEmp(Emp emp);
int deleteEmp(int empno);
}
package com.msb.dao.impl;
import com.msb.dao.EmpDao;
import com.msb.pojo.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @Author: bingwoo
*/
@Repository
public class EmpDaoImpl implements EmpDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findEmpCount() {
/*查询员工个数
* queryForObject 两个参数
* 1 SQL语句
* 2 返回值类型
*
* */
Integer empCount = jdbcTemplate.queryForObject("select count(1) from emp", Integer.class);
return empCount;
}
@Override
public Emp findByEmpno(int empno) {
/*
* 查询单个员工对象
* queryForObject三个参数
* 1 SQL语句
* 2 RowMapper接口的实现类对象,用于执行返回的结果用哪个类来进行封装 ,实现类为BeanPropertyRowMapper
* 3 SQL语句中需要的参数 (可变参数)
* */
BeanPropertyRowMapper<Emp> rowMapper =new BeanPropertyRowMapper<>(Emp.class);
Emp emp = jdbcTemplate.queryForObject("select * from emp where empno =?", rowMapper, empno);
return emp;
}
@Override
public List<Emp> findByDeptno(int deptno) {
/*
* 查询单个员工对象
* query三个参数
* 1 SQL语句
* 2 RowMapper接口的实现类对象,用于执行返回的结果用哪个类来进行封装 ,实现类为BeanPropertyRowMapper
* 3 SQL语句中需要的参数 (可变参数)
* */
BeanPropertyRowMapper<Emp> rowMapper =new BeanPropertyRowMapper<>(Emp.class);
List<Emp> emps = jdbcTemplate.query("select * from emp where deptno =?", rowMapper, deptno);
return emps;
}
@Override
public int addEmp(Emp emp) {
/*增删改
* 统统用update方法 两个参数
* 1 SQL语句
* 2 SQL语句需要的参数 (可变参数)
*
* */
String sql ="insert into emp values(DEFAULT ,?,?,?,?,?,?,?)";
Object[] args ={emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno()};
return jdbcTemplate.update(sql,args);
}
@Override
public int updateEmp(Emp emp) {
String sql ="update emp set ename =? , job =?, mgr=? , hiredate =?, sal=?, comm=?, deptno =? where empno =?";
Object[] args ={emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno(),emp.getEmpno()};
return jdbcTemplate.update(sql,args);
}
@Override
public int deleteEmp(int empno) {
String sql ="delete from emp where empno =?";
return jdbcTemplate.update(sql, empno);
}
}
7. 测试代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Date;
import java.util.List;
/**
* @Author: bingwoo
*/
public class Test1 {
@Test
public void testEmpService(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
EmpService empService = context.getBean(EmpService.class);
// 查询员工个数
int empCount = empService.findEmpCount();
System.out.println(empCount);
// 根据员工编号查询员工对象
Emp byEmpno = empService.findByEmpno(7521);
System.out.println(byEmpno);
/*根据部门编号查询多个员工对象集合*/
List<Emp> emps = empService.findByDeptno(20);
emps.forEach(System.out::println);
/*增加员工信息*/
int rows = empService.addEmp(new Emp(null, "TOM", "SALESMAN", 13, new Date(), 2000.0, 100.0, 10));
System.out.println(rows);
/*根据员工编号修改员工信息*/
int rows = empService.updateEmp(new Emp(12, "JERRY", "MANAGER", 7839, new Date(), 3000.0, 0.0, 20));
System.out.println(rows);
/*根据员工编号删除员工信息*/
int rows = empService.deleteEmp(7939);
System.out.println(rows);
}
}
3. 批量实现(增删改查)
1. 实体类
package com.msb.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: bingwoo
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private String loc;
}
2. service
package com.msb.service;
import com.msb.pojo.Dept;
import java.util.List;
/**
* @Author: bingwoo
*/
public interface DeptService {
int[] deptBatchAdd(List<Dept> depts);
int[] deptBatchUpdate(List<Dept> depts);
int[] deptBatchDelete(List<Integer> deptnos);
}
package com.msb.service.impl;
import com.msb.dao.DeptDao;
import com.msb.pojo.Dept;
import com.msb.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: bingwoo
*/
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;
@Override
public int[] deptBatchAdd(List<Dept> depts) {
return deptDao.deptBatchAdd(depts);
}
@Override
public int[] deptBatchUpdate(List<Dept> depts) {
return deptDao.deptBatchUpdate(depts);
}
@Override
public int[] deptBatchDelete(List<Integer> deptnos) {
return deptDao.deptBatchDelete(deptnos);
}
}
3. dao
package com.msb.dao;
import com.msb.pojo.Dept;
import java.util.List;
/**
* @Author: bingwoo
*/
public interface DeptDao {
int[] deptBatchAdd(List<Dept> depts);
int[] deptBatchUpdate(List<Dept> depts);
int[] deptBatchDelete(List<Integer> deptnos);
}
package com.msb.dao.impl;
import com.msb.dao.DeptDao;
import com.msb.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: bingwoo
*/
@Repository
public class DeptDaoImpl implements DeptDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int[] deptBatchAdd(List<Dept> depts) {
String sql ="insert into dept values(DEFAULT,?,?)";
List<Object[]> args =new LinkedList<>();
for (Dept dept : depts) {
Object[] arg ={dept.getDname(),dept.getLoc()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
@Override
public int[] deptBatchUpdate(List<Dept> depts) {
String sql ="update dept set dname =? ,loc =? where deptno=?";
List<Object[]> args =new LinkedList<>();
for (Dept dept : depts) {
Object[] arg ={dept.getDname(),dept.getLoc(),dept.getDeptno()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
@Override
public int[] deptBatchDelete(List<Integer> deptnos) {
String sql ="delete from dept where deptno =?";
List<Object[]> args =new LinkedList<>();
for (Integer deptno : deptnos) {
Object[] arg ={deptno};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
}
4. 测试代码
package com.msb.test;
import com.msb.pojo.Dept;
import com.msb.service.DeptService;
import com.msb.service.EmpService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author: bingwoo
*/
public class Test2 {
@Test
public void testBatchAdd(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
DeptService deptService = context.getBean(DeptService.class);
List<Dept> depts =new ArrayList<>();
for (int i = 0; i < 10; i++) {
depts.add(new Dept(null,"name"+i,"loc"+i));
}
int[] ints = deptService.deptBatchAdd(depts);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatchUpdate(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
DeptService deptService = context.getBean(DeptService.class);
List<Dept> depts =new ArrayList<>();
for (int i = 51; i <=60; i++) {
depts.add(new Dept(i,"newname","newLoc"));
}
int[] ints = deptService.deptBatchUpdate(depts);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatchDelete(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
DeptService deptService = context.getBean(DeptService.class);
List<Integer> deptnos =new ArrayList<>();
for (int i = 51; i <=69; i++) {
deptnos.add(i);
}
int[] ints = deptService.deptBatchDelete(deptnos);
System.out.println(Arrays.toString(ints));
}
}
九、事务
- spring中可以使用如下方式实现事务的控制
- 编程式(不推荐)
- 声明式(掌握)
- 注解(简单,必会)
- XML配置(繁琐,了解)
1. 事务概念
- 事务(Transaction)指的是一个操作序列,该操作序列中的多个操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
- 目前常用的存储引擎有InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM(MySQL5.5之前默认的存储引擎),其中InnoDB支持事务处理机制,而MyISAM不支持。
2. 事务特性
- 事务处理可以确保除非事务性序列内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的序列,可以简化错误恢复并使应用程序更加可靠。
- 但并不是所有的操作序列都可以称为事务,这是因为一个操作序列要成为事务,必须满足事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这四个特性简称为ACID特性。
1. 原子性
- 原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体。
- 使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行。通常,某个事务中的操作都具有共同的目标,并且是相互依赖的。如果数据库系统只执行这些操作中的一部分,则可能会破坏事务的总体目标,而原子性消除了系统只处理部分操作的可能性。
2. 一致性
- 一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的。
- 例如:在转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配。
3. 隔离性
- 隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。也就是说:并发执行的事务之间既不能看到对方的中间状态,也不能相互影响。
- 例如:在转账时,只有当A账户中的转出和B账户中转入操作都执行成功后才能看到A账户中的金额减少以及B账户中的金额增多。并且其他的事务对于转账操作的事务是不能产生任何影响的。
4. 持久性
- 持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。但如果是由于外部原因导致的数据库故障,如硬盘被损坏,那么之前提交的数据则有可能会丢失。
3. 事务并发
1. 脏读(Dirty read)
- 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
2. 不可重复读(Unrepeatableread)
- 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
3.幻读(Phantom read)
- 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
4. 不可重复度和幻读区别
- 不可重复读的重点是修改,幻读的重点在于新增或者删除。
- 解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
- 例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
- 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读
5. 事务隔离级别
- 事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题。
- 事务的隔离级别从低到高依次为READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ以及SERIALIZABLE,隔离级别越低,越能支持高并发的数据库操作。
4. 注解方式
1. 在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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--spring 注解扫描-->
<context:component-scan base-package="com.msb"/>
<!--读取jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"></property>
<property name="password1" value="${jdbc_password1}"></property>
<property name="url" value="${jdbc_url}"></property>
<property name="driverClassName" value="${jdbc_driver}"></property>
</bean>
<!--配置JDBCTemplate对象,并向里面注入DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--通过set方法注入连接池-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--将数据源注入事务管理器-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2.在Service层中添加事务的注解
package com.msb.service.impl;
import com.msb.dao.AccountDao;
import com.msb.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author: bingwoo
*/
@Service
//@Transactional //加在类上,代表类中的所有方法都添加了事务控制
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional// 放在方法上,就是仅仅对当前方法增加了事务控制
public int transMoney(int from, int to, int money) {
int rows=0;
rows+=accountDao.transMoney(from, 0 - money);
int i =1/0;
rows+=accountDao.transMoney(to, money);
return rows;
}
}
3. @Transactional 注解
-
@Transactional 注解的一些参数和参数的含义
-
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_UNCOMMITTED,readOnly = true,rollbackFor = ClassCastException.class,noRollbackFor = NullPointerException.class,timeout = 10)
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; String[] label() default {}; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; String timeoutString() default ""; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
-
propagation 事务的传播行为(面试)
-
多事务方法之间调用,事务是如何管理的
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择(默认)。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
-
如果service层 add方法调用了 addDept和addEmp两个方法
-
PROPAGATION_REQUIRED
- 如果add方法有事务,那么addDept和addEmp就加入到add方法里的事务
- 如果add方法没有事务,那么就新建一个事务,将addDept和addEmp加入到这个新的事务中
-
PROPAGATION_REQUIRES_NEW
- 无论add是否有事务,都建立一个新的事务,所有的方法都加入到新的事务中,add原来的事务就不用了
4. isolation 事务的隔离级别
-
DEFAULT (默认):这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。MySQL默认REPEATABLE_READ,Oracle默认READ_COMMITTED。
-
READ_UNCOMMITTED (读未提交) :这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
-
READ_COMMITTED (读已提交) :保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
-
REPEATABLE_READ (可重复读) :这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
-
SERIALIZABLE(串行化) :这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。
-
timeout 超时时间:事务一定要在多长时间之内提交,如果不提交就会回滚
-
readOnly 只读事务:事务是否只能读取数据库的数据,如果为true,则不允许进行增删改
-
rollbackFor 指定发生回滚的异常:当方法发生哪些异常时才会回滚
-
noRollbackFor 指定不发生回滚的异常:当方法发生哪些异常时,不会回滚
5. xml 方式配置
- applicationContext中,通过AOP实现事务的控制
<?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"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.msb"/>
<!--读取jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"></property>
<property name="password1" value="${jdbc_password1}"></property>
<property name="url" value="${jdbc_url}"></property>
<property name="driverClassName" value="${jdbc_driver}"></property>
</bean>
<!--配置JDBCTemplate对象,并向里面注入DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--通过set方法注入连接池-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--将数据源注入事务管理器-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="txAdvice">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="transMoney" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.msb.service.AccountService.transMoney(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
总结
Spring 框架整理知识点,其中主要的知识点在于IOC 控制反转和AOP切面编程,Beand的生命周期,事务的特性及隔离级别。这些都是最基础,而且必须要知道的。这里把必须掌握的知识点汇总一下,方便以后查阅。如发现问题请留言指正,我会及时更新,期待与大家共同进步!