Spring 总结(学习+面试)

2023-05-16

Spring学习笔记

文章目录

    • Spring学习笔记
      • 1.0.概述
          • 1.1使用Spring框架的好处
          • 1.2特点
      • 2.0体系结构
      • 3.0创建Spring-java工程(eclipse)
      • 4.0通过Spring创建对象的方式
      • 5.0 由Spring管理的对象的作用域与生命周期
      • 6.0 Spring IoC
        • 6.1 IOC原理
        • 6.2. 通过SET方式注入属性的值
        • 6.3. 通过构造方法注入属性的值
      • 7.0. 注入集合类型的属性值
        • 7.1. List
        • 7.2. Set
        • 7.3. 数组
        • 7.4. Map
        • 7.5. Properties
      • 8. Spring表达式
      • 9. 自动装配(autowire)
      • ----------------------------
      • 附1:List与Set
      • 附2:内存溢出/内存泄漏/Leak
      • 10. SpringIOC注解
        • 10.1. 通用注解
        • 10.2. 关于作用域与生命周期的注解
      • 11. 自动装配
      • 12. SpringAop
        • 12.1 原理是什么
        • 12.2 aop名词有什么
        • 12.3 Spring切面可以应用5种类型的通知Advice
        • 12.4 配置aop
        • 12.5 aop机制,实现,具体怎样使用,具体到标签?
        • 12.6 代理模式
          • 12.6.1静态代理
          • 12.6.2JDK动态代理
          • 12.6.3cglib动态代理
          • 12.6.4JDK和cglib代理的区别
      • 13. spring 事务
        • 13.1 事务特性(ACID)
        • 13.2 Spring事务管理方式
        • 13.3 事务实现方式(声明式)
        • 13.4 演示
          • (1)编程式事务管理
          • (2)基于AspectJ的声明式事务管理
          • (3)基于注解的方式
        • 13.5 事务属性
          • (1)事务隔离级别(定义了一个事务可能受其他并发事务影响的程度):
          • (2)事务传播行为(为了解决业务层方法之间互相调用的事务问题):
          • (3) 事务超时属性(一个事务允许执行的最长时间)
          • (4) 事务只读属性(对事物资源是否执行只读操作)
          • (5) 回滚规则(定义事务回滚规则)

1.0.概述

Spring框架是一个开源的JAV平台,它最初是由Rod Johnson编写,2003年6月在Apache 2.0 许可下发布。

1.1使用Spring框架的好处
  • 方便解耦,简化开发

通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。

  • AOP编程的支持

通过Spring提供的AOP功能,方便进行面向切面的编程,如性能监测、事务管理、日志记录等。

  • 声明式事务的支持
  • 方便集成各种优秀框架
1.2特点
  • 控制反转 IOC(Inversion of Control)

所谓的控制反转,就是指将对象的创建,对象的存储(map),对象的管理(依赖查找,依赖注入)交给了spring容器。

  • 依赖注入 DI (Dependency Injection)

Spring 最认同的技术是控制反转的依赖注入(DI)Dependency Injection模式。控制反转(IoC)Inversion of Control 是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。Spring框架通过DI实现了IoC,DI是实现手段,而IoC是实现的目标。
依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。

  • 面向方面的程序设计(AOP)
    是将纵向重复的代码,横向抽取出来。事务管理、安全检查、缓存、权限管理等

2.0体系结构

3.0创建Spring-java工程(eclipse)

创建Maven Project,创建时勾选Create a simple projectGroup Id输入cn.fyy.springArtifact Id输入SPRING-01(本应该也是包名的一部分,但是,暂且作为项目名),Packaging选择war(也可以是jar,后续实际使用的其实都会是war)。

创建出来的项目默认没有web.xml文件,作为WEB项目来说是错误的,所以,需要先生成该文件。

然后,需要添加所以依赖的框架,在pom.xml中添加<dependencies>节点,然后,在子级中添加spring-webmvc的依赖(其实,目前只要使用spring-context依赖,而spring-webmvc中包含该依赖,后续学习SpringMVC框架时就必须使用spring-webmvc):

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>4.3.8.RELEASE</version>
</dependency>

目前,对使用的Spring的要求是不低于4.2版本

当项目创建完成后,将文件applicationContext.xml复制到项目中的src/main/resources中。

假设希望通过Spring创建java.util.Date类的对象,则应该在applicationContext.xml中添加配置:

<!-- id:当从Spring容器中获取该对象时使用的名称 -->
<!-- class:需要创建的类 -->
<bean id="date" class="java.util.Date"></bean>

接下来,可以通过单元测试查看配置效果,需要先添加单元测试的依赖:

public class TestCase {

	@Test
	public void getDate() {
		// 1. 加载Spring配置文件,获得Spring容器
		ClassPathXmlApplicationContext ac
			= new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		
		// 2. 从Spring容器中获取对象
		Date date = (Date) ac.getBean("date");
		
		// 3. 测试所获取的对象
		System.out.println(date);
		
		// 4. 关闭Spring容器,释放资源
		ac.close();
	}
	
}

4.0通过Spring创建对象的方式

(a) 类中存在无参数的构造方法(实用,掌握)

java.util.Date类为例,当Spring创建对象时,会自动调用其无参数构造方法。

在Spring的配置文件中,配置方式为:

<bean id="date" class="java.util.Date"></bean>

如果某个类中并不存在无参数的构造方法,则不可以通过这种方式进行配置。

(b) 类中存在静态工厂方法(不实用,了解)

如果某个类中存在某个static修饰的方法,且方法的返回值类型就是当前类的类型,例如java.util.Calendar类就是如此:

public abstract class Calendar {
	public static Calendar getInstance() {
		// ...
	}
}	

符合这种条件的类,可以配置为:

<!-- (b) 类中存在静态工厂方法 -->
<!-- factory-method:工厂方法的名称 -->
<bean id="calendar" 
	class="java.util.Calendar"
	factory-method="getInstance">
</bean>

© 存在实例工厂方法(更不实用,了解)

在其他类中,存在某个工厂方法,可以创建指定的类的对象,例如:

public class Phone {
	public Phone(String name) {
	}
}

public class PhoneFactory {
	public Phone newInstance() {
		return new Phone("HuaWei");
	}
}

当需要Spring创建Phone对象时,可以让Spring先创建PhoneFactory的对象(实例),然后调用该对象的newInstance()方法,即可获取Phone的对象,从而完成对象的创建过程。

具体配置为:

<!-- (c) 存在实例工厂方法 -->
<!-- factory-bean:配置为工厂类的bean-id -->
<bean id="phoneFactory" 
	class="cn.fyy.spring.PhoneFactory">
</bean>
<bean id="phone"
	class="cn.fyy.spring.Phone"
	factory-bean="phoneFactory"
	factory-method="newInstance">
</bean>

5.0 由Spring管理的对象的作用域与生命周期

(a) 由Spring管理的对象的作用域

单例:单一实例,在某个时刻,可以获取到的同一个类的对象将有且仅有1个,如果反复获取,并不会得到多个实例。

单例是一种设计模式,其代码格式可以是:

public class King {
	private static King king = new King();

	private King() {
	}

	public static King getInstance() {
		return king;
	}
}

以上单例模式也称之为“饿汉式单例模式”,其工作特性为“程序运行初期就已经创建了该类的实例,随时获取实例时,都可以直接获取”,还有另外一种单例模式是“懒汉式单例模式”,其工作特性为“不到逼不得已不会创建类的对象”,其代码是:

public class King {
	private static final Object LOCK = new Object();

	private static King king;

	private King() {
	}

	public static King getInstance() {
		if (king == null) {
			synchronized (LOCK) {
				if (king == null) {
					king = new King();
				}
			}
		}
		return king;
	}
}

理论上来说,懒汉式的单例模式可能可以节省一部分性能消耗,例如整个系统运行了30分钟之后才获取对象的话,则前30分钟是不需要创建对象的!但是,这种优势只是理论上的优势,在实际应用时,2种单例模式的差异表现的并不明显!

通过以上单例模式的设计方式,可以发现单例的对象都是使用了static修饰的!所以,具有“唯一、常驻”的特性!

在Spring管理对象时,可以配置lazy-init属性,以配置是否为懒汉式的单例模式:

<!-- lazy-init:是否懒加载,取值为true或false(默认) -->
<bean id="user" 
	class="cn.fyy.spring.User"
	lazy-init="true">
</bean>

在Spring管理对象时,可以配置scope属性,以配置类的对象是否是单例的:

<!-- scope:是否单例,取值为singleton(默认,单例)或prototype(非单例) -->
<bean id="user" 
	class="cn.fyy.spring.User"
	scope="prototype">
</bean>

Ps:

scope属性:

​ (1)singleton 默认值
​ 单例对象 :被标识为单例的对象在spring容器中只会存在一个实例
​ (2)prototype
​ 多例原型:被标识为多例的对象,每次在获得才会被创建,每次创建都是新的对象
​ (3)request
​ Web环境下,对象与request生命周期一致
​ (4)session
Web环境下,对象与session生命周期一致

​ (5)global session

​ 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。

总结:绝大多数情况下,使用单例singleton(默认值),但是在与struts整合时候,务必要用prototype多例,因为struts2在每次请求都会创建一个新的Action,若为单例,在多请求情况下,每个请求找找spring拿的都是同一个action。

(b) 由Spring管理的对象的生命周期

生命周期描述了某个对象从创建到销毁的整个历程,在这个历程中,会被调用某些方法,这些方法就是生命周期方法,学习生命周期的意义在于了解这些方法的调用特征,例如何时调用、调用次数,以至于开发功能时,可以将正确的代码重写在正确的方法中!

以Servlet为例,其生命周期方法主要有init()service()destroy(),其中,init()destroy()都是只执行1次的方法,而service()在每次接收到请求时都会被调用,且init()方法是创建对象之后就会执行的方法,destroy()是销毁对象的前一刻执行的方法,所以,如果有某个流对象需要初始化,初始化的代码写在destroy()方法中就是不合适的!反之,如果要销毁流对象,也不能把代码写在init()中!

由Spring管理的单例对象也并不是开发人员完全可控的,即不知道何时创建的,何时销毁的,所以,Spring提供了“允许自定义初始化方法和销毁方法”的做法,开发人员可以自行定义2个方法分别表示初始化方法和销毁方法:

public class User {

	public User() {
		super();
		System.out.println("创建User对象……");
	}
	
	public void init() {
		System.out.println("初始化方法被调用……");
	}
	
	public void destroy() {
		System.out.println("销毁方法被调用……");
	}
	
}

配置:

<!-- init-method:初始化方法的名称 -->
<!-- destroy-method:销毁方法的名称 -->
<bean id="user" 
	class="cn.fyy.spring.User"
	init-method="init"
	destroy-method="destroy">
</bean>

注意:生命周期方法的配置是建立在“单例”基础之上的,如果对象并不是单例的,讨论生命周期就没有意义了。

6.0 Spring IoC

IoC:Inversion of Control,即“控制反转”,在传统的开发模式中,创建对象和管理对象都是由开发人员自行编写代码来完成的,可以理解为“开发人员拥有控制权”,在使用了Spring之后,开发人员需要做的就只是完成相关配置即可,具体的创建过程和管理方式都是由Spring框架去实现的,可以理解为“把控制权交给了框架”,所以,称之为“控制反转”。

管理对象最重要的是“配置某个对象的属性”,假设User类中有public String name;属性,甚至希望获取User对象的同时,name属性已经是有值的了,Spring在处理属性值的时候,采取了DI(Dependency Injection,依赖注入)做法。

关于DI和IoC的关系:Spring框架通过DI实现了IoC,DI是实现手段,而IoC是实现的目标。

示例:

public class User {
	public String name;
}

如果需要为name属性注入值,首先,需要为name属性添加SET方法:

public class User {

	public String name;

	public void setName(String name) {
		this.name = name;
	}

}

然后,在Spring的配置文件中添加配置:

<bean id="user" 
	class="cn.fyy.spring.User"">
	<!-- name:为哪个属性注入值 -->
	<!-- value:注入的值 -->
	<property name="name" value="Java"></property>
</bean>

其实,在配置<property>节点的name属性,取值应该是“需要注入值的属性对应的SET方法的方法名去除set字样并把首字母改为小写后得到的名称”,因为Spring在处理时,会将配置的值name的首字母改为大写,得到Name,然后在左侧拼接set,以得到setName这个名称,然后,尝试调用名为setName的方法!

由于Eclipse等开发工具生成SET方法的模式与Spring得到SET方法的模式是完全相同的,所以,可以粗略的理解为<property>节点的name属性值就是需要注入的属性名称!

6.1 IOC原理

(1).xml配置文件

(2).dom4j 解析xml

(3).工厂设计模式

(4).反射==>Class.forName(classValue).newIntstance()

在这里插入图片描述

6.2. 通过SET方式注入属性的值

假设存在:

public class UserDao {
	
	public String driver; // com.mysql.jdbc.Driver

}

如果需要为driver注入值,首先需要为该属性添加SET方法(强烈建议使用开发工具生成SET方法,不要手动编写):

public void setDriver(String driver) {
	this.driver = driver;
}

然后,在Spring的配置文件中,添加<property>节点以进行配置:

<bean id="userDao" class="cn.fyy.spring.UserDao">
	<property name="driver" value="com.mysql.jdbc.Driver" />
</bean>

另外,如果某个属性的值并不是基本值(在Spring中,把基本数据类型的值和字符串统一称为基本值),例如:

public class UserServlet {
	public UserDao userDao;
}

以上属性的值应该是前序创建的UserDao类的对象,则,在配置时,可以通过<property>节点的ref属性引用那个<bean>id

<bean id="userServlet" class="cn.fyy.spring.UserServlet">
	<property name="userDao" ref="userDao" />
</bean>

综合来看,无论属性的值是什么类型,只要是通过SET方式注入属性值,首先都必须为属性添加SET方法,然后在<bean>节点下级通过<property>节点进行配置,如果属性的值是基本值,则使用value属性直接编写对应的值,如果属性的值不是基本值,则使用ref属性引用另一个<bean>id(如果没有所说的另一个<bean>,就想办法配出这样一个<bean>)。

6.3. 通过构造方法注入属性的值

假设存在:

public class AdminDao {
	
	public String url;

	public AdminDao(String url) {
		super();
		this.url = url;
	}

}

在配置时:

<!-- 通过构造方法注入属性值 -->
<bean id="adminDao" class="cn.fyy.spring.AdminDao">
	<!-- index:参数的序号,即第几个参数 -->
	<constructor-arg index="0" 
		value="jdbc:mysql://..." />
</bean>

7.0. 注入集合类型的属性值

7.1. List

假设存在:

// Frank, Andy, Lucy, Kate
public List<String> names;

如果希望通过SET方式注入属性的值,需要先生成SET方法,然后,配置为:

<!-- 注入List类型的值:Frank, Andy, Lucy, Kate -->
<property name="names">
	<list>
		<value>Frank</value>
		<value>Andy</value>
		<value>Lucy</value>
		<value>Kate</value>
	</list>
</property>

Spring框架在处理时,会使用ArrayList封装List类型的属性的值。

7.2. Set

假设存在:

// Beijing, Shanghai, Guangzhou, Shenzhen
public Set<String> cities;

则配置为:

<!-- 注入Set类型的值:Beijing, Shanghai, Guangzhou, Shenzhen -->
<property name="cities">
	<set>
		<value>Beijing</value>
		<value>Shanghai</value>
		<value>Guangzhou</value>
		<value>Shenzhen</value>
	</set>
</property>

Spring框架在处理时,会使用LinkedHashSet封装Set类型的属性的值。

7.3. 数组

假设存在:

// { 9, 5, 2, 7 } 
public int[] numbers;

则配置为:

<!-- 注入数组类型的值:{ 9, 5, 2, 7 } -->
<property name="numbers">
	<array>
		<value>9</value>
		<value>5</value>
		<value>2</value>
		<value>7</value>
	</array>
</property>

其实,在Spring框架中,注入List集合类型的值和数组类型的值时,使用<list>节点或者<array>节点都是可以的,即:数据是List类型的,使用<array>来配置,或者数据是数组类型的,使用<list>来配置,都是正确的。但是,在实际应用时,还是应该注意区分。

7.4. Map

假设存在:

// username=root, password=1234, from=Hangzhou, age=26
public Map<String, String> session;

则配置为:

<!-- 注入Map类型的值:username=root, password=1234, from=Hangzhou, age=26 -->
<property name="session">
	<map>
		<entry key="username" value="root" />
		<entry key="password" value="1234" />
		<entry key="from" value="Hangzhou" />
		<entry key="age" value="26" />
	</map>
</property>

7.5. Properties

在配置Properties类型的属性值时,其配置的节点结构是:

<!-- 注入Properties类型的值 -->
<property name="config">
	<props>
		<prop key="username">root</prop>
		<prop key="password">1234</prop>
	</props>
</property>

另外,也可以准备专门的**.properties文件,例如在src/main/resources下创建db.properties**文件:

url=jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver=com.mysql.jdbc.Driver
username=root
password=1234

然后,在Spring的配置文件中,使用<util:properties>节点就可以直接读取**.properties**文件:

<util:properties id="config" 
	location="classpath:db.properties" />

以上<util:properties>节点也是一种<bean>节点,所以,注入值时,可以通过ref引用这个节点:

<!-- 注入Properties类型的值 -->
<property name="config" ref="config" />

8. Spring表达式

Spring表达式是使用占位符的方式定义在Spring的配置文件中的,作用是用于获取其他<bean>中的属性的值!

假设存在:

public class ValueBean {
	
	// 值是SampleBean中的names中的第2个
	public String name;
	
}

首先,需要确定注入值的方式,可以是通过SET方式注入,也可以通过构造方法注入,如果选择通过SET方式注入,需要先生成SET方法:

public void setName(String name) {
	this.name = name;
}

然后,在Spring的配置文件中进行配置:

<!-- 使用Spring表达式 -->
<bean id="valueBean" class="cn.fyy.spring.ValueBean">
	<!-- 值是SampleBean中的names中的第2个 -->
	<property name="name"
		value="#{sampleBean.names[1]}" />
</bean>

可以发现,Spring表达式的基本格式是使用#{}进行占位,其内部语法是:

#{bean的id.属性}

如果属性的类型是List集合、Set集合或者数组,则在属性右侧使用[]写出索引或者下标:

#{bean的id.属性[索引或者下标]}

如果属性的类型是Map集合或者Properties,可以使用的语法:

#{bean的id.属性.key}

#{bean的id.属性['key']}

9. 自动装配(autowire)

自动装配:不需要在Spring的配置文件中进行属性值的注入,只需要配置允许自动装配,Spring就会自动的完成属性值的注入。

假设存在StudentServlet依赖于StudentDao

public class StudentServlet {
	
	public StudentDao studentDao;
	
	public void setStudentDao(StudentDao studentDao) {
		this.studentDao = studentDao;
	}

	public void reg() {
		System.out.println("StudentServlet.reg()");
		studentDao.reg();
	}

}

public class StudentDao {
	
	public void reg() {
		System.out.println("StudentDao.reg()");
	}

}

就可以配置为:

<bean id="studentDao"
	class="cn.fyy.spring.StudentDao" />
<bean id="studentServlet" 
	class="cn.fyy.spring.StudentServlet"
	autowire="byName">
</bean>

以上配置中,autowire属性就是用于配置自动装配的。

当取值是byName时,表示“按照名称自动装配”,在这个过程中,Spring会先检测到在StudentServlet中有名为studentDao的属性,会根据该属性得到setStudentDao这个方法名称,然后,尝试找到与SET方法名称对应的bean的id,即查找某个id为studentDao<bean>,如果找到,则自动调用setStudentDao方法来完成自动装配,如果没有找到匹配的bean-id,则不会尝试自动装配。简单的来说,就是SET方法的名称需要与bean-id相对应,属性的名称可以和bean-id不对应。自动装配是一种“尝试性”的操作,能装就装,装不上也不会报错!

另外,还可以取值为byType,表示“按照类型自动装配”,在这个过程,Spring会检测StudentServlet中以set作为前缀的方法,并尝试调用这些方法,调用时,会在Spring容器中查找与参数类型相符合的bean,如果没有匹配的对象,则不自动装配,如果找到1个,则执行该方法,以完成自动装配,如果找到2个或者更多,则直接报错错误

还有其它装配模式,一般不必了解。

在实际开发时,并不会使用这种方式来实现自动装配,因为这种做法存在的问题:属性是否被装配了,表现的不明显,如果不详细的阅读完整的源代码,根本无法确定类中的哪些属性被装配了值,而哪些属性没有被装配值!

目前,主要理解自动装配的思想,及byNamebyType这2种装配模式的特性即可。

----------------------------

附1:List与Set

List中的元素是可以重复的,例如在同一个List中存储2个string-1,而Set中的元素是不可以重复的,例如在同一个Set中添加2个string-1,实际只会存储第1次添加的string-1,第2次添加的是相同的数据,则不会被存储下来!判断“元素是否相同”的标准是:调用equals()对比的结果是true,并且2个元素的hashCode()返回值相同。

List是序列的集合,典型的实现类有ArrayListLinkedList,其中,ArrayList查询效率高,但是修改效率低,而LinkedList查询效率低,但是修改效率高。

Set是散列的集合,从实际表现来看,并不能说Set是无序的,例如TreeSet会把元素按照字典排序法进行排序,如果元素是自定义的数据类型,需要该类型实现Comparable接口,重写其中的int compareTo(T other)方法,实际上TreeSet会调用各元素的compareTo()方法实现排序,这个排序过程是运行时执行的,从数据存储的角度来看,数据在内存中依然是散列的,另外,还有LinkedHashSet是通过链表的形式将各个元素“链”起来的,所以,输出显示这种Set时,输出结果与添加元素的顺序是保持一致的!

所有Set都是只有key没有value的Map。

附2:内存溢出/内存泄漏/Leak

内存溢出并不是“使用的内存超出了限制”,如果是使用的内存超出了限制,会直接出现OutOfMemoryError

内存溢出通常是因为程序意外崩溃,而某些资源并没有被释放!例如:尝试读取硬盘上的某个文件,假设使用了FileInputStream fis变量,在读取过程中,出现了IOException导致程序崩溃,则fis变量就不再可用,变量对应的流对象可能还处于与硬盘上的文件是连接的状态,所以就会出现“作为程序员来说,已经无法控制这个对象了,但是,由于对象仍然处于连接状态,JVM中的垃圾回收机制并不会把它当做垃圾进行回收”,这样的数据如果越来越多,就会无谓的浪费更多的内存,导致可用内存越来越少,最终,继续积累的话,就会导致“溢出”。

所以,少量的内存溢出其实是没有明显的危害的!但是,仍然应该尽可能的处理掉所有可能存在的内存溢出问题!最简单的解决方案就是“随时用完随时关闭”。

10. SpringIOC注解

10.1. 通用注解

使用注解的方式来管理对象时,就不必在Spring的配置文件中使用<bean>节点进行配置了,但是,需要先配置一项“组件扫描”,使得Spring框架知道需要管理的类在哪里:

<!-- 配置组件扫描的根包 -->
<context:component-scan base-package="cn.fyy.spring" />

然后,为需要创建对象的类添加@Component注解即可:

@Component
public class UserDao {

}

也就是说,“组件扫描 + 注解”就可以实现Spring创建对象!

在配置组件扫描时,base-package表示“根包”,假设类都在cn.fyy.spring包中,可以直接配置为这个包,也可以配置为cn.fyy,甚至配置为cn都是可以的!一般不推荐使用过于简单的根包,例如实际使用的是cn.fyy.spring.daocn.fyy.spring.servlet等,可以把根包设置为cn.fyy.spring,却不建议设置为cn或者cn.fyy

关于使用的注解,可以是:

  • @Component:通用注解

  • @Controller:添加在控制器之前的注解

  • @Service:添加在业务类之前的注解

  • @Repository:添加在处理持久层的类之前的注解

在配置Spring创建和管理对象时,在类之前添加以上4个注解中的任意1个即可,以上4个注解的作用相同,使用方式相同,语义不同。

在使用以上注解后,由Spring创建的对象的bean-id默认就是类名首字母改为小写的名称,例如UserDao类的默认bean-id就是userDao,如果需要自定义bean-id,可以对注解进行配置,例如:

@Component("dao")
public class UserDao {

}

10.2. 关于作用域与生命周期的注解

使用@Scope注解可以配置某类的对象是否是单例的,可以在该注解中配置属性为singletonprototype,当配置为@Scope("prototype")时表示非单例的,如果希望是单例,则不需要配置该注解,默认就是单例的。

在单例的前提下,默认是饿汉式的单例,如果希望是懒汉式的单例模式,可以在类之前添加@Lazy注解,在该注解中还可以配置truefalse,例如@Lazy(false),但是,没有必要这样配置,如果希望是饿汉式的,根本就不需要添加该注解,如果希望是懒汉式的,只需要配置@Lazy即可,而不需要写成@Lazy(true)

在被Spring管理的类中,可以自定义方法,作为初始化方法和销毁时调用的方法,需要添加@PostConstruct@PreDestroy注解,例如:

@Component
public class UserDao {

	public UserDao() {
		System.out.println("创建UserDao的对象!");
	}
	
	@PostConstruct
	public void init() {
		System.out.println("UserDao.init()");
	}
	
	@PreDestroy
	public void destroy() {
		System.out.println("UserDao.destroy()");
	}
	
}

以上2个注解是javax包中的注解,使用时,需要为项目添加Tomcat运行环境,以使用Java EE相关的jar包,才可以识别以上2个注解!

11. 自动装配

假设存在:

@Repositor
public class UserDao {
	
	public void reg() {
		System.out.println("UserDao.reg()");
	}

}

如果存在UserServlet需要依赖于以上UserDao,则在UserServlet中的属性之前添加@Autowired注解即可,例如:

@Controller
public class UserServlet {
	
	@Autowired
	public UserDao userDao;
	
	public void reg() {
		System.out.println("UserServlet.reg()");
		userDao.reg();
	}

}

当然,以上2个类都必须是被Spring所管理的,即:都在组件扫描的包下,且都添加了@Component或等同功能的注解。

通过注解实现自动装配时,并不需要属性有SET方法!Spring框架就是将值直接赋值过去的!

使用@Resource注解也可以实现自动装配,它是Java EE中的注解,它的装配模式是:优先byName实现装配,如果装配失败,会尝试byType实现装配,且,如果byType装配,要求匹配类型的对象必须有且仅有1个,无论是0个还是超过1个,都会报告错误!

使用@Resource时还可以配置需要注入的bean的id,例如:

@Resource(name="ud1")

使用@Autowired时,会优先byType,如果找到1个匹配类型的对象,则直接装配,如果没有匹配类型的对象,则直接报告错误,如果找到多个匹配类型的对象,则会尝试byName,如果byName装配失败,则报告错误!@AutoWired默认以类型进行查找,@Resource默认以名称进行查找

12. SpringAop

12.1 原理是什么

AOP思想的实现一般都是基于 代理模式 ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,JDK动态代理模式只能代理接口而不能代理类。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。Cglib可以对任何类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。

  • 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;
  • 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。

12.2 aop名词有什么

Aspect(切面):通知和切入点的结合。在SpringAOP中,切面通过带有@Aspect注解的类实现。

Joinpoint(连接点):目标对象中,所有可以增强的方法

Pointcut(切入点):目标对象中,已经增强的方法。

Advice(通知/增强):增强的代码

Target(目标对象):被代理对象

Weaving(织入):将通知应用到切入点的过程

Proxy(代理):将通知织入目标对象之后,形成代理对象

12.3 Spring切面可以应用5种类型的通知Advice

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

12.4 配置aop

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

12.5 aop机制,实现,具体怎样使用,具体到标签?

AOP,面向切面编程,是将纵向重复的代码,横向抽取出来。最明显的体现就是过滤器和拦截器的使用。

    Spring实现AOP的本质是动态代理,Spring采用两种动态代理的方式,分别是JDK动态代理和cglib动态代理。JDK动态代理的被代理对象必须实现接口,而cglib动态代理的被代理对象原则上可以是任何类,cglib实现动态代理的原理是对被代理对象进行继承,重写被代理对象中所有的方法,所以被代理对象不能被final修饰。

    Spring操作AOP,具体可分为4步。
    1.导包;
    2,准备目标对象;
    3.准备通知;
    4,将通知织入目标对象。
    其中2(准备目标对象)和3(准备通知)由java代码实现,4(将通知织入目标对象)有两种实现方式,一种是xml配置,一种是注解配置。其中,xml配置用的标签有<aop:config>、<aop:pointcut>、<aop:aspect>、<aop:before>、<aop:after-returning>、<aop:around>、<aop:after-throwing>、<aop:after>。注解配置用到的注解有:@Aspect、@Pointcut、@Before、@AfterReturning、@Around、@AfterThrowing、@After。

12.6 代理模式

12.6.1静态代理
public interface Subject {
    void request();
    void hello();
}

目标对象:

public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }

    @Override
    public void hello() {
        System.out.println("hello");
    }
}

代理对象:

public class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("before");
        try{
            realSubject.request();
        }catch (Exception e){
            System.out.println("ex:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after");
        }
    }

    @Override
    public void hello() {
   		System.out.println("before");
        realSubject.hello();
        System.out.println("after");
    }
}
public class Client {
    public static void main(String[] args){
        Subject subject = new Proxy(new RealSubject());
        subject.request();
    }
}

目标对象和代理对象共同作为接口的实现类,代理对象把真正的方法委托给目标对象来执行,自己就执行额外的也就是AOP要织入的逻辑,即客户端调用代理的方法,同时给代理类传入目标对象,在代理类里面代理类通过目标对象来调用目标类的方法,在执行方法前后加上代理类的逻辑。

在这里插入图片描述

12.6.2JDK动态代理
  1. 类 java.long.reflect.Proxy
  2. 接口: InvocationHandler
  3. 只能基于接口进行动态代理

jdk运行期动态代理源码解析:其实就是真实类实现一个接口,我们再写一个类似于切面的类,实现invocationhandler接口且实现invoke方法,同时要保存真实类对象,初始化时赋值对象,invoke方法中反射方式调用真实对象方法,在方法前后可以加上定制的逻辑,这个方法其实是动态代理对象调用的,动态代理对象是客户端通过动态代理类实例化的,而动态代理类是真实对象方法执行前的运行期生成的.class类,这个类实现了和真实对象一样的接口,所以也有真实对象的方法,调用代理对象方法时也就可以传入参数,然后代理对象再将方法和参数传递给invocationhandler的实例对象。

12.6.3cglib动态代理

在这里插入图片描述
在这里插入图片描述
(1) 生成指定类对象的子类,也就是重写类中的业务函数。

(2) 执行回调函数,加入intercept()函数。

(3) 创建这个类的子类对象。

反射技术实现,调用目标对象的方法

可以强制使用cglib代理

@EnableAspectJAutoProxy(ProxyTargetClass = true)
12.6.4JDK和cglib代理的区别
  1. JDK只能针对有接口的类的接口方法进行代理 ps: 接口的方法默认修饰为 public abstract
  2. cglib基于继承来实现代理,无法对 static、final修饰的类代理,以及对private、static修饰的方法代理

13. spring 事务

13.1 事务特性(ACID)

原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性: 执行事务前后,数据保持一致;

隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;

持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

13.2 Spring事务管理方式

(1) 编程式事务管理

通过Transaction Template手动管理事务,实际应用中很少使用,

(2) 声明式事务

推荐使用(代码侵入性最小),实际是通过AOP实现

13.3 事务实现方式(声明式)

基于 TransactionInterceptor 的声明式事务: Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 Spring 声明式事务有很大作用。

基于 TransactionProxyFactoryBean 的声明式事务: 第一种方式的改进版本,简化的配置文件的书写,这是 Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。

基于< tx> 和< aop>命名空间的声明式事务管理: 目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。

基于 @Transactional 的全注解方式: 将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。

13.4 演示

SQL:

create table `account` (
	`username` varchar (99),
	`salary` int (11)
); 
insert into `account` (`username`, `salary`) values('小王','3000');
insert into `account` (`username`, `salary`) values('小马','3000');

(1)编程式事务管理

注意: 通过添加/删除accountMoney() 方法中int i = 10 / 0这个语句便可验证事务管理是否配置正确。

OrdersDao.java(Dao层)

package cn.itcast.dao;

import org.springframework.jdbc.core.JdbcTemplate;

public class OrdersDao {
	// 注入jdbcTemplate模板对象
	private JdbcTemplate jdbcTemplate;

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	// 对数据操作的方法不包含业务操作
	/**
	 * 小王少钱的方法
	 */
	public void reduceMoney() {
		String sql = "update account set salary=salary-? where username=?";
		jdbcTemplate.update(sql, 1000, "小王");
	}

	/**
	 * 小马多钱的方法
	 */
	public void addMoney() {
		String sql = "update account set salary=salary+? where username=?";
		jdbcTemplate.update(sql, 1000, "小马");
	}
}

OrdersService.java(业务逻辑层)

package cn.itcast.service;

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import cn.itcast.dao.OrdersDao;

public class OrdersService {
	// 注入Dao层对象
	private OrdersDao ordersDao;

	public void setOrdersDao(OrdersDao ordersDao) {
		this.ordersDao = ordersDao;
	}

	// 注入TransactionTemplate对象
	private TransactionTemplate transactionTemplate;

	public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		this.transactionTemplate = transactionTemplate;
	}

	// 调用dao的方法
	// 业务逻辑,写转账业务
	public void accountMoney() {
		transactionTemplate.execute(new TransactionCallback<Object>() {

			@Override
			public Object doInTransaction(TransactionStatus status) {
				Object result = null;
				try {
					// 小马多1000
					ordersDao.addMoney();
					// 加入出现异常如下面int
					// i=10/0(银行中可能为突然停电等。。。);结果:小马账户多了1000而小王账户没有少钱
					// 解决办法是出现异常后进行事务回滚
					int i = 10 / 0;// 事务管理配置后异常已经解决
					// 小王 少1000
					ordersDao.reduceMoney();
				} catch (Exception e) {
					status.setRollbackOnly();
					result = false;
					System.out.println("Transfer Error!");
				}

				return result;
			}
		});

	}
}

TestService.java(测试方法)

package cn.itcast.service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestService {
	@Test
	public void testAdd() {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"beans.xml");
		OrdersService userService = (OrdersService) context
				.getBean("ordersService");
		userService.accountMoney();
	}
}

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd  
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd  
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	<!-- 配置c3po连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 注入属性值 -->
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property>
		<property name="user" value="root"></property>
		<property name="password" value="153963"></property>
	</bean>
	<!-- 编程式事务管理 -->
	<!-- 配置事务管理器 -->
	<bean id="dataSourceTransactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 配置事务管理器模板 -->
	<bean id="transactionTemplate"
		class="org.springframework.transaction.support.TransactionTemplate">
		<!-- 注入真正进行事务管理的事务管理器,name必须为 transactionManager否则无法注入 -->
		<property name="transactionManager" ref="dataSourceTransactionManager"></property>
	</bean>

	<!-- 对象生成及属性注入 -->
	<bean id="ordersService" class="cn.itcast.service.OrdersService">
		<property name="ordersDao" ref="ordersDao"></property>
		<!-- 注入事务管理的模板 -->
		<property name="transactionTemplate" ref="transactionTemplate"></property>
	</bean>

	<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	<!-- JDBC模板对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
</beans> 
(2)基于AspectJ的声明式事务管理

OrdersService.java(业务逻辑层)

package cn.itcast.service;

import cn.itcast.dao.OrdersDao;

public class OrdersService {
	private OrdersDao ordersDao;

	public void setOrdersDao(OrdersDao ordersDao) {
		this.ordersDao = ordersDao;
	}

	// 调用dao的方法
	// 业务逻辑,写转账业务
	public void accountMoney() {
		// 小马多1000
		ordersDao.addMoney();
		// 加入出现异常如下面int i=10/0(银行中可能为突然停电等。。。);结果:小马账户多了1000而小王账户没有少钱
		// 解决办法是出现异常后进行事务回滚
		int i = 10 / 0;// 事务管理配置后异常已经解决
		// 小王 少1000
		ordersDao.reduceMoney();
	}
}

配置文件:

	<!-- 配置c3po连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 注入属性值 -->
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property>
		<property name="user" value="root"></property>
		<property name="password" value="153963"></property>
	</bean>
	<!-- 第一步:配置事务管理器 -->
	<bean id="dataSourceTransactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 第二步:配置事务增强 -->
	<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
		<!-- 做事务操作 -->
		<tx:attributes>
			<!-- 设置进行事务操作的方法匹配规则 -->
			<!-- account开头的所有方法 -->
	        <!--
	          propagation:事务传播行为; 
	          isolation:事务隔离级别;
	          read-only:是否只读;
	          rollback-for:发生那些异常时回滚 
	          timeout:事务过期时间
	         -->
			<tx:method name="account*" propagation="REQUIRED"
				isolation="DEFAULT" read-only="false" rollback-for="" timeout="-1" />
		</tx:attributes>
	</tx:advice>

	<!-- 第三步:配置切面 切面即把增强用在方法的过程 -->
	<aop:config>
		<!-- 切入点 -->
		<aop:pointcut expression="execution(* cn.itcast.service.OrdersService.*(..))"
			id="pointcut1" />
		<!-- 切面 -->
		<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1" />
	</aop:config>


	<!-- 对象生成及属性注入 -->
	<bean id="ordersService" class="cn.itcast.service.OrdersService">
		<property name="ordersDao" ref="ordersDao"></property>
	</bean>
	<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
(3)基于注解的方式

OrdersService.java(业务逻辑层)

package cn.itcast.service;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import cn.itcast.dao.OrdersDao;

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
public class OrdersService {
	private OrdersDao ordersDao;

	public void setOrdersDao(OrdersDao ordersDao) {
		this.ordersDao = ordersDao;
	}

	// 调用dao的方法
	// 业务逻辑,写转账业务
	public void accountMoney() {
		// 小马多1000
		ordersDao.addMoney();
		// 加入出现异常如下面int i=10/0(银行中可能为突然停电等。。。);结果:小马账户多了1000而小王账户没有少钱
		// 解决办法是出现异常后进行事务回滚
		// int i = 10 / 0;// 事务管理配置后异常已经解决
		// 小王 少1000
		ordersDao.reduceMoney();
	}
}

配置文件:

	<!-- 配置c3po连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 注入属性值 -->
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property>
		<property name="user" value="root"></property>
		<property name="password" value="153963"></property>
	</bean>
	<!-- 第一步:配置事务管理器 (和配置文件方式一样)-->
	<bean id="dataSourceTransactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 第二步: 开启事务注解 -->
	<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
	<!-- 第三步 在方法所在类上加注解 -->
	
	
	<!-- 对象生成及属性注入 -->
	<bean id="ordersService" class="cn.itcast.service.OrdersService">
		<property name="ordersDao" ref="ordersDao"></property>
	</bean>
	<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

Money();
}
}


**配置文件:**

```java
	<!-- 配置c3po连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 注入属性值 -->
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property>
		<property name="user" value="root"></property>
		<property name="password" value="153963"></property>
	</bean>
	<!-- 第一步:配置事务管理器 (和配置文件方式一样)-->
	<bean id="dataSourceTransactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 第二步: 开启事务注解 -->
	<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
	<!-- 第三步 在方法所在类上加注解 -->
	
	
	<!-- 对象生成及属性注入 -->
	<bean id="ordersService" class="cn.itcast.service.OrdersService">
		<property name="ordersDao" ref="ordersDao"></property>
	</bean>
	<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

13.5 事务属性

(1)事务隔离级别(定义了一个事务可能受其他并发事务影响的程度):

我们先来看一下 并发事务带来的问题 ,然后再来介绍一下 TransactionDefinition 接口 中定义了五个表示隔离级别的常量。

并发事务带来的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致一下的问题。

  • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

  • 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。

    例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

  • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

  • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于新增或者删除。

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
(2)事务传播行为(为了解决业务层方法之间互相调用的事务问题):

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

(3) 事务超时属性(一个事务允许执行的最长时间)

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

(4) 事务只读属性(对事物资源是否执行只读操作)

事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

(5) 回滚规则(定义事务回滚规则)

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 总结(学习+面试) 的相关文章

随机推荐

  • 信号量与生产者消费者问题

    生产者 消费者问题 生产者 消费者题型在各类考试 xff08 考研 程序员证书 程序员面试笔试 期末考试 xff09 很常见 xff0c 原因之一是生产者 消费者题型在实际的并发程序 xff08 多进程 多线程 xff09 设计中很常见 x
  • IOC的实现原理—反射与工厂模式

    反射机制概念 我们考虑一个场景 xff0c 如果我们在程序运行时 xff0c 一个对象想要检视自己所拥有的成员属性 xff0c 该如何操作 xff1f 再考虑另一个场景 xff0c 如果我们想要在运行期获得某个类的Class信息如它的属性
  • Spring中bean的作用域与生命周期

    在Spring中 xff0c 那些组成应用程序的主体及由Spring IoC容器所管理的对象 xff0c 被称之为bean 简单地讲 xff0c bean就是由IoC容器初始化 装配及管理的对象 xff0c 除此之外 xff0c bean就
  • 前后端分离架构概述

    1 背景 前后端分离已成为互联网项目开发的业界标准使用方式 xff0c 通过nginx 43 tomcat的方式 xff08 也可以中间加一个nodejs xff09 有效的进行解耦 xff0c 并且前后端分离会为以后的大型分布式架构 弹性
  • 数据库的事务隔离级别总结

    学习数据库的时候常常会接触到事务 ACID等概念 xff0c 那么到底什么是数据库的事务 xff0c 数据库事务又具有哪些特点 xff0c 和ACID有怎样的关系 xff0c 事务的隔离级别又是做什么的呢 xff1f 事务及其四大特性 xf
  • ubuntu18 遇到libc6 版本冲突问题

    问题描述 xff1a You might want to run 39 apt fix broken install 39 to correct these The following packages have unmet depende
  • Linux 系统裁剪--制作一个最小化的Linux iso镜像

    1 前言 一直以来都想制作一个最小化的Linux系统 xff0c 这个小系统需要有常用的Linux 命令 xff0c 以及定制化的某些功能 可是由于种种原因一直没能实现 xff0c 最近终于有时间把它做了出来 本文所说的精简的Linux系统
  • git为单独的仓库设置提交的用户名

    在我们平时的学习中可能有这么一种需求 xff0c 在公司进行开发的时候 xff0c 一般会参与多个项目的开发 xff0c 而项目提交代码时 xff0c 一般请求情况下提供的用户都是同一个 xff0c 而我们为了方便可能会使用全局进行git
  • Ubuntu18.04安装Xfce桌面与VNC远程工具

    1 Xfce桌面的安装 Xfce是一款轻量级的桌面环境 xff0c 运行在类Unix操作系统 xff08 如Linux FreeBSD 和 Solaris xff09 上 xff0c 界面清爽美观且对用户友好 在安装Xfce前需要更新一下系
  • Nginx的临时文件权限问题

    问题 在重启了nginx发现部分较大的Post请求出现了500错误 xff0c 然后 xff0c 查看Nginx错误日志 xff0c 类似如下 xff1a nginx open 34 usr local nginx client body
  • Spring Security中启用CSRF防护(REST版)

    问题 之前文章 Spring Security 43 Spring Session Redis 43 JJWT 介绍了怎么使用Spring Security实现restful风格的登录api 现在 xff0c 我们在这个基础上面实现csrf
  • Android Editable

    简单记一下 xff0c 以前没怎么用过 EditText View的getText直接返回的就是 Editable 而 TextView则是getEditableText才返回 Editable 还有就是注释所说 Replaces the
  • Linux虚拟机断电后开机出现:Entering emeryency mode. Exit the shell to continue.

    在一台服务器上 xff0c 搭建了4个linux虚拟机 可是昨晚不知道怎么的 xff0c 公司断电 今早来的时候发现服务器关机了 然后开机 xff0c 启动虚拟机 xff0c 其中有一台启动不了 xff0c 提示信息如下 xff1a 解决方
  • Web标准和常用浏览器及其内核

    Web标准 Web标准是由W3C组织和其他标准化组织制定的一 系列标准的集合 W3C 万维网联盟 是国际最著名的标准化组织 Web标准的优点 xff1a 1 遵循Web标准除了可以让不同的开发人员写出的页面更标准 更统一 2 让Web的发展
  • java集合集锦_

    java集合集锦 文章目录 java集合集锦一 java集合框架图二 集合简介三 集合遍历四 Arraylist 与 LinkedList 区别 五 ArrayList 与 Vector 区别呢 六 要对集合更新操作时 xff0c Arra
  • java map转为实体类的方法

    一 自己定义方法 span class token keyword public span span class token keyword static span span class token generics function sp
  • oracle 一行转多行

    span class token keyword SELECT span substr span class token punctuation span PBTYPE span class token punctuation span i
  • MiroTik 路由器配置无线中继模式(超细教程)

    关于MikroTik 看一下百度百科中关于MikroTik的介绍吧 MikroTik成立于1995年 xff0c 总部位于拉脱维亚首都里加 xff0c 主要从事开发路由器和无线ISP系统 1997年RouterOS系统 xff0c 2002
  • springmvc项目,启动报错Context initialization failed

    错误描述如下 xff1a springmvc项目 tomcat启动报错 Context initialization failed org span class token punctuation span springframework
  • Spring 总结(学习+面试)

    Spring学习笔记 文章目录 Spring学习笔记1 0 概述1 1使用Spring框架的好处1 2特点 2 0体系结构3 0创建Spring java工程 xff08 eclipse xff09 4 0通过Spring创建对象的方式5