7.Spring表达式语言(SpEL)
7.1.SpEL简介
(1)什么是表达式语言
SpEL:Spring Expression Language, Spring 表达式语言
(2)SpEL特点
SpEL是强大的表达式语言。
支持运行时查询、操纵一个对象图功能。
SpEL语言的语法类似于EL,提供了更多的功能。
SpEL是一个基于技术中立的API,允许需要时与其他表达式语言集成。
SpEL与Spring不是直接绑定关系,它可以独立存在,并应用到其它平台
7.2.SpEL基本语法
XML配置文件中使用:#{表达式}
Bean注解中使用:@Value(“#{表达式}”)
引用其他对象属性:#{对象名.属性}
(1)算数运算符
- 算数运算符:+, -, *, /, %, ^
1.xml中使用:#{表达式}
- 写一个student实体类
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
- 然后在applicationContext.xml中配置属性
<bean id="student" class="com.tjetc.domain.Student" > <property name="name" value="#{'张三'}"></property> <property name="age" value="#{10*2}"></property> </bean>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean(Student.class); System.out.println(student);
- 运行结果
Student{name='李四', age=18}
2.Bean注解中使用 :@Value(“#{表达式 }”)
- 在实体类中用@Value(“#{10+8}”)注解赋值,加上@Component纳入spring容器管理
@Component public class Student { @Value("李四") private String name; @Value("#{10+8}") private int age; public Student(String name, int age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
- applicationContext.xml文件,配置扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
- 其他的代码不变
3.以用其他对象的属性:#{对象名.属性}
<bean id="a" class="com.tjetc.domain.A"> <property name="s" value="#{b.firstName}"></property> </bean> <bean id="b" class="com.tjetc.domain.B"> <property name="firstName" value="张"></property> <property name="lastName" value="无忌"></property> </bean>
4.使用类的静态变量:#{T(类的全路径名).静态变量名}
<bean id="a" class="com.tjetc.domain.A"> <property name="s" value="#{b.firstName}"></property> <property name="d" value="#{T(java.lang.Math).PI}"></property> </bean>
5.使用类的静态方法:#{T(类的全路径名).方法名(参数)}
<bean id="a" class="com.tjetc.domain.A"> <property name="s" value="#{b.firstName+' '+b.lastName}"></property> <property name="d" value="#{T(java.lang.Math).max(3.1,12.3)}"></property> </bean>
6.加号还可以作为字符串拼接
<bean id="a" class="com.tjetc.domain.A"> <property name="s" value="#{b.firstName+' '+b.lastName}"></property> </bean> <bean id="b" class="com.tjetc.domain.B"> <property name="firstName" value="张"></property> <property name="lastName" value="无忌"></property> </bean>
7.使用类的非静态方法:#{bean的id.方法名(参数)}
public class B { public int sum() { return 10+20; } }
<bean id="a" class="com.tjetc.domain.A"> <property name="i" value="#{b.sum()}"></property> </bean> <bean id="b" class="com.tjetc.domain.B"></bean>
(2)比较运算符
<,>==,>=,<=,!=,lt(小于),gt(大于),eq(等于),le(小于等于),ge(大于等于)
<property name="b" value="#{1 lt 1}"></property>
A [i=0, d=0.0, s=null, b=false]
(3)逻辑运算符号
and, or, not(!)
<property name="b" value="#{not(1==1 or 2==3)}"></property>
A [i=0, d=0.0, s=null, b=false]
(4)三目运算符
#{条件表达式?’true’:’false’}
<property name="s" value="#{2>1?'大于':'不大于'}"></property>
A [i=0, d=0.0, s=大于, b=false]
(5)正则表达式
表达式 | 说明 |
. | 除了换行符之外的任意字符 |
* | 匹配前面的子表达式零次或多次 |
/…/ | 代表一个模块的开启和结束 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{m} | 正好出现m次 |
{m,} | 至少m次 |
{,n} | 至少n次 |
{m,n} | 至少m次,至多n次 |
\d | 数字 |
\w | 字母数字下划线,单词 |
^ | 匹配输入字符串的开始位置 |
$ | 匹配输入字符串的结束位置 |
\s | 任何空白字符 |
\S | 任何非空白子符 |
\d | 匹配一个数字字符。等价于[0-9] |
\D | 匹配一个非数字字符。等价于[ ^ 0-9] |
\W | 匹配任何非单词字符。 |
- 语法
- #{变量名或值 matches ‘正则表达式’}
- 例子
<bean id="a" class="com.tjetc.domain.A"> <property name="b" value="#{b.firstName matches '1[3578]\d{9}'}"></property> </bean> <bean id="b" class="com.tjetc.domain.B"></bean>
A [i=0, d=0.0, s=大于, b=true]
8.SpringAOP
8.1.SpringAOP简介
(1)什么是AOP
- AOP:Aspect Oriented Programming 面向切面的编程
- (2)AOP与OOP
- AOP:Aspect Oriented Programming 面向切面的编程
- OOP:Object Oriented Programming 面向对象的编程
(3)AOP与OOP的区别
- 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。
- 思想结构不同:OOP是纵向结构,AOP是横向结构。
- 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。
- OOP与AOP联系:
- 两者之间是一个相互补充和完善的关系。
- AOP的优点:
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- Spring IOC容器不依赖AOP,如果不需要可以不导入AOP相关包。
(4)Spring AOP提供了两种模式
- 基于XML的模式
- 基于@AspectJ注解模式
(5)基于Spring 的 AOP,重要应用有哪些
- 用AOP声明性事务代替EJB的企业服务
- 用AOP做日志处理
- 用AOP做权限控制,如Spring Security
(6)AOP中的专业术语
- 连接点Joinpoint
- 方法的位置
- 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。 Spring仅支持方法的连接点。
- 切点Pointcut
- 定位到方法的条件
- 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。但在这为数从多的连接点中,如何定位到某个感兴趣的连接点上?AOP通过“切点”定位特定连接点。
- 增强Advice
- 增强是织入到目标类连接点上的一段程序代码。增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以SPRING所提供的增强接口都是带方位名的:BeforeAdvice,AfterReturningAdivce,throwsAdvice等等。
- 目标对象Target
- 增强逻辑的织入的目标类(被代理的目标类)
- 引介Introduction
- 引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口。通过AOP的引介功能,我们可以动态地为该业务类添加接口的实例逻辑,让业务类成为这个接口的实现类。
- 织入Weaving
- 织入是将增强添加对目标类具体连接点上的过程。
- 根据不同的实现技术,AOP有三种织入方式:
- 编译期织入,这要求使用特殊的JAVA编译器
- 类装载期织入,这要求使用特殊的类加载器;
- 动态代理织入,在运行期为目标类添加增强生成的方式。
- 代理Proxy
- 一个类被AOP增强后,就产出了一个结果类,它是整合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类。所以我们可以采用调用原类相同的方式调用代理类。
- 切面Aspect
- 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SPRINGAOP就是负责实施切面的框架,它将切面所定义的横切逻辑到切面所指定的连接点中。
(6)通知的类型(5种)
- before:前置通知(应用:各种校验)
- 在方法执行前执行,如果通知抛出异常,阻止方法运行
- afterReturning:后置通知(应用:常规数据处理)
- 方法正常返回后执行,如果方法中抛出异常,通知无法执行
- 必须在方法执行后才执行,所以可以获得方法的返回值。
- around:环绕通知(应用:十分强大,可以做任何事情)
- 方法执行前后分别执行,可以阻止方法的执行
- 必须手动执行目标方法
- afterThrowing:抛出异常通知(应用:包装异常信息)
- 方法抛出异常后执行,如果方法没有抛出异常,无法执行
- after:最终通知(应用:清理现场)
- 方法执行完毕后执行,无论方法中是否出现异常
8.2.AOP编程XML方式声明
(1)XML配置文件的方式声明切面
- 添加maven依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.15.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
- 写业务类(不写注解)
public class UserService { public String login(String name){ System.out.println(name+":用户登录--login()..."); return name; } }
- 写切面类
public class TransactionPoint { //增强部分 public void before(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); for (Object arg : args) { System.out.println("参数:"+arg); } System.out.println("前置增强"); } public void afterReturning(){ System.out.println("后置增强"); } public void after(){ System.out.println("最终增强"); } public void afterThrowimg(){ System.out.println("例外增强"); } /* public Object around(ProceedingJoinPoint pjp) throws Throwable { Object proceed = null; try{ System.out.println("环绕增强开始"); proceed = pjp.proceed(); System.out.println("环绕增强结束"); }catch (Exception e){ System.out.println("环绕例外增强"); e.printStackTrace(); }finally { System.out.println("环绕最终增强"); } return proceed; }*/ }
- applicationContext.xml配置bean和切面
<bean id="userService" class = "com.tjetc.service.UserService"></bean> <bean id="transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean> <!-- aop配置 --> <aop:config> <!--配置切面 --> <aop:aspect id="myaspect" ref="transactionPoint"> <!-- 切点 --> <aop:pointcut expression="execution(* com.tjetc.service..*.*(..))" id="mycut"/> <!-- 增强 --> <aop:before method="before" pointcut-ref="mycut"/> <aop:after-returning method="afterReturning" pointcut-ref="mycut"/> <aop:after method="after" pointcut-ref="mycut"/> <aop:after-throwing method="afterThrowimg" pointcut-ref="mycut"/> </aop:aspect> </aop:config>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean(UserService.class); userService.login("LiXiang");
- 运行结果
参数:LiXiang 前置增强 LiXiang:用户登录--login()... 后置增强 最终增强
(2)配置切面
1.切面的普通类:<bean id = "transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean> 2.<aop:config>子节点<aop:aspect id = "myaspect" ref = "personAspect">启动和增强</aop:aspect>
(3)配置切点
1.切点配置的位置 (1)aop:config下,aop:config下所有的切面都能使用该切面 (2)aop:aspect下,只能是该切面能使用该切点 2.<aop:pointcut expression=”execution(* com.tjetc.service..*.*(..))” id=”mycut”>
(4)配置增强
1.增强的配置位置 aop:aspect下 2.增强5个 (1)前置增强 <aop:before method=”before” pointcut-ref=”mycut”> (2)后置增强 <aop:after-returning method=”afterReturning” pointcut-ref=”mycut”> (3)例外增强 <aop:after-throwing method=”afterThrowing” pointcut-ref=”mycut”> (4)最终增强 <aop:after method=”after” pointcut-ref=”mycut”> (5)环绕增强 <aop:around method=”around” pointcut-ref=”mycut”>
(5)后置增强的返回值
- 第一步:在配置文件的aop:after-returning添加属性returning=”变量名”
- 第二步:在切面类的afterReturning(Object 变量名)方法添加参数Object 变量名
- 第三步:测试调用有返回值的方法
<aop:after-returning method="afterReturning" pointcut-ref="mycut" returning="res"/>
public void afterReturning(JoinPoint jp,Object res) { System.out.println("后置通知:res="+res); }
(6)异常增强得到异常对象
- 第一步:在applicationContext.xml配置aop:after-throwing的属性throwing=”ex”
- 第二步:在切面类的afterThrowing(Exception ex)
- 第三步:在业务类的login()方法抛出异常
- 第四步:测试调用login()方法
<aop:after-throwing method="afterThrowing" pointcut-ref="mycut" throwing="ex"/>
public void afterThrowing(Exception ex) { System.out.println("例外通知:ex="+ex); }
(7)在增强里接受参数
- 使用JoinPoint接口的getArgs()接受参数
- 第一步:直接在切面类的增强方法里写JoinPoint jp参数
- 第二步:在方法体写jp.getArgs();得到数组,遍历数组得到每一个参数的值输出.
public void before(JoinPoint jp) { Object[] args = jp.getArgs(); for (Object object : args) { System.out.println("接收参数:"+object); } System.out.println("前置通知"); }
8.3.AOP编程注解方式声明
(1)AOP注解方式编程
@AspectJ是一种风格样式,可以把普通的java类声明为一个切面
- applicationContext.xml中添加AOP命名空间(1+2)
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
- 启动@Aspect注解支持
<aop:aspectj-autoproxy/>
- 启动注解扫包描机制
<context:component-scan base-package="com.tjetc"/>
- 添加aspectjweaver的maven依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
- 写切面类
@Component @Aspect public class TransactionPoint { //切点 @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件 private void anyMethod(){} //方法签名返回void类型 //增强部分 @Before("anyMethod()") public void before(){ System.out.println("前置增强"); } @AfterReturning("anyMethod()") public void afterReturning(){ System.out.println("后置增强"); } @After("anyMethod()") public void after(){ System.out.println("最终增强"); } @AfterThrowing("anyMethod()") public void afterThrowimg(){ System.out.println("例外增强"); } @Around("anyMethod()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知开始"); Object proceed = pjp.proceed(); System.out.println("环绕通知结束"); return proceed; } }
- 写业务类
@Service public class UserService { public void login(){ System.out.println("用户登录--login()..."); } }
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean(UserService.class); userService.login();
- 运行结果
环绕通知开始 前置增强 用户登录--login()... 后置增强 最终增强 环绕通知结束
(2)测试异常
- 将UserService中加入除0异常
@Service public class UserService { public void login(){ System.out.println("用户登录--login()..."); System.out.println(1/0); } }
- 测试运行
前置增强 用户登录--login()... 例外增强 最终增强 Exception in thread "main" java.lang.ArithmeticException: / by zero
没有走后置增强,直接走的例外增强。
(3)环绕增强单独使用
- 切点类中只写around增强
@Component @Aspect public class TransactionPoint { //切点 @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件 private void anyMethod(){} //方法签名返回void类型 @Around("anyMethod()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object proceed = null; try{ System.out.println("环绕增强开始"); proceed = pjp.proceed(); System.out.println("环绕增强结束"); }catch (Exception e){ System.out.println("环绕例外增强"); e.printStackTrace(); }finally { System.out.println("环绕最终增强"); } return proceed; } }
- 测试运行
环绕增强开始 用户登录--login()... 环绕增强结束 环绕最终增强
(4)联合使用pointcut表达式
在一个切面类中可以声明多个切点表达式,把几个切点签名用&& || !连起来使用
@Component @Aspect//切面类=切点+增强 public class TransactionPrint { //切点 @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件 private void anyMethod1(){} //方法签名,返回值void @Pointcut("execution(* com.tjetc.dao..*.*(..))") //定位到连接点的条件 private void anyMethod2(){} //方法签名,返回值void @Pointcut("anyMethod1() || anyMethod2()") //定位到连接点的条件 private void anyMethod(){} //方法签名,返回值void
(5)声明Advice
本类的方法直接写方法名()
@Before("anyMethod()") public void before() { System.out.println("前置增强"); }
非本类的方法写类的全路径名.方法名() (确保方法签名是public能访问的;否则报错)
@Component @Aspect public class Transcation2 { @Before("com.tjetc.aspect.TransactionPrint.anyMethod()") public void before() { System.out.println("前置增强2"); } }
(6)@AfterReturning返回值
第一步:在@AfterReturning添加returning属性retruning=”方法参数的名称”
第二步:在方法里写一个参数,名称是returning属性的值,接收返回值
@AfterReturning(value = "anyMethod()",returning = "result") public void afterReturning(Object result){ System.out.println("返回值:"+result); System.out.println("后置增强"); }
(7)@AfterThrowing异常
当异常发生时异常通知如何得到异常信息?
实现步骤:
第一步:在@AfterThrowing添加属性throwing=”方法参数的名称”
第二步:在方法里写一个参数,名称是throwing属性的值,接收异常对象
@AfterThrowing(value="anyMethod()",throwing="ex") public void afterThrowing(Exception ex) { System.out.println("异常通知,ex="+ex); }
(8)在增强里接收参数
使用JoinPoint接口的getArgs()接收参数
@Before("anyMethod()") public void before(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); for (Object arg : args) { System.out.println("参数:"+arg); } System.out.println("前置增强"); }
8.4.JDK动态代理
*(1)JDK动态代理是java.lang.reflect.包提供的方式,它必须借助一个接口才能产生代理对象,所以要先定义接口,代码如下:
public interface HelloWord { void sayHelloWord(); }
(2)然后提供HelloWord的实现类
public class HelloWordImpl implements HelloWord { @Override public void sayHelloWord() { System.out.println("Hello Word"); } }
(3)这时就可以进行JDK动态代理了,先建立起代理对象和真实服务对象的关系,然后实现代理逻辑,代理类要实现java.lang.reflect.InvocationHandler接口,重写invoke()方法
public class JDKProxyExample implements InvocationHandler { //真实对象 private Object target = null; /** *建立代理对象和真实对象的代理关系,并返回代理对象 * @Param target 真实对象 * @return 代理对象 */ public Object bind(Object target){ this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } /** * 代理方法逻辑 * @param proxy 代理对象 * @param method 代理方法对象 * @param args 当前方法参数 * @return 代理结果返回 * @throws Throwable 异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置增强"); //参数一:真实对象 参数二: 方法参数 Object invoke = method.invoke(target, args); System.out.println("后置增强"); return invoke; } }
**第1步,建立代理对象和真实对象的关系.**这里是使用了bind方法创建代理对象,方法里面首先用类的属性target保存了真实对象,然后通过如下代码建立并生成了代理对象.
Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
其中newProxyInstance方法的三个参数:
第1个:类加载器,我们采用了target本身的类加载器.
第2个:把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现接口下.
第3个:定义实现方法逻辑的代理类.
**第2步,实现逻辑方法.**invoke方法可以实现代理逻辑.
invoke(Object proxy, Method method, Object[] args)
invoke方法的三个参数含义如下:
proxy,代理对象,就是bind方法生成的对象
method:代理方法对象
args:调度方法的参数
当我们使用了代理对象调度方法后,他就会进入到invoke方法里面
Object invoke = method.invoke(target, args);
这行代码相当于调用真实的对象方法,只是通过反射实现而已.
第3步,测试JDK动态代理
public static void main(String[] args) { JDKProxyExample jdk = new JDKProxyExample(); //绑定关系,因为挂在接口上的实现类,所以声明代理对象HelloWord proxy HelloWord bind = (HelloWord) jdk.bind(new HelloWordImpl()); //此时bind是一个代理对象,调用代理方法 bind.sayHelloWord(); }
运行结果:
前置增强 Hello Word 后置增强
此时,在调度打印Hello Word之前和之后都可以加入相关的逻辑,甚至可以步调度Hello Word的打印.
8.5.CGLIB动态代理
JDK动态代理必须提供接口才嫩不过使用,在一些不能提供接口的环境中,只能采用其他的第三方方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只需要一个非抽象类就可以实现动态代理。
(1)我们还是用HelloWordImpl这个实现类,这次不需要接口
public class HelloWordImpl { public void sayHelloWord() { System.out.println("Hello Word"); } }
(2)采用CGLIB动态代理技术,实现MethodInterceptor接口
public class CGLIBProxyExample implements MethodInterceptor { //真实类对象 private HelloWordImpl target = null; public CGLIBProxyExample() { super(); // TODO Auto-generated constructor stub } public CGLIBProxyExample(HelloWordImpl target) { super(); this.target = target; } /** * 生成CGLIB代理对象 */ public HelloWordImpl bind(){ Enhancer enhancer = new Enhancer(); //指定父类,即目标类。 因为cglib原理 子类增强父类,参数为真实类的class对象 enhancer.setSuperclass(HelloWordImpl.class); //设置回掉接口.参数为代理类对象 enhancer.setCallback(this); //生成并返回代理对象 return (HelloWordImpl)enhancer.create(); } /** * @param proxy 代理对象 * @param method 方法 * @param args 方法参数 * @param methodProxy 方法代理 * return 代理逻辑返回 * @throws Throwable 异常 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("前置增强"); Object o=methodProxy.invokeSuper(proxy, args); System.out.println("后置增强"); return o; } } }
这里面用了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置那个类为它的代理类。最后调用create()方法。
(3)测试CGLIB动态代理
public static void main(String[] args) { HelloWordImpl hello = new HelloWordImpl(); HelloWordImpl h = new CGLIBProxyExample(hello).bind(); System.out.println(h); h.sayHelloWord(); }
运行结果:
前置增强 Hello Word 后置增强
掌握了JDK动态代理就很容易掌握CGLIB动态代理,因为二者是相似的。他们都是用getProxy方法生成代理对象的,制定代理的逻辑类。而二者的区别就在于一个要实现接口,一个不需要实现接口。
9.Spring事务管理
9.1.事务的分类
- 本地事务:local transaction 使用单一资源管理器,管理本地资源。
- 全局事务:global transaction 通过事务管理和多种资源管理器,管理多种不同的资源。
- 编程式事务:通过编码方式,开启事务、提交事务、回滚事务。
- 声明性事务:通过xml配置或注解,实现事务管理,Spring AOP 和 EJB都是声明性事务。
- JTA事务:Java Transaction API ,使用javax.transaction.UserTransaction接口,访问多种资源管理器。
- CMT事务:Container Management transaction ,通过容器自动控制事务的开启,提交和回滚。
事务策略接口:PlatformTransactionManager
事务状态接口:TransactionStatus
事务定义接口:TransactionDefinition
9.2.Spring声明性事务
XML管理声明性事务
(1)pom.xml中添加依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> </dependencies>
(2)配置文件db.properties配置数据源
jdbc.driver = com.mysql.jdbc.Driver jdbc.url = jdbc.mysql:///cc jdbc.username = root jdbc.password = 123456
(3)applicationContext.xml文件
<!--配置基本扫描包--> <context:component-scan base-package = "com.tjetc"></context:component-scan> <!--加载db.properties获取四大金刚的值--> <context:properties-placeholder location="classpath:db.properties"/> <!--配置数据源--> <bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name = "driverClassName" value = "${jdbc.driver}"></property> <property name = "url" value = "${jdbc.url}"></property> <property name = "username" value = "${jdbc.username}"></property> <property name = "password" value = "${jdbc.password}"></property> </bean> <!--配置数据源事务管理器--> <bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name ="dataSource" ref = "dataSource"></property> </bean> <!--配置事务增强:tx:advice对目标的哪些方法使用事务增强--> <tx:advice> <tx:attributes> <tx:method name = "add*" propagation = "REQUIRED" rollback-for = "Throwable"/> <tx:method name = "update*" propagation = "REQUIRED" rollback-for = "Throwable"/> <tx:method name = "del*" propagation = "REQUIRED" rollback-for = "Throwable"/> <!--*代表除了上面方法之外的其他方法--> <tx:method name = "*" propagation = "REQUIRED" read-only = "true"/> </tx:attributes> </tx:advice>
tx:method属性
属性 | 是否需要? | 默认值 | 描述 |
name | 是 | 事务属性关联的方法名,通配符(*)可以用来指定一批关联到相同的事务属性的方法。 | |
propagation | 不 | REQUIRED | 事务传播行为 |
isolation | 不 | DEFAULT | 事务隔离级别 |
timeout | 不 | -1 | 事务超时的时间(以秒为单位) |
read-only | 不 | false | 事务是否只读? |
rollback-for | 不 | 将触发进行回滚的Exception(s) |
no-rollback-for | 不 | 不被触发进行回滚的Exception(s) |
(4)Student实体类
public class Student { private int id; private String name; private int age; }
(5)dao层
@Repository public class StudentDao extends JdbcDaoSupport { @Resource private JdbcTemplate jdbcTemplate; public boolean add(Student student){ String sql = "insert into student(name,age) values(?,?)"; int i = jdbcTemplate.update(sql, student.getName(), student.getAge()); System.out.println("受影响的行数:"+i); return i>0; } }
(6)service层
@Service public class StudentService { @Autowired private StudentDao studentDao; public void add(Student student){ studentDao.add(student); System.out.println(1/0); studentDao.add(student); } }
(7)测试代码
public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); StudentService studentService = context.getBean(StudentService.class); studentService.add(new Student("张三",18)); }
注解方式管理声明式事务
(1)pom.xml、db.properties配置文件同上
(2)applicationContext.xml文件配置tx:annotation-driven注解
<!-- 配置扫描包 --> <context:component-scan base-package="com.tjetc"></context:component-scan> <!-- 加载从db.properties取得4大金刚的值 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置数据源事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--tx:annotation-driven 代表可以采用@Transactional注解方式使用事务--> <tx:annotation-driven transaction-manager = "txManager"/>
(3)业务层的类上或者方法上写@Transactional注解
- 写在类上代表类的所有方法都是用事务
- 写在方法上值堆该方法使用事务
@Service // @Transactional(rollbackFor=Throwable.class)//写在类上代表类的所有方法都使用事务 public class StudentService { @Autowired private StudentDao studentDao; @Transactional(rollbackFor=Throwable.class)//写在方法只对该方法使用事务 public void add(Student student) { studentDao.add(student); System.out.println(1/0); studentDao.add(student); throw new ArrayIndexOutOfBoundsException("异常回滚测试..."); } }
(4)测试代码
public static void main(String[] args) { //实例化容器对象 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器得到bean StudentService studentService = context.getBean(StudentService.class); //调用方法 studentService.add(new Student("张三", 20)); }
9.3.事务传播特性
- PROPAGATION_REQUIRED , required , 必须 【默认值】
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将创建一个新的事务。
- PROPAGATION_SUPPORTS ,supports ,支持
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将以非事务执行。
- PROPAGATION_MANDATORY,mandatory ,强制
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将抛异常。
- PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
- 如果A有事务,将A的事务挂起,B创建一个新的事务
- 如果A没有事务,B创建一个新的事务
- PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
- 如果A有事务,将A的事务挂起,B将以非事务执行
- 如果A没有事务,B将以非事务执行
- PROPAGATION_NEVER ,never,从不
- 如果A有事务,B将抛异常
- 如果A没有事务,B将以非事务执行
- PROPAGATION_NESTED ,nested ,嵌套
- A和B底层采用保存点机制,形成嵌套事务。
- 如果当前存在事务,则在嵌套事务内执行。
- 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
@Service public class ServiceA { @Autowired private ServiceB serviceB; @Autowired private StudentDao studentDao; @Transactional(rollbackFor=Throwable.class) public void methodA() { System.out.println("methodA()..."); studentDao.add(new Student("李四", 21)); serviceB.methodB(); } }
@Service public class ServiceB { @Autowired private StudentDao studentDao; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public void methodB() { System.out.println("methodB()..."); studentDao.add(new Student("赵六", 22)); } }
事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。
9.4.事务的四个关键属性
- 原子性:事务时一个原子操作,有一系列动作完成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性:一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
- 隔离性:可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。
- 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中
9.5.事务隔离级别
- Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
- Read Commited:读已提交数据(会出现不可重复读和幻读)
- Repeatable Read:可重复读(会出现幻读)
- Serializable:串行化