Spring框架IOC原理

IoC全称Inversion of Control,直译为控制反转。直白的意思即把原来类new的权限交给Spring框架。

在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。

因此,IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。

ApplicationContext的三个常用实现类

  • ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了
  • FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
  • AnnotationConfigApplicationContext:它是用于读取注解创建容器的

核心容器的两个接口引发出的问题

  • ApplicationContext:(单例对象适用,通常采用此接口)它在构建容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中的配置对象。
  • BeanFactory:(多例对象适用)它在构建容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。

Spring对bean的管理细节

创建bean的三种方式

  • 第一种方式,使用默认构造函数创建,在Spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建,例如:
<bean id="accountService" class="cn.whz.service.impl.AccountServiceImpl"></bean>
  • 第二种方式,使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入Spring容器),例如:
<bean id="instanceFactory" class="cn.whz.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
  • 第三种方式,使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入Spring容器),例如:
<bean id="accountService" class="cn.whz.factory.StaticFactory" factory-method="getAccountService"></bean>

bean对象的作用范围(base标签的scope属性)

  • 作用:用于指定bean的作用范围
  • 取值:常用的就是单例的和多例的
    • singleton:单例的(默认值)
    • prototype:多例的
    • request:作用于web应用的请求范围
    • session:作用于web应用的会话范围
    • global- session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session

bean对象的生命周期

  • 单例对象:单例对象的生命周期和容器相同
    • 出生:当容器创建时对象出生
    • 活着:只要容器还在,对象一直活着
    • 死亡:容器销毁,对象消亡
  • 多例对象
    • 出生:当我们使用对象时Spring框架为我们创建
    • 活着:对象只要在使用过程中就一直活着
    • 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收

Spring中的依赖注入

依赖注入:Dependency Injection

IOC的作用:降低程序间的耦合(依赖关系)

依赖关系的管理:以后都交给Spring来维护,在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置文件中说明

依赖关系的维护:就称之为依赖注入

依赖注入

三种能注入的数据

1.基本类型和String

2.其他bean类型(在配置文件中或者注解配置过低bean)

3.复杂类型/集合类型

三种注入方式

1.使用构造函数提供:如果是经常变化的数据,并不适用于该方式注入

  • 使用的标签:constructor-arg

  • 标签出现的位置:bean标签的内部

  • 标签中的属性:

    • type:用于指定要注入的数据的标签类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引位置是从0开始
    • name:用于给指定构造函数中指定的名称的参数赋值 (常用的)

    以上三个属性用于指定给构造函数中哪个参数赋值

    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他的bean类型的数据。它指的就是在Spring的IOC核心容器中出现过的bean对象
  • 优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功

  • 弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

2.使用set方法提供(更常用这种方式)

  • 使用的标签:property

  • 标签出现的位置:bean标签的内部

  • 标签中的属性:

    • name:用于指定注定时所调用的set方法名称

    • value:用于提供基本类型和String类型的数据

    • ref:用于指定其他的bean类型的数据。它指的就是在Spring的IOC核心容器中出现过的bean对象

  • 优势:创建对象时,没有明确的限制,可以直接使用默认构造函数

  • 弊端:如果有某个成员必须有值,则获取对象是有可能set方法没有执行

3.使用注解提供

复杂类型的注入/集合类型的注入

  • 用于给List/Array/Set结构集合注入的标签:list、array、set
  • 用于给Map/Properties结构集合注入的标签:map、properties
  • 结构相同,标签可以呼唤

注解注入的使用

用于创建对象的注解:他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的

@Component
  • 作用:用于把当前类对象存入Spring容器中

  • 属性:

    • value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写
@Controller:一般用在表现层
@Service:一般用在业务层
@Repository:一般用在持久层

以上三个注解他们的作用和属性与@Component是一模一样的,他们三个是Spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

用于注入数据的注解:他们的作用就和在XML配置文件中的<bean>标签中写一个<property>标签是一样的

@Autowired
  • 作用:自动按照类型注入。

    注意事项:

    只要容器中有唯一的一个bean对象类型和要注入的类型变量匹配,就可以注入成功。
    如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错。

    如果IOC容器中有多个类型匹配时:会根据变量名称,在找出的类中寻找继续查找对应的变量名

  • 出现位置:可以是变量上,也可以是方法上

  • 细节:在使用注解注入时,set方法就不是必须的了

@Qualifier
  • 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以单独使用。需要在@Autowired注解之后使用,否则会空指针异常
@Resource
  • 作用:直接按照bean的id注入,它可以独立使用
  • 属性:
    • name:用于指定bean的id

以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现

集合类型的注入只能通过XML实现

@Value
  • 作用:用于注入基本类型和String类型的数据。它可以使用Spring中SpEL(也就是Spring的EL表达式)

    SpEL的写法:${表达式}

用于改变作用范围的注解:他们的作用就和在XML配置文件中的<bean>标签中使用scope属性实现的功能是一样的

@Scope
  • 作用:用于指定bean的作用范围
  • 属性:
    • value:指定范围的取值。常用取值:singleton prototype(默认值:singleton)

和生命周期相关的(了解即可):他们的作用就和在XML配置文件中的<bean>标签中使用init-method和destory-method实现的功能是一样的

@PreDestory:用于指定销毁方法
@PostConstruct:用于指定初始化方法

Spring中的配置类使用

@Configuration

  • 作用:指定当前类是一个配置类
  • 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写,反之则必须写

@Import

  • 作用:用于导入其他的配置类
  • 属性:
    • value[]:用于指定其他配置类的字节码。当我们使用@Import的注解之后,有Import注解配置的类就是父配置类,而导入的都是子配置类

@ComponentScan

  • 作用:用于通过注解指定Spring在创建容器时要扫描的包
  • 属性:
    • value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package="cn.whz"></context:component-scan>

@Bean

  • 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
  • 属性:
    • name:用于指定bean的id。当不写时,默认值是当前方法的名称
  • 细节:当我们使用注解配置方法时,如果方法有参数,Spring框架会去容器中查找有没有可用的bean对象。查找方式和@Autowried注解的作用一样

@Scope

  • 作用:用于指定bean的作用范围,在@Bean注解之后使用
  • 取值:singleton(单例的默认值)、prototype(多例的)

@Qualifier

  • 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以单独使用。可以在@Bean注解后的方法的形参变量名中使用

@PropertySource

  • 作用:用于指定properties文件的位置
  • 属性:
    • value:指定文件的名称和路径。关键字:classpath:表示类路径下

Spring整合JUnit的配置

  • 1.导入Spring整合JUnit的jar坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>X.X.X</version>
</dependency>
  • 2.使用JUnit提供的一个注解把原有的main方法替换了,替换成Spring提供的。
@RunWith(SpringJUnit4ClassRunner.class)
  • 3.告知Spring的运行器,Spring和IOC创建是基于XML还是注解的,并说明位置

@ContextConfiguration

  • 属性:
    • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
    • classes:指定注解类所在的位置
@ContextConfiguration(locations = "classpath:bean.xml")
@ContextConfiguration(classes = SpringConfiguration.class)

注意事项:当我们使用Spring5.X版本的时候,要求JUnit的jar必须是4.12及以上

动态代理

Java原生动态代理(基于接口的动态代理)

全部例子类代码:
接口类:
public interface IProducer {
    void saleProduct(float money);
    void afterService(float money);
}
--------------------------------------------------------------------
实现类:
public class Producer implements IProducer {
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱" + money);
    }

    public void afterService(float money) {
        System.out.println("提供售后服务,并拿到钱" + money);
    }
}
--------------------------------------------------------------------
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Client {

    public static void main(String[] args) {
        IProducer producer = new Producer();
        IProducer proxyproducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    //下文有带注释的代码片段
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float) args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer,money * 0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyproducer.saleProduct(10000f);
    }
}
  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强
  • 分类:
    • 基于接口的动态代理
      • 设计的类:Proxy
      • 提供者:JDK官方
    • 基于子类的动态代理
  • 如果创建代理对象:使用Proxy类的newProxyInstance方法
  • 创建代理对象的要求:被代理类最少事项一个接口,如果没有则不能使用
  • newProxyInstance方法的参数:
    • ClassLoader(类加载器):它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法
    • Class[] (字节码数组):它是用于让代理对象和被代理对象有相同方法。固定写法
    • InvocationHandler:用于提供增强代码,它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的,此接口的实现类都是谁用谁写
      • 重写invoke()方法:
IProducer proxyproducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * @param proxy     代理对象的引用
                     * @param method    当前执行的方法
                     * @param args      当前执行方法所需的参数
                     * @return          和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float) args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer,money * 0.8f);
                        }
                        return returnValue;
                    }
                });

cglib动态代理(基于子类的动态代理)

全部例子类代码:
public class Producer {
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱" + money);
    }

    public void afterService(float money) {
        System.out.println("提供售后服务,并拿到钱" + money);
    }
}
--------------------------------------------------------------------
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();

        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被执行对象的任何方法都会经过该方法
             * @param o         代理对象的引用
             * @param method    当前执行的方法
             * @param objects   当前执行方法所需的参数
             * 以上三个参数和基于接口的动态代理中invoke方法的proxy、method、args参数是一样的
             * @param methodProxy   当前执行方法的代理对象
             * @return          和被代理对象方法有相同的返回值
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float) objects[0];
                //2.判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer,money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(10000f);
    }
}
  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强
  • 分类:
    • 基于接口的动态代理
    • 基于子类的动态代理
      • 涉及的类:Enhancer
      • 提供者:第三方cglib库
  • 如果创建代理对象:使用Enhancer类的create方法
  • 创建代理对象的要求:被代理类不能是最终类
  • create方法的参数:
    • Class(字节码):它是用于指定被代理对象的字节码
    • Callback:用于增强的代码,我们一般写的都是该接口的子接口实现类:MethodInterceptor
Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被执行对象的任何方法都会经过该方法
             * @param o         代理对象的引用
             * @param method    当前执行的方法
             * @param objects   当前执行方法所需的参数
             * 以上三个参数和基于接口的动态代理中invoke方法的proxy、method、args参数是一样的
             * @param methodProxy   当前执行方法的代理对象
             * @return          和被代理对象方法有相同的返回值
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float) objects[0];
                //2.判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer,money * 0.8f);
                }
                return returnValue;
            }
        });

Spring中AOP的概念

AOP概述

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的
基础上,对我们的已有方法进行增强。

AOP的作用及优势

  • 作用:在程序运行期间,不修改源码对已有方法进行增强。
  • 优势:减少重复代码、提高开发效率、维护方便

AOP的实现方式

使用动态代理技术

AOP相关术语

  • Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
  • Pointcut(切入点): 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
  • *Advice(通知/增强): *所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
  • 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
  • Target(目标对象): 代理的目标对象。
  • Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面): 是切入点和通知(引介)的结合。

Spring中基于XML的AOP配置步骤

1、把通知Bean也交给Spring来管理

2、使用aop:config标签表明开始AOP的配置

3、使用aop:aspect标签表明配置切面

  • id属性:是给切面提供一个唯一标识
  • ref属性:是指定通知类bean的id

4、在aop:aspect标签的内部使用对应标签来配置通知的类型

  • 五种通知标签的method、printcut、printcut-ref和arg-names属性用法都相同

配置切入点的表达式:aop:pointcut标签

  • id属性:用于指定表达式的唯一标识
  • expression属性:用于指定表达式内容
  • 此标签写在aop:aspect标签内部只能当前切面使用
  • 此标签写在aop:aspect标签外部就能让所有切面使用,但必须写在使用的切面之前

前置通知:aop:before标签

  • 执行时间:在切入点方法执行之前执行

  • method属性:用于指定类中哪个方法是前置通知

  • printcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

  • pointcut-ref属性:用于引用aop:pointcut标签的id

后置通知:aop:after-returning标签

  • 执行时间:在切入点方法正常执行之后执行

异常通知:aop:after-throwing标签

  • 执行时间:在切入点方法执行产生异常之后执行

最终通知:aop:after标签

  • 执行时间:无论切入点方法是否正常执行它都会在其后面执行

环绕通知:aop:around标签

  • 问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
  • 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
  • 解决:Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在执行程序时,Spring框架会为我们提供该接口的实现类供我们使用。
  • 它是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,例如:
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        Object[] args = pjp.getArgs();
        try {
            System.out.println("前置通知");
            rtValue = pjp.proceed(args);
            System.out.println("后置通知");
            return rtValue;
        } catch (Throwable t) {
            System.out.println("异常通知");
            throw new RuntimeException(t);
        } finally {
            System.out.println("最终通知");
        }
    }

5、切入点表达式的写法

  • 关键字:execution(表达式)
  • 表达式:访问修饰符 返回值 包名.类名.方法名(参数列表)
  • 标准的表达式写法例如:public void cn.whz.service.impl.AccountServiceImpl.saveAccount()
  • 访问修饰符可以省略:void cn.whz.service.impl.AccountServiceImpl.saveAccount()
  • 返回值可以使用通配符,表示任意返回值:* cn.whz.service.impl.AccountServiceImpl.saveAccount()
  • 包名可以使用通配符,表示任意包。但是有几级包就需要写几个:* *.*.*.*.AccountServiceImpl.saveAccount()
  • 包名可以使用..表示当前包及其子包:* *..AccountServiceImpl.saveAccount()*
  • 类名和方法名都可以使用*来实现通配:` ...()`
  • 参数列表:
    • 可以直接写参数类型:
      • 基本类型直接写名称
      • 引用类型写包名.类名的方式,例如java.lang.String
    • 可以使用通配符表示任意类型,但是必须有参数
    • 可以使用..表示有无参数均可,有参数可以是任意类型
  • 全通配写法:* *..*.*(..)*
  • 实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法,例如: cn.whz.service.impl.*.*(..)

Spring中基于注解的AOP的使用

XML文件中的设置

  1. 更改约束文件

  2. 配置Spring创建容器时要扫描的包

    <context:component-scan base-package="cn.whz"></context:component-scan>

  3. 配置Spring开启AOP注解的支持

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

@Aspect

  • 功能:表示当前类时一个切面类

@EnableAspectJAutoProxy

  • 功能:在切面类中使用该标签可以不需要XML文件配置

@Pointcut

  • 功能:用于指定表达式内容,在方法上使用,方法名就是id
@Pointcut("execution(* cn.whz.service.impl.*.*(..))")
private void pt1(){}

前置通知@Before

后置通知@AfterReturning

异常通知@AfterThrowing

最终通知@After

环绕通知@Around

@Around(切入点表达式或者引用切入点表达式方法)
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            //1.获取参数
            Object[] args = pjp.getArgs();
            //2.前置通知
            //3.执行方法
            rtValue = pjp.proceed(args);
            //4.后置通知
            //返回结果
            return rtValue;
        } catch (Throwable t) {
            //5.异常通知
            throw new RuntimeException(t);
        } finally {
            //6.最终通知
        }
    }

注意事项

在Spring框架基于注解的AOP的使用中,后置通知和最终通知执行顺序会对换。

Spring的事务控制

  1. JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
  2. Spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在spring-tx-5.0.3.RELEASE.jar中。
  3. Spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。日常开发中重点是使用配置的方式实现。

Spring中事务控制的API

PlatformTransactionManager

此接口是Spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下:

PlatformTransactionManager接口提供事务操作的方法,包含有3个具体的操作

  • 获取事务状态信息
    • TransactionStatus getTransaction(TransactionDefinition
      definition)
  • 提交事务
    • void commit(TransactionStatus status)
  • 回滚事务
    • void rollback(TransactionStatus status)

我们在开发中都是使用它的实现类,如下:

真正管理事务的对象:

  • org.springframework.jdbc.datasource.DataSourceTransactionManager
    • 使用SpringJDBC或iBatis进行持久化数据时使用
  • org.springframework.orm.hibernate5.HibernateTransactionManager
    • 使用Hibernate版本进行持久化数据时使用

TransactionDefinition

它是事务的定义信息对象,里面有如下方法:

  • 获取事务对象名称:String getName()
  • 获取事务隔离级:int getlsolationLevel()
  • 获取事务传播行为:int getPropagationBehavior()
  • 获取事务超时时间:int getTimeout()
  • 获取事务是否只读:boolean isReadOnly()
    • 读写型事务:增加、删除、修改开启事务
    • 只读型事务:执行查询时,也会开启事务

事务的隔离级别

事务隔离级反映事务提交并发访问时的处理态度

  • ISOLATION_DEFAULT:默认级别,归属下列某一种
  • ISOLATION_READ_UNCOMMITTED:可以读取未提交数据
  • ISOLATION_READ_COMMITTED:只能读取已提交数据,解决脏读问题(Oracle默认级别)
  • ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复读
    问题(MySQL默认级别)
  • ISOLATION_SERIALIZABLE:是否读取其他事务提交添加后的数据,解决幻影读问题

事务的传播行为

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW: 新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。

超时时间

默认值是-1,没有超时限制。如果有,以秒为单位进行设置。

是否是只读事务

建议查询时设置为只读。

TransactionStatus

此接口提供的是事务具体的运行状态,方法介绍如下:

TransactionStatus接口描述了某个时间点上事务对象的状态信息,包含有6个具体的操作:

  • 刷新事务:void flush()
  • 获取是否是否存在存储点:boolean hasSavepoint()
  • 获取事务是否完成:boolean isCompleted()
  • 获取事务是否为新的事务:boolean isNewTransaction()
  • 获取事务是否回滚:boolean isRollbackOnly()
  • 设置事务回滚:void setRollbackOnly()

Spring基于XML的声明式事务控制

1、配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

2、配置事务通知:需要导入事务的约束 tx名称空间和约束,同时也需要AOP的

  • 使用tx:advice标签配置事务通知
    • 属性:
      • id:给事务通知起一个唯一标识
      • transaction-manager:给事务通知提供一个事务管理器引用

3、aop:pointcut标签:配置AOP中的通用切入点表达式

4、aop:advisor标签:建立事务通知和切入点表达式的对应关系

5、配置事务的属性:在事务通知tx:advice标签的内部的tx:attributes标签中的tx:method标签中配置

  • Isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
  • propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询犯法可以选择SUPPORTS
  • read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写
  • timeout:用于指定事务的超时时间。默认值是-1,表示永不超时。如果指定了数值,以秒为单位
  • rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
  • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false" ></tx:method>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>

Spring基于注解的声明式事务控制

1、配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

2、开启Spring对注解事务的支持

<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3、在业务层上加上@Transactional注解(该注解里可以配置事务的属性、事务控制都只能在业务层上)

@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)

Spring基于编程式事务控制

实际开发中不使用这种方式,这种方式会重复写大量的代码和Spring框架应用相悖

1、配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

2、配置事务模版对象(并且可以在其中配置事务的属性)

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"></property>
</bean>

3、在业务层的程序使用

private TransactionTemplate transactionTemplate;

public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
    this.transactionTemplate = transactionTemplate;
}
//在每个方法中都需要重复写
transactionTemplate.execute(new TransactionCallback<Object>() {
    public Object doInTransaction(TransactionStatus transactionStatus) {
        //在这里写上需要运行的程序代码
        return null;
    }
});