Spring学习(汇总)
Spring 是一个分层的 JavaSE/EEfull-stack(一站式) 轻量级开源框架。
spring概述
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作 Expert One-On-One J2EE Development and Design 中阐述的部分理念和原型衍生而来。它是 为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使 用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring 使用基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。然而,Spring 的用途不仅限于服务器端的开发。从简单性、 可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 的核心是控制反转 (IoC)和面向切面(AOP)。简单来说,Spring 是一个分层的 JavaSE/EEfull-stack(一站式) 轻量级开源框架。
EE 开发分成三层结构:
- WEB层:Spring MVC.
- 业务层:Bean 管理:(IOC)
- 持久层:Spring 的 JDBC 模板.ORM 模板用于整合其他的持久层框架
为什么会用spring
方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象创建和依赖关系维护,交给 Spring 管理。
AOP 编程的支持
Spring 提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程
方便程序的测试
Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序
方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、 MyBatis、Quartz 等)的直接支持
降低JavaEE API 的使用难度
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装, 使这些 API 应用难度大大降低
IOC:反转控制
将重建对象的方法进行反转,之前~pojo对象的创建等是由开发人员自己维护,包括依赖关系也是自己注入。
使用spring之后,对象的创建以及依赖关系由spring完成创建以及注入。
反转控制就是反转了对象的创建方式,由我们自己创建反转给了程序(Spring)。
- 传统方式
对象A如果想使用对象B的功能方法,在需要的时候创建对象B的实例,调用需要的方法,对对象B有主动的控制权。
- IOC容器
当使用IOC容器之后,对象A和B之间失去了直接联系,对象A如果想使用对象B的功能方法,IOC容器会自动创建一个对象B实例注入到对象A需要的功能模块中,这样对象A失去了主动控制权,也就是控制反转了。
DI:依赖注入
实现IOC思想,需要DI做支持。
IOC给对象直接建立关系的动作,称为DI依赖注入(Dependency Injection);依赖:对象A需要使用对象B的功能,则称对象A依赖对象B。
注入:在对象A中实例化对象B,从而使用对象B的功能,该动作称为注入。
注入方式:
- set方法注入
- 构造方法注入
- 字段注入
注入类型:
- 值类型注入(八大基本数据类型)
- 引用类型注入
Spring中的工厂(容器)
BeanFactory(过时)
Spring的原始接口,针对原始接口的实现类功能比较单一。
BeanFactory接口实现类的容器特点是:每次再获得对象时才会创建对象。
在硬件资源匮乏的年代使用
ApplicationContext
接口实现类的容器特点是:每次容器启动时,就会创建容器中配置的所有对象。
除此之外,还提供了更多丰富的功能。
ApplicatioContext 接口有两个实现类:
从类路径下加载配置文件:ClassPathXmlApplicationContext
从硬盘绝对路径下加载:FileSystemXmlApplicationContext
通俗地说:
BeanFactory :是在 getBean 的时候才会生成类的实例.
ApplicationContext :在加载 applicationContext.xml(容器启动)时候就会创建.
总结
使用场景:
在web开发中,使用applicationContext
在资源匮乏的环境可以使用Beanfactory(例如:手机端)
针对上面用到的几个核心API进行说明,后续持续总结。
1、BeanFactory
这是一个工厂,用于生成任意bean。采取延迟加载,第一次getBean时才会初始化Bean。
2、ApplicationContext
是BeanFactory的子接口,功能更强大。(国际化处理、事件传递、Bean自动装配、各种不同应用层的Context实现)。当配置文件被加载,就进行对象实例化。
3、ClassPathXmlApplicationContext
用于加载classpath(类路径、src)下的xml加载xml运行时位置:/WEB-INF/classes/…xml
4、FileSystemXmlApplicationContext
用于加载指定盘符下的xml加载xml运行时位置:/WEB-INF/…xml,通过ServletContext.getRealPath()获得具体盘符配置。
Bean元素
Bean元素: 使用该元素描述需要被spring容器管理的对象。
name属性:给被管理的对象起个名字,获得对象时根据该名称获得对象,名字可以重复,可以使用特殊字符。
class属性:被管理对象的完整类名。
id属性: 与name属性功能一致,但名称不可重复,不能使用特殊字符。
结论: 尽量使用name属性。
三种对象的创建方式:
空参构造创建:(重要)
<bean name="user" class="pers.sfl.pojo.User"/>
静态工厂(了解)
调用UserFactory类中的createUser方法创建名为user2的对象,放入容器
1
2
3
4<bean name="user2"
class="pers.sfl.pojo.UserFactory"
factory-method="createUser ">
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package pers.sfl.pojo;
import pers.sfl.pojo.User;
public class UserFactory {
public static User createUser (){
System.out.println("静态工厂创建User");
return new User();
}
}
//测试
public void test2() throws Exception{
ApplicatiionContext ac = new ClassPathXmlApplicatiionContext("applicatiionContext.xml");
User user = (User) ac.getBean("user2");
System.outl.println(user);
}实例工厂
会去走无参构造
1
2
3
4
5
6<bean name="user3"
factory-bean="userFactory"
factory-method="createUser2" ></bean>
<bean name="userFactory"
class="pers.sfl.pojo.UserFactory">
</bean>1
2
3
4
5
6public class UserFactory {
public User createUser2 (){
System.out.println("实例工厂创建User");
return new User();
}
}
Scope 属性:
singleton:(默认值 )—— 单例对象,被标识为单例的对象在spring容器中只会存在一个实例。
1
<bean name="user" class="pers.sfl.pojo.User" scope="singleton"/>
1
2
3
4User user1 = ac.getBean("user");
User user2 = ac.getBean("user");
System.out.println(user1==user2); //结果返回true
//这是返回true的原因是,因为是单例模式,只承认一个对象,所以返回true。prototype:多例原型,被标识为多例的对象,每次再获得时才会创建,每次创建都是新的对象.
1
<bean name="user" class="pers.sfl.pojo.User" scope="prototype"/>
1
2
3
4
5User user1 = ac.getBean("user");
User user2 = ac.getBean("user");
System.out.println(user1==user2); //结果返回false
//现在返回的结果是false,而且打印了好几次的构造方法,
//因为每次创建对象都会返回不同的对象,在内存中的地址也是不一样的,所以返回falserequest:web环境下,对象与request生命周期一致
session:web环境下,对象与session生命周期一致
绝大多数,都使用默认值,单例对象。
整合struct2时,ActionBean必须配置为多例
生命周期属性
可以配置一个方法作为生命周期初始化方法,spring会在对象创建之后立即调用。(init-method)
也可以配置一个方法作为声明周期的销毁方法,spring容器在关闭并销毁所有容器中的对象之前调用(init-destory)
1
2
3
4
5<bean name="user"
class="pers.sfl.pojo.User"
init-method="init"
destroy-method="destory"/>
//这两个名称:init/destory 必须和pojo类中定义的方法名保持一致。
分模块化配置:
在实际开发中,对象pojo会很多,如果全放一个配置文件里,不好维护也比较繁琐
在主配置文件中引用其他配置文件即可
1 | <import resource="路径设置去掉src之前的部分即可"> |
spring属性注入
属性注入的四种方式:
set方法注入(最重要)
注入值类型:
1
2
3
4<bean name="user" class="pers.sfl.pojo.User">
<property name="username" value="Tom"></property>
<property name="age" value="12"></property>
</bean>引用类型: 引用类型注入,我们需要在创建一个pojo类型,以car为例:
我们需要将Car类作为User类的属性并且在Car本类中,同时提供get()/set()方法以及toString()方法。
1
2
3
4
5
6
7
8public class User {
private String username;
private int age;
//...
private Car car;
//get和set方法...
}然后先将Car配置到核心配置文件中:
1
2
3
4
5<!-- 将Car对象配置到容器中来 -->
<bean name="car" class="pers.sfl.pojo.Car">
<property name="name" value="布加迪威龙"></property>
<property name="color" value="black"></property>
</bean>然后对User对象的配置进行改造:
1
2
3
4
5
6
7
8
9
10<bean name="user" class="pers.sfl.pojo.User">
<property name="username" value="Tom"></property>
<property name="age" value="12"></property>
<!--为car属性注入上方配置的Car对象 -->
<property name="car" ref="car"></property>
</bean>
说明:
普通类型注入使用value
引用类型注入使用ref
构造函数注入
通过构造函数注入首先就要有构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public User(String username,Car car){
this.username = username;
this.car = car;
System.out.println("这是User(String username,Car car)构造方法。。");
}
````
```xml
<bean name="user" class="pers.sfl.pojo.User">
<constructor-arg name="username" value="jerry"></constructor-arg>
<constructor-arg name="car" ref="car"></constructor-arg>
</bean>
````
- 如果有多个构造呢,把car放前面,username放后面会出现怎么样的结果?
> 有name type index 可以准确定位到某个具体的方法
```java
public User(String username,Car car){
this.username = username;
this.car = car;
System.out.println("这是User(String username,Car car)构造方法。。");
}
public User(Car car,String username){
this.username = username;
this.car = car;
System.out.println("这是User(Car car,String username)构造方法。。");
}
这里有一个index,可以指定某个属性的位置,从0开始
1 | <bean name="user" class="pers.sfl.pojo.User"> |
我们还可以注意到有一个type,是指定属性的类型的,假如有属性名相同但数据类型不相同的属性时,就可以使用type属性。,
name属性:构造函数的参数名
index属性:构造函数的参数索引
type属性: 构造函数的参数类型
名称空间注入
p空间注入,使用的还是set无参构造方法
首先导入p命名空间,使用p:
属性完成注入:
xmlns:p="http://www.springframework.org/schema/p"
值类型:
p:属性名="值"
对象类型:
p:属性名 ref="bean名称"
1 | <bean name="user" |
spel注入
Spring Expression Language——Spring表达式语言
1 | <bean name="user4" class="pers.sfl.pojo.User"> |
复杂类型注入
数组:
1 | <bean name="cb" class="pers.sfl.pojo.CollectionBean"> |
List:
1 | <bean name="cb" class="pers.sfl.pojo.CollectionBean"> |
数组和List集合大同小异,只是各用各的标签,数组用array,List集合用list
Map:
1 | <bean name="cb" class="pers.sfl.pojo.CollectionBean"> |
Prop:
1 | <bean name="cb" class="pers.sfl.pojo.CollectionBean"> |
Spring管理容器在项目中的生命周期
在使用junit单元测试时,每次都需要加载spring容器来获取被bean管理的对象。但是applicationContext容器一经加载就会创建容器中所有的对象,junit测试时每次都需要加载容器,这属于错误的示范,那么如何解决这个问题呢?
1 | ApplicatiionContext ac = new ClassPathXmlApplicatiionContext("applicatiionContext.xml"); |
让spring容器的生命周期随着项目的启动而记载,项目的关闭而销毁,需要配置监听器:
导包 spring-web.XXX.jar
在web.xml里配置listener监听器
1
2
3
4
5
6
7
8
9
10
11<!-- 可以让spring容器随项目的启动而创建,随项目的关闭而销毁 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定配置spring配置文件的位置 -->
<context-param>
<!--名称固定,不能随便取,可以在源码里找到-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>测试
1
2
3
4
5
6
7
8
9
10
11
12//从application域中获取spring容器。
public void test() throws Exception{
//1.获得servletContext对象
ServletContext sc = ServletActionContext.getServletContext();
// 2.从sc中获得ac容器
WebApplicationContext ac =WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
// 3.从ac容器中获得bean
User user = (User)ac.getbean("user2");
System.outl.println(user);
}
Spring注解开发
使用注解配置Spring,为主配置文件引入新的命名空间(约束)
spring-context-4.2.xsd
开启使用注解代替配置文件(如果报错,加一个aop包)
1 | <!-- 指定扫描 pers.sfl.pojo包下的所有类中的注解 |
在类中使用注解完成配置
对象的配置(*****)
@Component("user")
相当于在xml中这样配:<bean name="user" class="pers.sfl.pojo.User">
@Component("")
//pojo@Service("")
//service@Controller("")
//web@Repository("")
//dao层
作用域的配置(*)
对象使用注解配好了,那作用域怎么配呢,怎么指定是单例还是多例呢
@Scope(scopeName="singleton | prototype")
//指定对象的作用域:单例/多例
属性的注入@Value(" ")
有两种方式:
- 第一种:在属性上注入(通过反射的Field赋值)
- 第二种:在set方法上注入(推荐使用)
1 | public class User { |
对象作为类的属性注入
@AutoWired("")
首先先将该类注入到bean中
@Component(" ")
然后使用
@AutoWired
,假如匹配到多个类型一致的对象,将无法具体选择注入哪一个对象,可以用@Qualifier
来指定具体注入的对象,比如你配置了两个userd对象,user1/user2,你想调用user2,就@Qualifier("user2")
1
2
3
4
5
6
7
8
9
public class User {
private String name;
//...
//自动装配
private Car car;
}@Resource(name="")
推荐使用手动注入,指定需要注入哪个名称的bean对象
1
2
3
4
5
6
7
8
9
public class User {
private String name;
//...
//自动装配
private Car car;
}
初始化和销毁方法
1 | //在对象被创建后调用 |
Spring与junit单元测试整合
导包 导一个test包
在类头添加注
1
2
3
4//创建容器
//指定创建容器使使用哪个配置文件测试:
Spring——AOP
什么是AOP
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP的好处
对程序进行增强:不修改源码的情况下. * AOP 可以进行权限校验,日志记录,性能监控,事务控制.
底层实现
代理机制:Spring 的 AOP 的底层用到两种代理机制:
JDK 的动态代理 :针对实现了接口的类产生代理.
Cglib 的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类 的子类对象.
相关术语
目标对象:
Joinpoint(连接点):目标对象中,所有可以增强的代码。相当于sql中的一条记录
Pointcut(切入点):目标对象中,已经增强的方法,相当于sql中的查询条件
Advice(通知/增强):增强的代
- method before advice 前置增强
- after returning advice 后置增强
- method interceptor 环绕增强(相当于方法拦截器)
- throws advice 异常捕获增强
Target(目标对象):被代理对象
Weaving(织入):通知+切入点,将通知织入到切入点的过程叫织入。
Proxy(代理):将通知织入到目标对象之后,形成代理对象
aspect(切面):切入点+通知
Aspectj注解参数解释:
pointcut方法注解上的
execution( * com.test.service.impl.. * . * (..))
参数解释:
- 第一个 * 代表返回值类型,
- 第一个..代表当前包和子包,
- 第二三个 * 分别代表所有类和所有方法,
- 第二个..代表任意参数
AOP准备工作包:4+2+2
- 4+2就是spring的基本包以及commons logging/log4j
- 2:spring的aop包:spring-aop.jar + spring-aspects.jar
- 第三方包:aopalliancce.jar / aspectj.weaver
准备目标对象
1 | package pers.sfl.service; |
准备通知
1 | public class Myadvice{ |
配置进行织入
将通知织入目标对象中
在applicationContext.xml中导入新的约束,aop约束
配置目标对象
1
2
3
4<!-- 配置目标对象 -->
<bean name="userService" class="pers.sfl.service.UserServiceImpl"></bean>
<!-- 配置通知对象 -->
<bean name="myAdvice" class="pers.sfl.aspect.Myadvice"></bean>配置将通知织入到目标对象
- 配置切入点:
public void pers.sfl.service.UserServiceImpl.save()
void pers.sfl.service.UserServiceImpl.save()
——public 忽略可以不用写* pers.sfl.service.UserServiceImpl.save()
——对返回值不做要求,无论以什么类型返回都可以进行切入* pers.sfl.service.UserServiceImpl.*()
——不仅只对save()方法,对此类中所有的方法进行切入,不考虑返回值类型(必须是无参方法)* pers.sfl.service.*ServiceImpl.*(..)
——不仅只对save()方法,对类中所有的方法进行切入,不考虑返回值类型(不考虑是否有参数),且不考虑是哪个class类* cn.itcast.service..*ServiceImpl.*(..)
——一个.是找service包下的类,两个.还会找其子类,一般不常用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<aop:config>
<!-- 配置切入点在spring中,切点是一个表达式。你想增强哪个方法你就切哪个方法 -->
<aop:pointcut expression="execution(* pers.sfl.service.*ServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAdvice">
<!-- 指定名为before()的方法作为前置通知 -->
<aop:before method="before" pointcut-ref="pc"/>
<!--后置通知 (出现异常则不执行)-->
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pc"/>
<!-- 异常拦截通知 -->
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<!-- 后置通知(无论有没有异常都执行) -->
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
<!-- 切记:method里的内容不要加括号,会报错!!!!会报一个Failed to load ApplicationContext-->- 配置切入点:
注解式AOP配置
在配置文件中开启注解配置AOP
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在通知类上加入注解@Aspect
(表示此类是一个通知类)
1 |
|
方法上加入注解
@Before
加上注解不行,你还得告诉spring,你往哪个方法织入,就可以这样写,表达式+方法。
@Before("execution(* pers.sfl.service.*ServiceImpl.*(..))")
因为每个方法都需要复制,不符合开发习惯,造成代码冗余,我们可以这样:
1
2
3
4
5
6
7
8
9
10//定义一个公共方法,供其他方法调用,
//方法头上需要配置@Pointcut注解来提示此方法为切入点
//然后在改造一下其他的方法:
public void controllerAspect(){
}
例子
- around方法需要有返回值,不然会报错
aournd方法不能try catch, 需要抛出异常,否则spring事务回滚失效。
1 | package pers.sfl.annotation; |
事务
什么是事务:
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.
事务特性:ACID
原子性 :强调事务的不可分割.
一致性 :事务的执行的前后数据的完整性保持一致.
隔离性 :一个事务执行的过程中,不应该受到其他事务的干扰
持久性 :事务一旦结束,数据就持久到数据库
安全性
如果不考虑隔离性引发安全性问题:
脏读 :一个事务读到了另一个事务的未提交的数据
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
虚幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.
事务隔离级别
解决读问题:设置事务隔离级别
未提交读 :脏读,不可重复读,虚读都有可能发生
已提交读 :避免脏读。但是不可重复读和虚读有可能发生
可重复读 :避免脏读和不可重复读.但是虚读有可能发生.
串行化的 :避免以上所有读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
spring操作事务
因为在不同平台上,操纵事务的代码各不相同,spring提供了一个接口 PlatformTransactionManager接口
JDBC平台:DataSourceTransactionManager
Hibernate平台:HibernateTransactionManager
注意:在spring中玩事务管理最为核心的对象就是TransactionManager
spring管理事务的属性介绍
事务的隔离级别:
- 读未提交
- 读已提交
- 可重复读
- 串行化
是否只读:
true 只读
false 可操纵
事务传播行为(propagation behavior):指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA与methodB不在同一个类中,methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
事务传播行为
PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
可以把事务想像成一个胶囊,在这个场景下方法B用的是方法A产生的胶囊(事务)。
举例有两个方法:
1 |
|
单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
举例有两个方法:
1 |
|
单纯的调用methodB时,methodB方法是非事务的执行的。
当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
1 |
|
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
PROPAGATION_REQUIRES_NEW
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。
它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。
1 |
|
当调用
1 | main{ |
相当于调用
1 | main(){ |
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了
PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
单独执行方法Bw无事务
执行方法A,运行到方法B的时候,会挂起事务A,然后以非事务的方式运行方法B.
PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。
PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
这里关键是嵌套执行。
1 |
|
如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:
1 | main(){ |
当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
xml配置aop事务
导包+新约束
加起来一共配置过四个命名空间:
beans:最基本
context: 读取properties配置
aop:配置aop
tx:配置事务通知
配置事务通知
1
2
3
4
5<!-- 事务核心管理器,封装了所有事务操作,依赖于连接池 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes
<!--以方法为单位,指定方法应用什么事务属性
isolation:隔离级别
propagation:传播行为
read-only:是否只读
-->
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>配置事务织入
1
2
3
4
5
6
7
8
9
10<!-- 配置事务织入 -->
<aop:config>
<!--配置切点表达式-->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="txpc"/>
<!--配置切面:通知+切点
advice-ref:通知的名称
pointcut-ref:切点的名称
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txpc"/>
</aop:config>
Spring注解配置aop事务
之前的导包+tx约束是不变的~
开启注解事务
1
2<!-- 开启使用注解配置事务 -->
<tx:annotation-driven/>使用注解
嫌麻烦,可以在类头上加入该注解,如果有的方法需要只读,可在添加在方法上
1
2
3
4
public void teansfer(Interger from, Integer to, Double money){
//....
}