• Spring框架使用备忘


    Spring学习笔记

    Spring体系结构图

    Spring体系结构

    Spring中的IOC和DI

    BeanFactory和ApplicationContext

    BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。

    BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。

    BeanFactory 是初始化 Bean 和调用它们生命周期方法的“吃苦耐劳者”。注意,BeanFactory 只能管理单例(Singleton)Bean 的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean 实例被创建之后便被传给了客户端,容器失去了对它们的引用。

    BeanFactory有着庞大的继承、实现体系,有众多的子接口、实现类。来看一下BeanFactory的基本类体系结构(接口为主):

    Spring工厂类继承关系图

    1、BeanFactory作为一个主接口不继承任何接口,暂且称为一级接口

    2、有3个子接口继承了它,进行功能上的增强。这3个子接口称为二级接口

    3、ConfigurableBeanFactory可以被称为三级接口,对二级接口HierarchicalBeanFactory进行了再次增强,它还继承了另一个外来的接口SingletonBeanRegistry

    4、ConfigurableListableBeanFactory是一个更强大的接口,继承了上述的所有接口,无所不包,称为四级接口

    (这4级接口是BeanFactory的基本接口体系。继续,下面是继承关系的2个抽象类和2个实现类:)

    5、AbstractBeanFactory作为一个抽象类,实现了三级接口ConfigurableBeanFactory大部分功能。

    6、AbstractAutowireCapableBeanFactory同样是抽象类,继承自AbstractBeanFactory,并额外实现了二级接口AutowireCapableBeanFactory

    7、DefaultListableBeanFactory继承自AbstractAutowireCapableBeanFactory,实现了最强大的四级接口ConfigurableListableBeanFactory,并实现了一个外来接口BeanDefinitionRegistry,它并非抽象类。

    8、最后是最强大的XmlBeanFactory,继承自DefaultListableBeanFactory,重写了一些功能,使自己更强大。

    ApplicationContext

    如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现。

    ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。

    二者区别

    1.BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

    BeanFacotry延迟加载,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用 ApplicationContext。

    ApplicationContext则会在上下文启动后预载入所有的单实例Bean。通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

    2.BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。(Applicationcontext比 beanFactory 加入了一些更好使用的功能。而且 beanFactory 的许多功能需要通过编程实现而 Applicationcontext 可以通过配置实现。比如后处理 bean , Applicationcontext 直接配置在配置文件即可而 beanFactory 这要在代码中显示的写出来才可以被容器识别。 )

    3.beanFactory主要是面对与 spring 框架的基础设施,面对 spring 自己。而 Applicationcontex 主要面对与 spring 使用的开发者。基本都会使用 Applicationcontex 并非 beanFactory 。

    ApplicationContext接口的实现类:

    ClassPathXmlApplicationContext
    它是从类的根路径下加载配置文件 推荐使用这种
    FileSystemXmlApplicationContext
    它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
    AnnotationConfigApplicationContext:
    当我们使用注解配置容器对象时,需要使用此类来创建spring 容器。它用来读取注解。

    Spring中基于XML的DI配置方式:

    bean标签

    作用: 用于配置对象让spring来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

    属性:

    • id:给对象在容器中提供一个唯一标识。用于获取对象。

    • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。

    • scope:指定对象的作用范围。

      • singleton :默认值,单例的.

      • prototype :多例的.

      • request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.

      • session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.

      • global session :WEB项目中,应用在Portlet环境.如果没有Portlet环境那么globalSession相当于session.

      • init-method:指定类中的初始化方法名称。

      • destroy-method:指定类中销毁方法名称。

    实例化bean的三种方式:

    • 使用默认无参构造函数
    • spring管理静态工厂--使用静态工厂的方法创建对象
    • spring管理实例工厂--使用实例工厂的方法创建对象

    依赖注入属性的三种情况:

    • 基本类型和String类 constructor-arg标签的name-value pair
    • POJO类型 property标签的name-ref pair
    • 集合类型:
      • List结构的:
        • array, list, set
      • Map结构的:
        • map, entry, props, prop

    在注入集合数据时,只要结构相同,标签可以互换。

    依赖注入的三种方式:

    • 构造函数注入
    • set方法注入
    • 注入集合属性

    bean的作用范围和生命周期

    1. 单例对象:scope="singleton"

    一个应用只有一个对象的实例。它的作用范围就是整个引用。

    生命周期:

    ​ 对象出生:当应用加载,创建容器时,对象就被创建了。

    ​ 对象活着:只要容器在,对象一直活着。

    ​ 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

    1. 多例对象:scope="prototype"

    每次访问对象时,都会重新创建对象实例。

    生命周期:

    ​ 对象出生:当使用对象时,创建新的对象实例。

    ​ 对象活着:只要对象在使用中,就一直活着。

    ​ 对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。

    来一个示例性的XML配置文件:

    <bean id="AccountService" class="cn.whalien.service.AccountServiceImpl"
          scope=""
          init-method=""
          destroy-method="">
    	<property name="" value="" ref=""></property>
    </bean>
    

    Spring中基于注解的DI配置方式:

    我们把注解分为四类:

    • 用于创建对象的

      和一个<bean>的作用差不多

      • @Component

        作用:用于把当前类对象存入Spring容器

        属性:

        ​ value: 用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写

      • @Controller 一般用在表现层

      • @Service 一般用在业务层

      • @Repository 一般用在持久层

      后面三个注解的作用和属性与@Component完全相同;它们三个是Spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。

    • 用于注入数据的

      <bean>中写一个property属性的作用差不多

      • @Autowired

        作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功;如果容器内有多个bean对象类型和要注入的变量类型匹配,则以变量名为id查找容器中的key,如果找到,则匹配成功;否则匹配失败。

        属性:

        • value: BeanFactory用来查找Spring容器中对象的id。默认是类名首字母小写。

        出现位置:可以是构造函数,方法,方法参数,域,注解类型至上。

        @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
        

        注意事项:当使用注解的时候,set方法就不是必须的了。

      • @Qualifier

        作用:在自动按照类型注入的基础上,再按照bean的id注入。它在给字段注入时,不能够单独使用,必须和@Autowired配合使用。但是给方法参数注入时,可以独立使用。

        属性:

        • value: 指定bean的id。

        出现位置:

        @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
        
      • @Resource

        作用:直接按照bean的id注入,但是它也只能注入bean类型的数据。

        属性:

        • name: 注意name属性不能够省略。

        出现位置:

        @Target({TYPE, FIELD, METHOD})
        

        注意一下,以上的三个注解都只能用于注入bean类型的域,遇到基本类型和String类型就没有办法了。而且,集合类型的数据域只能由xml配置文件的方式注入。

      • @Value

        作用:注入基本数据类型和String类型的数据

        属性:

        • value: 用于指定值,可以使用Spring中的SpEL((el表达式,写法:${表达式}))

        出现位置:

        @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
        
    • 用于改变作用范围的

      <bean>中写一个scope属性的作用差不多

      • @Scope

        作用:用于指定bean的作用范围

        属性:

        • value:指定作用域。常用的singleton, prototype
        • scopeName:value的别名
        • proxyMode:
    • 和生命周期相关的

      <bean>中写init-method和destroy-method属性的作用差不多

      • @PreDestroy

        作用:用于指定销毁方法

        出现位置:

        @Target(METHOD)
        
      • @PostConstruct

        作用:用于指定初始化方法

        出现位置:

        @Target(METHOD)
        

        注意以上两者都是直接注解在方法上,因此是不需要属性的。

    ​ 但是,在使用了上述的注解后,我们还是无法完全消除xml配置文件。对于我们自己写的类,我们可以

    ​ 采取Annotation的方式来进行依赖注入,对于第三方依赖包,我们则无法对它们打Annotation。那

    ​ 如果我们想要完全的消除xml配置文件,我们该怎么做呢?接下来说一些Spring新注解:

    • @Configuration

      作用:指定当前类是一个配置类

      细节:当配置类作为AnnotationConfigApplicationContext对象的参数时,该注解可以不写。否则,必须要写该注解

      出现位置:

      @Target(ElementType.TYPE)
      
    • @ComponentScan

      作用:用于通过注解指定spring在创建容器时要扫描的包

      属性:

      • value: 指定创建容器时要扫描的包
      • basePackages: value的别名

      出现位置:

      @Target(ElementType.TYPE)
      
    • @Bean

      作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中

      属性:

      • name:用于指定bean的id。当不写时,默认值是当前方法的名称

      出现位置:

      @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
      

      细节:

      ​ 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对 象,查找的方式和Autowired注解的作用是一样的。

    • @Import

      作用:用于导入其他配置类

      属性:

      • value: 用于指定其他配置类的字节码

        ​ 当我们使用Import注解之后,有Import注解的类是父配置类,而导入的都是子配置类

    • @PropertySource

      作用:用于指定properties文件的位置

      属性:

      • value:指定文件的名称和路径

        ​ 关键字:classpath: 表示类路径下

        ​ file : 表示为文件的绝对路径

      出现位置:

    @Target(ElementType.TYPE)

    
    
    
    ### Spring整合Junit:
    
    #### JUnit的特点:
    
    1. main方法,junit集成了一个main方法,会判断test类中哪些方法有`@Test`注解。然后利用反射调用。
    2. junit不知道我们有没有使用IOC容器,因此也就不会进行读取配置文件/配置类。
    
    
    
    因此,整合有个简单的思路就是,我们把junit的默认main方法给替换掉,实现一个可以加载Spring IOC容器的main方法:
    
    具体操作步骤:
    
    + 导入spring整合junit的jar坐标(spring-test)
    
    + 使用junit提供的`@Runwith`替换原有的`@Runner`,替换成spring提供的
    
    如:
    
    ```java
    @Runwith(SpringJUnit4ClassRunner.class)
    
    • 告知spring的运行器,spring和ioc创建是基于xml还是注释的,并且说明位置。

      • @ContextConfiguration
        • locations:
          • classpath关键字:说明类路径下的xml文件位置
          • classes关键字 :指定注解类所在位置

      当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上

      如:

      @ContextConfiguration(classes = SpringConfiguration.class) 
      
    • @Runwith

      作用:使用另一个Runner替换掉被标注测试类继承的Runner

      属性:

      • value:Class<? extends Runner>

        用来替换被标注类的class对象

      出现位置:

      @Target(ElementType.TYPE)
      

      比如:

      @Runwith(SpringJUnit4ClassRunner.class)
      
    • @ContextConfiguration

      作用:告知spring的运行器,spring和ioc创建是基于xml还是配置类的方式,并说明位置

      属性:

      • locations(Alias for values):

        指定配置文件或者配置类的路径

      出现位置:

      @Target(ElementType.TYPE)
      

      比如:

      @ContextConfiguration(classes = SpringConfiguration.class) 
      

    Spring中的AOP

    Spring中基于XML的AOP配置方式:

    • 导入aop:aspect命名空间(xmlns:aop)

    • 把通知Bean交给spring来管理

    • 开始配置,使用aop:config标签

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

      • id属性:是给切面提供一个唯一标识
      • ref属性:是指定通知类bean的id
    • 在aop:aspect标签的内部使用对应标签来配置通知的类型

      • aop:before:表示配置前置通知

        • method属性:用于指定增强方法

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

          切入点表达式的写法:

          关键字:execution(表达式)

          标注的表达式写法:访问修饰符 返回值 包名.类名.方法名(参数列表)

          比如:

          <aop:before method="logInfo" pointcut="execution(public void cn.whalien.AccountService.saveAccount(cn.whalien.domain.Account))"/>
          

          可以看到上面这种切入点表达式其实还是很复杂的,每一个方法都这么写让人很难受,所以有没有一些比较简单的写法呢?

          • 访问修饰符可以省略

          • 返回值可以使用通配符表示任意返回值

          • 包名可以使用通配符表示任意包(有几级包就需要几个通配符)

          • 使用 .. 代表某个包及其子包

          • 类名和方法名可以使用通配符进行通配

          • 参数列表

            • 基本类型可以直接写名称
            • 引用类型需要写报名.类名的方式 如:java.lang.String
            • 可以使用通配符表示任意类型,但是必须要有参数
            • 可以使用 .. 表示有无参数都可以,有参数可以是任意类型
          • 全通配写法

            * *..*.*(..)

          实际开发中切入表达式的通常写法:

          ​ 切到业务层实现类下的所有方法,比如:

          cn.whalien.service.*.*(..)

      • aop:after-returning:表示后置通知

      • aop:after-throwing:表示异常通知

      • aop:after:表示最终通知

      • aop:around:配置环绕通知

        问题:当我们如上述配置好环绕通知以后,切入点方法没有执行,而通知方法却执行了。

        分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用

        解决:Spring框架为我们提供了一个接口--ProceedingJoinPoint。该接口有一个proceed()方法,此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数。在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。

        spring中的环绕通知,是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

    • aop:pointcut:用于指定切入点表达式

      • id属性:切入点表达式id
      • expression: 指定切入点表达式内容

      需要注意,这个时候需要在通知类型标签中启用pointcut-ref属性。而且当该标签写在aop:pointcut标签内部,只有当前切面可以使用;写在外面的时候,要满足约束要去(即要写在四种通知标签前面,aop:config标签里面。

      给个示例配置文件:

          <aop:config>
              <aop:pointcut id="ptAccountService" expression="execution(* cn.whalien.service.impl.*.*(..))"/>
              <aop:aspect id="transactionAdvice" ref="txManager">
                  <aop:before method="beginTransaction" pointcut-ref="ptAccountService"/>
                  <aop:after-returning method="commit" pointcut-ref="ptAccountService"/>
                  <aop:after-throwing method="rollback" pointcut-ref="ptAccountService"/>
                  <aop:after method="release" pointcut-ref="ptAccountService"/>
                  <aop:around method="aroundTransaction" pointcut-ref="ptAccountService"></aop:around>
              </aop:aspect>
      
          </aop:config>
      

    Spring中基于注解的IOC配置:

    配置方法:
    • 配置spring创建容器时要扫描的包/配置类使用@Configuration, @ComponentScan(或者在xml文件中配置时,<context:component-scan base-package="cn.whalien"/>指定要扫描的包)

    • 配置spring开启注解AOP的支持/配置类使用@EnableAspectJAutoProxy(或者在xml文件中配置时,<aop:aspectj-autoproxy/>打开对AOP注解的支持)

    • 将Java Beans通过注解注入到代码中

    • 给增强类打@Aspect annotation表示为切面类(必须要做的事)

    • 写个辅助方法,打@Pointcut注解(并且通过value值指定切入点表达式),生成一个切入点表达式

    • 给相应的增强方法打对应的通知类型注解:@Before, @AfterReturning, @AfterThrowing, @After,@Around注解。注意,比较早期的版本,会存在异常通知/后置通知与最终通知错位的情况。(解决办法,用比较新的jar包(>=5.2.7),或者用xml文件的方式配置)

    切面类中的通知Annotation执行顺序:

    从spring-aop 5.2.7开始,Spring AOP不再严格按照AspectJ定义的规则来执行advice,而是根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing。

    Advice Ordering

    简单来说就是这样:

    spring-aop>5.2.7

    在spring-aop 5.2.7之前,Spring AOP按照Aspect J定义的顺序来执行Advice:

    Spring的说法:

    What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

    AspectJ 定义的顺序:

    At a particular join point, advice is ordered by precedence.

    A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.

    A piece of before advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join pint if there is no further advice, will run.

    Running after returning advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.

    Running after throwing advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.

    Running after advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.

    简单来说,在spring-aop 5.2.7之前的版本,执行顺序是如下图的:

    正常情况:

    正常情况

    异常情况:

    异常情况

    多个切面的情况:

    多个切面的情况

    当然,官方文档也有说,我们可以使用@Order注解来调整不同类型通知的执行顺序。但其实很容易想到,处理这些细枝末节的东西需要花费我们很长的时间,不如干脆点,写环绕通知@Around来处理。

    Spring中的Jdbc Template和事务管理

    Jdbc操作步骤:

    1. 注册驱动

      Class.forName(..) 推荐

      DriverManager.registerDriver(..)

      System.setProperty(..) 推荐

    2. 获取连接

      Connection conn = DriverManager.getConnection(..)

    3. 创建操作数据库的SQL语句(Statement)

      运行对象Statement负责运行SQL语句。由Connection对象产生。

      Statement st = connection.createStatement();

      Statement接口类还派生出两个接口类PreparedStatement和CallableStatement,这两个接口类对象为我们提供了更加强大的数据訪问功能。

      PreparedStatement能够对SQL语句进行预编译,这样防止了SQL注入,提高了安全性。

        PreparedStatement ps=connection.prepareStatement( "update user set id=? where username=?”);

      sql语句中用 ? 作为通配符,变量值通过参数设入:ps.setObject(1, object);

      而且预编译结果能够存储在PreparedStatement对象中。当多次运行SQL语句时能够提高效率。

      作为Statement的子类,PreparedStatement继承了Statement的全部函数。

      CallableStatement接口

      CallableStatement类继承了PreparedStatement类,他主要用于运行SQL存储过程。

      在JDBC中运行SQL存储过程须要转义。

      JDBC API提供了一个SQL存储过程的转义语法:

      ​ {call[,, ...]}

      procedure-name:是所要运行的SQL存储过程的名字

       [,, ...]:是相相应的SQL存储过程所须要的参数

    4. 运行SQL语句

      运行对象Statement 或 PreparedStatement 提供两个经常使用的方法来运行SQL语句。

        executeQuery(String sql),该方法用于运行实现查询功能的sql语句。返回类型为ResultSet(结果集)。

        如:ResultSet rs =st.executeQuery(sql);

        executeUpdate(String sql),该方法用于运行实现增、删、改功能的sql语句,返回类型为int,即受影响的行数。

        如:int flag = st.executeUpdate(sql);

    5. 处理运行结果

      ResultSet对象

        ResultSet对象负责保存Statement运行后所产生的查询结果。

        结果集ResultSet是通过游标来操作的。

        游标就是一个可控制的、能够指向随意一条记录的指针。

        有了这个指针我们就能轻易地指出我们要对结果集中的哪一条记录进行改动、删除,或者要在哪一条记录之前插入数据。一个结果集对象中仅仅包括一个游标。

      另外,借助ResultSetMetaData ,可以将数据表的结构信息都查出来。

        ResultSetMetaData rsmd= resultSet.getMetaData();

    6. 释放资源

      数据库资源不关闭,其占用的内存不会被释放,徒耗资源,影响系统。

    Java 数据持久层总结

    持久层总图

    可以看到,Spring中的JdbcTemplatecomons-dbutils都是对JDBC进行了一层薄薄的封装,使用方法也非常类似。不同的是commons-dbutils中的query方法通过BeanHandler<~>, BeanListHandler<~>决定返回的泛型是list还是单个实体类;而JdbcTemplate中的query方法RowMapper<~>只有BeanPropertyRowMapper<~>一种映射方法,返回的只有list

    ⚠️ Dao层消除重复代码的一种方式

    我们Dao的实现类通常需要写重复代码:

    private JdbcTemplate jt;
    
    private void setJdbcTemplate(JdbcTemplate jt){
        this.jt=jt;
    }
    

    这个时候,我们其实可以考虑实现一个JdbcDaoSupport类,在里面配置JdbcTemplate和其setter, getter方法。然后在所有Dao的实现类里,继承这个JdbcDaoSupport类。就能够消除重复代码。

    嗯,上面这个方法很容易就能想到,强大的Spring提供了这样的支持。我们只用直接导入JdbcDaoSupport类即可。但是需要注意的是,我们自己实现的JdbcDaoSupport类我们可以通过xml和annotation两种方式进行依赖注入,而是用spring提供的依赖,我们就只能够通过xml的方式进行配置好。具体选择使用Spring提供的还是自己实现的JdbcDaoSupport需要根据实际情况选用。

    Spring中的事务控制

    Spring事务控制的一组API
    • PlatformTransactionManager

      此接口是Spring的事务管理器,提供了我们常用的操作事务的方法。

      此接口需要实现的方法主要有:

      // 获取事务状态信息
      TransactionStatus getTransaction(@Nullable TransactionDefinition td) throws TransactionException;
      
      // 提交事务
      void commit(TransactionStatus ts) throws TransactionException;
      
      // 回滚事务
      void rollback(TransactionStatus ts) throws TransactionException;
      

      可以看到PlatformTransactionManager是一个接口,接下来我们看看PlatformTransactionManager的实现类以及其体系结构图。

      • PlatformTransactionManager体系结构图:

    Spring事务管理接口

    ​ 其中DataSourceTransactionManager被用在Spring JDBC和MyBatis中进行数据持久化,HibernateTransactionManager被用在Hibernate中进行数据持久化。

    • TransactionDefinition

      事务的定义信息对象,里面有如下接口定义:

      /**事务的传播行为*/
      default int getPropagationBehavior() {
          return PROPAGATION_REQUIRED;
      }
      
      /**事务的隔离级别*/
      default int getIsolationLevel() {
          return ISOLATION_DEFAULT;
      }
      
      /**事务提交或者回滚超时时间*/
      default int getTimeout() {
          return TIMEOUT_DEFAULT;
      }
      
      /**是否是只读事务*/
      default boolean isReadOnly() {
          return false;
      }
      
      /**获取事务名称*/
      @Nullable
      default String getName() {
          return null;
      }
      
      /**静态构造方法*/
      // Static builder methods
      static TransactionDefinition withDefaults() {
          return StaticTransactionDefinition.INSTANCE;
      }
      
      • 事务的隔离级别

      事务的隔离界别反映的是事务提交并发访问时的处理态度。

      // 默认级别,依据具体的数据库默认行为决定,归属于下列某一种
      int ISOLATION_DEFAULT = -1;
      
      // 可以读取未提交数据
      int ISOLATION_READ_UNCOMMITTED = 1;  // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
      
      // 只能读取已提交数据,解决脏读问题(Oracle 默认级别)
      int ISOLATION_READ_COMMITTED = 2;  // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
      
      // 是否读取其他食物提交修改后的数据,解决不可重复读的问题(MySQL默认级别)
      int ISOLATION_REPEATABLE_READ = 4;  // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
      
      // 是否读取其他事务提交添加后的数据,解决幻影读问题
      int ISOLATION_SERIALIZABLE = 8;  // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
      
      • 事务的传播行为
      // 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)。适合于写型事务
      int PROPAGATION_REQUIRED = 0;
      
      // 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。适合于读型事务
      int PROPAGATION_SUPPORTS = 1;
      
      // 使用当前的事务,如果当前没有事务,就抛出异常
      int PROPAGATION_MANDATORY = 2;
      
      // 新建事务,如果当前在事务中,把当前事务挂起。
      int PROPAGATION_REQUIRES_NEW = 3;
      
      // 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
      int PROPAGATION_NOT_SUPPORTED = 4;
      
      // 以非事务方式运行,如果当前存在事务,抛出异常
      int PROPAGATION_NEVER = 5;
      
      // 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
      int PROPAGATION_NESTED = 6;
      
      • 事务的超时时间

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

      int TIMEOUT_DEFAULT = -1;
      
      • 是否是只读事务

      查询事务可以设置为只读事务。

    • TransactionStatus

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

      // 刷新事务
      void flush();
      
      // 获取是否存在存储点
      boolean hasSavePoint();
      
      // 获取事务是否完成
      boolean isCompleted();
      
      // 获取事务是否为新事务
      boolean isNewTransaction();
      
      // 获取事务是否回滚
      boolean isRollbackOnly();
      
      // 设置事务回滚
      void setRollbackOnly();
      
    Spring基于XML的声明式事务控制

    操作步骤:

    1. 导入tx和aop的名称空间和约束

    2. 配置事务管理器

      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"/>
          </bean>
      
    3. 配置事务的通知

      <tx:advice id="txAdvice" transaction-manager="txManager"/>
      
    4. 配置aop通用的切入点表达式

       <aop:pointcut id="ptAccountService" expression="execution(* cn.whalien.service.impl.*.*(..))"/>
      
    5. 建立事务通知和切入点表达式的对应关系

      <aop:advisor advice-ref="txAdvice" pointcut-ref="ptAccountService"/>
      
    6. 配置事务的属性

      ​ tx:attributes标签是在事务的通知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="txManager">
              <tx:attributes>
                  <tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
                  <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
              </tx:attributes>
          </tx:advice>
      
    Spring基于注解的声明式事务控制

    操作步骤:

    1. 导入tx和aop的名称空间和约束

    2. 配置事务管理器

      使用纯Annotation来做的话,需要新建一个和事务相关的配置类用来获取事务管理器对象。

    3. 开启Spring对注解事务的支持

    XML方式:

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

    Annotation方式:

    @EnableTransactionManagement
    
    1. 在需要事务支持的地方使用@Transactional注解
    Spring编程式事务控制

    了解一下TransactionTemplate类,如何通过TransactionCallBack<Object>进行action.execute(..)的回调。但是,显然,采用编程式事务控制之后,代码中的样板代码又会变多。

    然后你说没事啊,我们用动态代理,AOP注入代码不就能解决了吗,但何必呢?增加了编码复杂度的同时还会使类爆炸。使用框架的目的是简化开发,而不是炫技。

    Reference:

    1. Spring教程IDEA版-4天-2018黑马SSM-02

    2. 你真的确定Spring AOP的执行顺序吗

    3. 基于AspectJ的Spring AOP执行顺序

    4. 记录Spring Boot 2.3.4.RELEASE版注解方式实现AOP和通知的执行顺序

  • 相关阅读:
    2018.9.22 Bubble Cup 11-Finals(Online Mirror,Div.2)
    2018.9.21 Codeforces Round #511(Div.2)
    2018.9.20 Educational Codeforces Round 51
    解题:USACO12OPEN Bookshelf
    解题:CF983B pyramid
    1214. 许可证密钥格式
    1212. 最大连续1的个数
    1270. 勒索信
    1250. 第三大的数
    1218. 补数
  • 原文地址:https://www.cnblogs.com/hwahe/p/14272708.html
Copyright © 2020-2023  润新知