12、事务控制

1、编程式事务控制及相关对象

1.1、**编程式事务控制,就是,使用java的API去编写代码 **

1.2、而声明式则是,以配置的方式去配置一些东西,不写代码,通过配置的方式来让Spring去帮我控制事物,好处有很多:解耦合、不写代码的情况下,对业务层的某些方法进行事物控制,这些都可以简化我们的编码

1.3为什么要学编程事务控制?

1.4底层的部分内容依旧采用的是编程式的形式,况且,声明式是基于编程式得来的,不了解原理,如何用的舒服?

1.5、事务控制管理器--PlatfromTransactionManager

1、图解

image-20220330155037148

PlatfromTransactionManager是个接口,只定义行为;Spring对这个接口提供了对应的实现,并且不止一种为什么

不同的技术,例如JDBC原生,要想控制事物需要Connection来控制事物。不同的DAO层实现的技术,控制事物的方式是不一样的。而平台事务管理器内部封装的就是控制事务的方式,他会根据你不同的dao层来为你提供控制事物的方式,根据其他事物管理器的实现去控制事物,这就是接口的作用, 行为规定好,但是实现的方式不一样

1.6、TransactionDefinition,事物的定义信息的对象

image-20220330155335454

内部封装的是一些控制事物的参数的

1、事物的隔离级别

image-20220330155623390

这些参数可以设置到**TransactionDefinition*中

2、传播行为

image-20220330155935610

A业务调用B业务,B业务看A业务有没得事物,有,就加入到A的事物当中,没有,就自己新建一个事物进去

A调B,B看A有事物,那就AB一起用这个事物;如果A没有事物,B就非事物方式运行

A调B,B业务看A有无事物,A有事物,就一起使用;没有,就抛异常

了解即可

当事物方法被另一个事物方法调用的时候,必须指定该事物方法是如何进行传播的,例如,上面的例子,方法可能会继续在现有事物中运行,也有可能新开一个事物继续在自己的事物中运行

1.7、TransactionStatus,事物的状态信息的

image-20220330160940561

1.8、总结

前两个对象需要进行手动配置告诉Spring框架我们配置了一些什么东西,例如我是使用的这个接口的哪个实现类(jdbc还是其他的),事物的参数设置也需要告诉Spring吧,事务的隔离级别是什么,传播行为是什么,都需要通过配置的方式告诉Spring,而第三个参数,是被动封装的,也就相当于前两个参数结果的体现,这个是不需要进行配置的

2、基于XML的声明式的事务控制

2.1、什么是声明式的事务控制?

声明的方式配置的方式去控制事物,在配置文件当中去声明,使用声明式的事物控制去代硬编码的事务控制

2.2、声明式事物处理的作用

image-20220330162031081

  • 声明式事物控制不侵入开发的组件--解耦性,一般业务逻辑是业务层面的,而事物控制,一般是系统层面的,如果将系统代码和业务代码放在一起是不是就耦合死了?

  • 将二者分开通过配置的方式进行解耦合结合,这样就可以在业务运行的时候对其进行一个系统层面的事物的控制了----AOP思想(业务是切点,事物就是通知、增强,而配置就是将二者结合在一起,也就是织入),在业务方法执行时,完成一个事物的控制

  • 在不需要事物管理的时候,在配置文件里面修改一下,即可移除事物,无需改变代码重新编译

  • Spring声明式事务控制底层使用的就是AOP

2.3、声明式事务控制---转账业务的环境搭建

1、老规矩,导包

  • Spring框架的包肯定要要嘛,然后测试集准备好

  • <!-- Spring框架 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.16</version>
    </dependency>
    <!-- junit测试 -->
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
        <!-- test测试集 -->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>5.3.16</version>
        </dependency>
      </dependencies>
    
  • mysql、JdbcTemplate、mysql驱动,c3p0数据源

  • <!-- jdbc的几个包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.16</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.16</version>
    </dependency>
    <!-- 导入mysql数据库 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>
    <!-- c3p0数据源 -->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
    <!-- mysql的驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>
    
  • AOP切面编程的第三方组件

    <!-- AOP的第三方包aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.9</version>
    </dependency>
    

2、简单设计一个转账业务

  • 在xml配置文件中配置数据源对象和JdbcTemplate操纵对象

    • 先引入jdbc的配置文件

      • image-20220330171943045
    • xml配置文件中引入

      • image-20220330172015979
    • 配置数据源

      • <!-- 配置数据源对象 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <!-- 值注入-EL表达式 -->
                <property name="driverClass" value="${jdbc.driver}"></property>
                <property name="jdbcUrl" value="${jdbc.url}"></property>
                <property name="user" value="${jdbc.user}"></property>
                <property name="password" value="${jdbc.password}"></property>
        </bean>
        
    • 配置JdbcTemplate,将数据源注入到内部

      • <!-- JdbcTemplate注入 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                <!-- 数据源对象注入 -->
                <property name="dataSource" ref="dataSource"></property>
        </bean>
        
  • dao层,内部有两个方法,一个转入一个转出

    • /**
       * 转出
       * @param Money 转出多少钱
       * @param outMan 转出人
       */
      void out(double Money,String outMan);
      
      /**
       * 转入
       * @param Money 转入多少钱
       * @param InMan 转入人
       */
      void in(double Money,String InMan);
      
  • dao层实现这俩方法(将其定义为组件@Component)

    • @Autowired
      // JdbcTemplate操纵数据库
      private JdbcTemplate template;
      
      @Override
      public void out(double Money, String outMan) {
          // 转钱操作
          template.update("update bank set money=money-? where name=?",Money,outMan);
          System.out.println("转账成功!");
      }
      
      @Override
      public void in(double Money, String InMan) {
          template.update("update bank set money=money+? where name=?",Money,InMan);
          System.out.println("收款成功!");
      
      }
      
  • Service层设计,调用这俩方法完成转账业务

    • /**
       * 转账业务
       * @param Money 转多少钱
       * @param OutMan 转出人
       * @param InMan 转入人
       * @return
       */
      void transfer(double Money,String OutMan,String InMan);
      
  • 实现类完成调用

    • @Service("userService")
      public class UserServiceImpl implements UserService {
          //调用dao层的方法
          @Autowired
          private UserDao userdao;
          @Override
          public void transfer(double Money, String OutMan, String InMan) {
              userdao.out(Money,OutMan);
              userdao.in(Money,InMan);
          }
      }
      
  • 创建一个假的控制层完成测试

    • public class UserController {
          public static void main(String[] args) {
              ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
              // 获取业务层的对象
              UserService userservice = (UserService) app.getBean("userService");
              userservice.transfer(500,"张三","李四");
          }
      }
      
  • 测试结果

    • image-20220330172234048

    • image-20220330172240882

2.4、业务层对事物的控制

我下面说的都为重点,业务层调用dao层,执行了两条SQL语句,这俩条SQL语句都是一个单独的事物,也就是说这俩玩意儿并没有在同一个事物当中去实现操作,为什么我会这么说?

1、在俩SQL语句中间设置一个自杀异常

  • 设置一个除0异常
    • image-20220330172603868

这个不用我说了吧?第一条SQL正常执行,但是第二个不会执行,因为会报错,这就相当于一个设计上的事物,bug,如何去解决,就涉及到了业务层对事务控制进行一个统一控制

2.5、可以对业务层设计一个事务控制,对所有业务统一操作

也就是说这个时候业务层就是一个连接点,而开启事务,提交事务,回滚事务,可以看做是增强,单独提取到切面类中进行规划和设计

2.6、XML声明式事务控制的实现

1、明确事物

  • 谁是切点? --- (转账方法)
  • 谁是通知?---(事物控制)
  • 配置切面---(1+1)

2、对切点进行配置

Spring对事物的控制已经帮我们封装好了,我们只需要导入对应的命名空间--tx即可使用,之前引入的那个spring-tx的包就是对事物的控制,事务控制是AOP单独封装的一个模块

<!-- 事物控制 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.3.16</version>
</dependency>

使用这个tx命名空间的时候我们需要先配置一个平台事务管理器,什么是平台事物管理器,我们的dao层用的是不是jdbc?mybatis?他们对应的实现类是谁?----DataSourceTransactionManager嘛~

2.1、配置平台事物管理对象(transactionManager)

内部还需要注入一个东西,datasource,为什么?

虽然现在使用的是jdbc模板,但JdbcTemplate实际就是对jdbc的一个简单的封装,而我这个平台管理器(DataSourceTransactionManager),它内部控制事物的时候,是不是通过conection(连接对象)来控制的connection源于谁?是不是要从数据源中获取datasource.getConnection()),所以,从哪儿获取,从哪儿拿。

然后我们这个平台事物控制对象就会从datasource里面去拿这个connection对象去对事物进行一个控制

2.2、配置

<!-- 配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
</bean>

2.3、配置事物的增强(tx:advice)

他的内部需要一个参数(transaction-manager),这还不简单,刚刚配置好的平台事物管理对象就是为这个服务的

<!-- 配置事务-增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"></tx:advice>

2.4、配置事物的属性(tx:attributes)

  • tx是事物
  • attributes是属性

讲述编程式事务控制的时候,我们需要配置平台事务管理对象第二个需要配置什么?是不是事物的一些信息和参数需要告诉spring,虽然spring帮我进行了事物的控制,但是我需要把一些事物的信息告诉他,让他按照我给定的这些信息来帮我进行事物的控制,所以这个地方

tx:attributes,就是用来配置事物的属性的

2.5、tx:method

image-20220330194523363

2.6、配置织入关系

  • 还是要先配置AOP

  • 内部的指定切面换为指定事物增强的方式

    • 说实话我是懵逼的

    • <aop:config>
              <!-- 哪个切点,需要配置什么事物 -->
              <!-- 我只有这一个切面,所以配置单个的 -->
      <aop:advisor advice-ref="txAdvice"              pointcut="execution(*com.waves.service.impl.UserServiceImpl.*(..))"/>
      </aop:config>
      

2.7、目的

我通过业务层去进行事物的控制最终需要达到一个什么效果?

  • 方法依旧是报错的
  • 钱不能少
  • 要么一起执行,要么一起翘der

开始测试

image-20220330195100839

调用方法

image-20220330195202580

image-20220330195209715

钱依旧是这么多,说明咱们的织入关系的确是完成了业务层对于事物的一个控制

3、知识要点

image-20220330195507195

  • 平台事物管理器

    • 告诉spring,我要是用哪套API来进行事物的控制

    • 对应的就是我们配的第一坨

    • <!-- 配置平台事务管理器 -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"></property>
      </bean>
      
  • 事物通知的配置

    • 他引用的使我们配置好的平台事务管理器

    • 事务通知内部配置的参数

    • 每个方法。每个事物,可以配置不同的参数,当然也可以摆烂,直接*

    • image-20220330195756228

    • 任意方法,参数默认

    • <!-- 配置事务-增强 -->
      <tx:advice id="txAdvice" transaction-manager="transactionManager">
              <tx:attributes>
                      <!-- 可以配置多个方法,*号是通配符的意思 -->
                      <tx:method name="*" />
              </tx:attributes>
      </tx:advice>
      
  • 事物AOP的织入

    • 原先AOP要指定一个切面,而现在事物需要指定一个aop:advisor

    • 代表只有一个通知,内部有个切点表达式,这个切点表达式也可以对外进行抽取

    • <!-- 配置织入AOP -->
      <aop:config>
              <!-- 切点表达式抽取 -->
              <aop:pointcut id="pointcut" expression="execution(* com.waves.service.impl.UserServiceImpl.*(..))"></aop:pointcut>
              <!-- 我只有这一个切面,所以配置单个的 -->
              <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
      </aop:config>
      

2.7、基于注解的声明式事物控制

1、配置事物管理器(这个还是需要配置的)

因为需要指定使用的是什么平台的事物管理器

<!-- 配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
</bean>

2、@Transactional()注解增强

内部可以配置参数,使用在方法体上,进行什么样的事务控制

image-20220330201959806

也可以作用在类上,该类下的所有方法都执行这个事务控制

image-20220330202019027

就近原则,如果方法体上已存在@Transactional注解,则按照方法体上的增强来执行

2.8知识要点

image-20220330202823929

image-20220330202829892

posted @ 2022-10-05 20:07  澜璨  阅读(90)  评论(0)    收藏  举报