声明式事务复盘

发布于 2021-11-01  1.52k 次阅读


一,原始JDBC的事务操作

无论编程式事务还是声明式事务,底层都封装原生JDBC对事务的操作

 jdbc的所有类在java.sql包中,操作Jdbc通过DriveManager获取Connection连接对象,通过这个连接对象进行各种操作

jdbc操作事务的方法:

  • commit():提交事务
  • rollback():回滚事务
  • setAutoCommit(boolean autoCommit):设置连接是否自动提交事务(默认为true自动提交事务)
  • getAutoCommit():获取当前连接的事务是否为自动提交
  • setTransactionIsolation(int leven):设置事务的隔离基本
  • getTransactionIsolation:获取当前的隔离级别

DBC提供了5中事务隔离级别,它们以常量的形式在Connection接口中定义,除了TRANSACTION_NONE外,其它4种隔离级别 与 MySQL的事务隔离级别一样

  • TRANSACTION_NONE(不支持事务)
  • TRANSACTION_READ_UNCOMMITTED
  • TRANSACTION_READ_COMMITTED
  • TRANSACTION_REPEATABLE_READ
  • TRANSACTION_SERIALIZABLE

例:使用原生JDBC事务测试:

public class JDBC_Transaction {

        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pstmt1 = null;
            PreparedStatement pstmt2 = null;
    
            try {
                //1.获取连接
                conn = JDBCUtils.getConnection();
                //开启事务(手动提交事务)
                conn.setAutoCommit(false);
    
                //2.定义sql
                //2.1 张三 - 500
                String sql1 = "update account set balance = balance - ? where id = ?";
                //2.2 李四 + 500
                String sql2 = "update account set balance = balance + ? where id = ?";
                //3.获取执行sql对象
                pstmt1 = conn.prepareStatement(sql1);
                pstmt2 = conn.prepareStatement(sql2);
                //4. 设置参数
                pstmt1.setDouble(1,500);
                pstmt1.setInt(2,1);
    
                pstmt2.setDouble(1,500);
                pstmt2.setInt(2,2);
                //5.执行sql                pstmt1.executeUpdate();
                // 手动制造异常
                int i = 3/0;
    
                pstmt2.executeUpdate();
                //提交事务                conn.commit();
            } catch (Exception e) {
                //事务回滚
                try {
                    if(conn != null) {
                        conn.rollback();
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }finally {
                JDBCUtils.close(pstmt1,conn);
                JDBCUtils.close(pstmt2,null);
            }

二,编程式事务(无案例)

编程式事务是声明式事务的基础,编程式事务就是通过编写事务代码实现事务的控制,而声明式事务则通过Spring配置事务代码简化代码编写,实现配置约定大于代码编写

编程式事务控制有几个重要对象:

  • PlatformTransactionManager接口:spring的事务管理器,它里面提供了我们常用的操作事务的方法
  • TransactionDefinition:事务的定义信息对象
  • TransactionStatus接口:提供事务的具体运行状态信息

一,PlatformTransactionManager

spring的事务管理器,它里面提供了我们常用的操作事务的方法

方法 说明
TransactionStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus status) 回滚事务
…… ……

注:PlatformTransactionManger是接口类型,不同的Dao层技术有不同的实现方式,例:JDBC或Mybatis是org.springframework.jdbc.DatasourceTransactionManager来实现此接口,而Hibernate使用org.springframework.orm.hibernate5.HibernateTransactionManager实现,但它们的方法都大同小异

二, TransactionDefinition

事务的定义信息对象,封装事务的相关参数如果隔离级别的设置和获取,事务的传播行为设置和获取

方法
说明
int getIsolationLevel()
获取事务的隔离级别
int getPropogationBehavior()
获取事务的传播行为
int getTimeout()
获取超时时间
boolean isReadOnly()
是否只读
……
……
事务的隔离级别:设置隔离级别可以解决事务并发情况下产生的脏读,重复读,幻读问题
  • ISOLATION_DEFAULT:使用数据库的默认隔离级别
  • ISOLATION_READ_UNCOMMITTED:读未提交,一个事务可以读取另一个事务未提交的数据
  • ISOLATION_READ_COMMITTED:读已提交,一个事务只能读取另一个事务已经提交的数据,可以解决脏读问题
  • ISOLATION_REPEATABLE_READ:不可重复读,一个事务在整个过程中可以多次重复执行某个查询,并返回的记录都相同,可以避免脏读和不可重复读
  • ISOLATION_SERIALIZABLE:串行化,一次只能一个用户执行操作(锁),可以解决幻读问题,但效率太低

事务的传播行为:解决业务方法调用业务方法时事务的同一性问题

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经有一个事务,就加入这个事务(默认)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务的方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前已在事务中,就把事务挂起
  • NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,就抛出异常
  • BESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行REQUIRED类似操作
  • 超时时间:默认为1,没有超时限制
  • 是否只读:建议查询时设置

三,TransactionStatus

设置和获取事务的状态信息

方法
说明
boolean hasSavepoint()
是否存储回滚点
boolean isCompleted()
事务是否完成
boolean isNewTransaction()
是否是新事务
boolean isRollbactOnly()
事务是否回滚
……
……

三,声明式事务

一,声明式事务的概念

Spring的声明式事务就是采用声明式的方式处理事务,声明在在spring的xml配置文件中进行声明,提供spring配置文件声明的方式代替编程式事务,简化操作

声明式事务的作用:

  • 事务管理不侵入开发模块,事务管理本质上是系统层面上的,开发的业务代码不应该和系统代码进行耦合,声明式事务就对两者进行了松耦合
  • 事务管理和维护方便,在需要修改事务时,只需要修改配置,而不需要修改代码

声明式事务底层使用了AOP,通过切面,在程序运行的过程中对业务进行控制

二,基于XML的事务控制

maven依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.0</version>
</dependency>

命名空间:

xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"

配置事务分为三步:

  • 配置事务管理器
  • 配置事务的通知
  • 将事务做为切面通过AOP织入具体业务方法中

一,配置事务管理器

配置事务管理器通过org.springframework.jdbc.datasource.DataSourceTransactionManager类,这个类需要加入DataSource实现

例:

<!--值从properties文件中获取的-->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="${user}"/>
    <property name="password" value="${password}"/>
    <property name="url" value="${url}"/>
    <property name="driverClassName" value="${drive}"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--通过set映入DataSource-->
    <property name="dataSource" ref="datasource"/>
</bean>

二,配置事务通知

在xml配置文件中通过<tx:adrice>标签配置事务通知

例:

<!--
transaction-manager:引入事务管理器
<tx:attributes>:配置具体通知
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
         <tx:method name="*" isolation="READ_UNCOMMITTED" timeout="100" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

事务通知的属性信息设置:声明式事务是简化了编程式事务,在编程式事务中的重要类的配置参数也可以用于声明中( <tx:method>中设置)

  • name:切点的名称,*代表所有
  • isolation:设置事务的管理级别
  • propogation:事务的传播级别
  • timeout:事务的超时时间
  • read-only:是否只读

三,事务的AOP织入

将事务做为切面织入业务方法中

在传统的AOP使用时通过<aop:aspect>来配置切面,但配置事务时,AOP通过了专门的标签<aop:advisor>来进行配置

例:

<!--
<aop:config>:AOP配置
<aop:adviso>:专门用于配置事务
pointcut:切点表达式,将事务织那个具体方法中
-->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service..*(..))"/>
</aop:config>

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

注解配置声明式事务的步骤:

  • 在xml文件中开启包扫描
  • 配置事务管理器(TransactionManager)
  • 开启注解事务驱动
  • 注解实现事务控制

1,前置配置

<!--开启包扫描-->
<context:component-scan base-package="com"/>

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

<!--开启注解事务驱动:transaction-manager加入事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>

2,注解控制

通过@Transactional()配置事务,这个注解替代了xml文件中的通知配置和AOP切点织入

  • 标注注解的方法就为切点
  • 通知配置可以在参数中进行配置

例:

 @Transactional(isolation = Isolation.READ_COMMITTED)
  public void accounts(String inname, String outname, int money){
    da.in(inname,money);
    System.out.println(1/0);
    da.out(outname,money);
}

四,dome

例:一个账户的转入和转出,基于两个客户FQ和WQL的交易(使用JDBCTemplate来进行操作数据库)

一,xml配置

① dao

public class deal implements deal_interface {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //转入
    public int in(String name,int money) {
        String SQL = "update jdbctemplatetest set money=money+? where name=?";
        int i = jdbcTemplate.update(SQL,money,name);
        return i;
    }

    //转出
    public int out(String name,int money) {
        String SQL = "update jdbctemplatetest set money=money-? where name=?";
        int i = jdbcTemplate.update(SQL,money,name);
        return i;
    }
}

② Service

public class tx_service {

    private deal da;
    public tx_service(deal de) {
    this.da=de;
    }

    public void accounts(String inname, String outname, int money){
      da.in(inname,money);
      da.out(outname,money);
  }
}

③ xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--引入外部mysql连接properties的信息文件-->
    <context:property-placeholder location="jdbc.properties"/>

    <!--配置DataSource,使用的是druid实现datasource类-->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="url" value="${url}"/>
        <property name="driverClassName" value="${drive}"/>
    </bean>

    <!--配置JdbcTemplate-->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--dao中的deal类,实现转入和转出-->
    <bean id="deal" class="com.dao.deal"/>

    <!--业务类-->
    <bean id="tx_service" class="com.service.tx_service">
      <constructor-arg name="de" ref="deal"/>
    </bean>

    <!--transaction管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--通知配置-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
             <tx:method name="*" isolation="READ_UNCOMMITTED" timeout="100" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--将事务做为切面进行织入-->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service..*(..))"/>
    </aop:config>
</beans>

④ 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/transaction.xml"})
public class test {

    @Autowired
    tx_service tx;

    @Test
    public void test(){
      tx.accounts("FQ","WQL",1000);
    }
}

二,注解配置

① dao (添加一个@Repository注解)

@Repository
public class deal implements deal_interface {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //转入
    public int in(String name,int money) {
        String SQL = "update jdbctemplatetest set money=money+? where name=?";
        int i = jdbcTemplate.update(SQL,money,name);
        return i;
    }

    //转出
    public int out(String name,int money) {
        String SQL = "update jdbctemplatetest set money=money-? where name=?";
        int i = jdbcTemplate.update(SQL,money,name);
        return i;
    }
}

② Service

@Service
public class tx_ann_service {

    @Autowired
    deal da;

    public void accounts(String inname, String outname, int money){
        da.in(inname,money);
        System.out.println(1/0);
        da.out(outname,money);
    }
}

③ xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--引入外部mysql连接properties的信息文件-->
    <context:property-placeholder location="jdbc.properties"/>

    <!--配置DataSource,使用的是druid实现datasource类-->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="url" value="${url}"/>
        <property name="driverClassName" value="${drive}"/>
    </bean>

    <!--配置JdbcTemplate-->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--transaction管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--transaction注解事务驱动:需要添加事务管理器transaction-manager-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--包扫描-->
    <context:component-scan base-package="com"/>
</beans>

④ 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/transaction_ann.xml"})
public class test {

    @Autowired
    tx_service tx;

    @Test
    public void test(){

      tx.accounts("FQ","WQL",1000);

    }
}

路漫漫其修远兮,吾将上下而求索