zoukankan      html  css  js  c++  java
  • Spring(四)事务管理

    1 事务的回顾

      1.1 什么是事务

      事务是针对数据库的一组操作,事务的语句要么都执行,要么都不执行

      1.2 事务特性

      四大特性:ACID

        原子性:整体

        一致性:完整

        隔离性:并发

        持久性:结果

      1.3 事务的隔离问题

      脏读:一个事务读取到另一个事务未提交的数据。

      不可重复读:一个事物读取到另一个事务已经提交的数据(update)。

      虚读(幻读):一个事务读取到另一个事务已经提交的数据(insert)。

      1.4 事务的隔离级别

      read uncommitted:读未提交。存在上面的3个问题

           read committed:读已提交。解决脏读,存在后2个问题

           repeatable read:可重复读。解决:脏读、不可重复读,存在最后1个问题

           serializable:串行化。都解决,相当于单事务。

      

      1.5 事务的操作  

      当ABCD为一个事务时:

    Connection conn = null;
    try{
      conn = ...;
      conn.setAutoCommit(false);
      A
      B
      C
      D
      conn.commit();
    } catch(){
      conn.rollback();
    }

      当AB为必须事务,CD为可选事务时

    Connection conn = null;
    Savepoint savepoint = null;  //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
    try{
      conn = ...;
      conn.setAutoCommit(false);
      A
      B
      savepoint = conn.setSavepoint();
      C
      D
      conn.commit();
    } catch(){
      if(savepoint != null){   //CD异常
         // 回滚到CD之前
         conn.rollback(savepoint);
         // 提交AB
         conn.commit();
      } else{   //AB异常
         // 回滚AB
         conn.rollback();
      }
    }

      1.6 Spring事务管理的三个核心接口

      了解即可,因为在实际开发中一般不会手动地管理事务。

      

      1.6.1 PlatformTransactionManager  

        平台事务管理器,用于管理事务。进行事务配置时,必须配置事务管理器。该接口中提供了三个事务操作方法:

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

      1.6.2 TransactionDefinition

        事务详情(事务定义、事务属性),spring用于确定事务具体详情,进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。

        

        传播行为:在两个业务之间如何共享事务。

        PROPAGATION_REQUIRED,必须【默认值】。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将创建一个新的事务。

        PROPAGATION_SUPPORTS,支持。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将以非事务执行。

        PROPAGATION_MANDATORY,强制。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将抛异常。

        PROPAGATION_REQUIRES_NEW,必须新的。 如果A有事务,将A的事务挂起,B创建一个新的事务。如果A没有事务,B创建一个新的事务。

        PROPAGATION_NOT_SUPPORTED,不支持。如果A有事务,将A的事务挂起,B将以非事务执行。如果A没有事务,B将以非事务执行。

        PROPAGATION_NEVER,从不。如果A有事务,B将抛异常。如果A没有事务,B将以非事务执行。

        PROPAGATION_NESTED,嵌套。A和B底层采用保存点机制,形成嵌套事务。

      1.6.3  TransactionStatus

        事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。我们不必关心这个,因为这是spring内部操作的事情。

        

    2 搭建环境

      

      2.1 创建表

      创建数据库spring_transaction,并在数据库下创建表t_account,插入两条记录。  

    create database spring_transaction;
    use spring_transaction;
    create table t_account(
      id int(10) primary key auto_increment,
      username varchar(50),
      money int(10)
    );
    insert into t_account(username,money) values('jack','10000');
    insert into t_account(username,money) values('rose','10000');    

      

      2.2 导入jar包

      创建项目,并且导入需要的jar包:

      

      2.3 dao层

       AccountDao.java

    public interface AccountDao {
        public void in(String inner,Integer money);
        public void out(String outer,Integer money);
    }

      AccountDaoImpl.java

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        @Override
        public void in(String inner, Integer money) {
            String sql = "update t_account set money=money+? where username=?";
            Object[] args = {money,inner};
            this.getJdbcTemplate().update(sql, args);
        }
        @Override
        public void out(String outer, Integer money) {
            String sql = "update t_account set money=money-? where username=?";
            Object[] args = {money,outer};
            this.getJdbcTemplate().update(sql,args);
        }
    }

       2.4 service层

      AccountService.java

    public interface AccountService {
        public void transfer(String outer,String inner,Integer money);
    }

      AccountServiceImpl.java

    public class AccountServiceImpl implements AccountService {
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        @Override
        public void transfer(String outer,String inner,Integer money) {
            accountDao.in(inner, money);
            accountDao.out(outer, money);
        }
    }

      2.5 Spring配置文件

      applicationcontext.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                  http://www.springframework.org/schema/beans/spring-beans.xsd">
            <!-- 配置DataSource的基本信息,需要注入四大参数 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1122"></property>
        </bean>
        
            <!-- 创建AccountDao实例对象   需要注入dataSource -->
        <bean id="accountDao" class="com.tcxpz.spring_pro11.transaction.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
            <!-- 创建AccountService实例对象   需要注入accountDao -->
        <bean id="accountService" class="com.tcxpz.spring_pro11.transaction.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    </beans>

      note:因为我们在AccountDaoImpl中继承了JdbcDaoSupport这个类,所以我们只用往AccountDaoImpl中注入dataSource,

        AccountDaoImpl就可以通过this.getJdbcTemplate()方法获得JdbcTemplate实例对象并使用。

        如果没有继承JdbcDaoSupport,那就需要我们在Spring中配置JdbcTemplate实例对象,并注入AccountDaoImpl

      2.6 测试

    public class TestTransaction {
        @Test
        public void demo(){
            String xmlPath = "applicationContext.xml";
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            AccountService accountService = (AccountService) applicationContext.getBean("accountService");
            accountService.transfer("jack", "rose", 1000);
        }
    }

      测试结果:

      

      如果我们在service层模拟比如断电之类的出错情况。

      

      控制台报错:

      

      再看看数据库,转入账户增加了,而转出账户没有减少,这就是因为没有将转入转出当成一个事务进行管理。

      

    3 手动管理事务(了解)

      spring底层使用 TransactionTemplate 事务模板对事务进行管理。我们的service层拿到这个TransactionTemplate 事务模板就可以开启、提交、回滚事务了。

      所以我们的Spring需要配置事务模板,并将模板注入给service层

      而我们的TransactionTemplate 事务模板又依赖于事务管理器,所以Spring还要向TransactionTemplate 注入DataSourceTransactionManager对象

      而事务管理器又要依赖于数据源,所以Spring还要向DataSourceTransactionManager注入DataSource对象

      

       3.1 创建表

      与上一个项目相同

       

      3.2 导入jar包

      与上一个项目相同

      

      3.3 dao层

      与上一个项目相同

      AccountDao.java

    public interface AccountDao {
        public void in(String inner,Integer money);
        public void out(String outer,Integer money);
    }

       AccountDaoImpl.java

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        @Override
        public void in(String inner, Integer money) {
            String sql = "update t_count set money=money+? where username=?";
            Object[] args = {money,inner};
            this.getJdbcTemplate().update(sql, args);
        }
    
        @Override
        public void out(String outer, Integer money) {
            String sql = "update t_count set money=money-? where username=?";
            Object[] args = {money,outer};
            this.getJdbcTemplate().update(sql,args);
        }
    }

      3.4 service层

      要在service层对事务进行管理,主要是要修改service层的AccountServiceImpl类。

      AccountService.java

    public interface AccountService {
        public void transfer(String outer,String inner,Integer money);
    }

      AccountServiceImpl.java

    public class AccountServiceImpl implements AccountService {
        //向service层注入accountDao对象
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        ////向service层注入transactionTemplate对象,用于对事务进行管理
        private TransactionTemplate transactionTemplate;
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
        @Override
        public void transfer(final String outer,final String inner,final Integer money) {
            transactionTemplate.execute(new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus status) {
                    accountDao.in(inner, money);
                    accountDao.out(outer, money);
                    return null;
                }
            });        
        }
    }

       3.5 Spring配置文件

      

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                  http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 配置DataSource的基本信息,需要注入四大参数 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1122"></property>
        </bean>
        <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate -->
        <bean id="accountDao" class="com.tcxpz.spring_pro10.transaction.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 -->
        <bean id="accountService" class="com.tcxpz.spring_pro10.transaction.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
            <property name="transactionTemplate" ref="transactionTemplate"></property>
        </bean>
        
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="transactionManager"></property>
        </bean>
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    </beans>

      3.6 测试

    public class TestTransaction {
        @Test
        public void demo(){
            String xmlPath="applicationContext.xml";
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            AccountService accountService = (AccountService) applicationContext.getBean("accountService");
            accountService.transfer("jack", "rose", 1000);
        }
    } 

      测试结果:

       

      如果我们在service层模拟比如断电之类的出错情况。

      

      控制台报错:

      

      再看一下数据库,表中的数据没有发生变化。说明我们转入转出看作是一个事物了

      

     4 TransactionProxyFactoryBean方式:半自动

      上面的项目中,我们需要在service层开启事务,重写doInTransaction()方法,并且将我们的业务操作嵌在中间。

      下面我们要使用TransactionProxyFactoryBean生成代理,它的优势在于代码中无须关注事务逻辑,而是交给Spring容器进行事务控制

      

      4.1 创建表  

      与上一个项目相同

      

      4.2 导入jar包

      与上一个项目相同

      

      4.3 dao层

      与上一个项目相同

      AccountDao.java

    public interface AccountDao {
        public void in(String inner,Integer money);
        public void out(String outer,Integer money);
    }

      AccountDaoImpl.java

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        @Override
        public void in(String inner, Integer money) {
            String sql = "update t_account set money=money+? where username=?";
            Object[] args = {money,inner};
            this.getJdbcTemplate().update(sql, args);
        }
        @Override
        public void out(String outer, Integer money) {
            String sql = "update t_account set money=money-? where username=?";
            Object[] args = {money,outer};
            this.getJdbcTemplate().update(sql,args);
        }
    }  

      4.4 service层

      AccountService接口与上一个项目相同

      AccountService.java

    public interface AccountService {
        public void transfer(String outer,String inner,Integer money);
    }

      AccountServiceImpl.java

    public class AccountServiceImpl implements AccountService {
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        @Override
        public void transfer(String outer,String inner,Integer money) {
            accountDao.in(inner, money);
            accountDao.out(outer, money);
        }
    }

      4.5 Spring配置文件

      

      applicationContext.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                  http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 配置DataSource的基本信息,需要注入四大参数 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1122"></property>
        </bean>
        <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate -->
        <bean id="accountDao" class="com.tcxpz.spring_peo12.proxy.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 -->
        <bean id="accountService" class="com.tcxpz.spring_peo12.proxy.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!-- ************************************************************************ -->    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager" ref="transactionManager"></property>
            <property name="target" ref="accountService"></property>
            <property name="proxyInterfaces" value="com.tcxpz.spring_peo12.proxy.AccountService"></property>
            <property name="transactionAttributes">
                <props>
                    <prop key="*">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
                </props>
            </property>
        </bean>
    </beans>

       4.6 测试

      

       附上测试类程序吧,方便日后重现实验结果,测试结果与之前的类似,总之对事务进行了很好的管理

    public class TestTransaction {
        @Test
        public void demo(){
            String xmlPath = "applicationContext.xml";
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            AccountService accountService = (AccountService) applicationContext.getBean("accountServiceProxy");
            accountService.transfer("jack", "rose", 1000);
        }
    }

    5 Spring AOP

      5.1 基于XML方式

      通过xml配置的方式,可以在applicationContext.xml中决定测试程序得到的是目标对象还是代理对象,而不用在测试类中修改getBean中的参数

      使用TransactionProxyFactoryBean实现声明式事务管理的方式的缺点是配置文件过于臃肿,难以阅读。因此Spring提供了tx/AOP配置的声明式事务管理方式,实际开发中最常用。

      只需修改Spring配置文件applicationContext.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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                  http://www.springframework.org/schema/beans/spring-beans.xsd
                                  http://www.springframework.org/schema/aop 
                                  http://www.springframework.org/schema/aop/spring-aop.xsd
                                  http://www.springframework.org/schema/context 
                                  http://www.springframework.org/schema/context/spring-context.xsd
                                  http://www.springframework.org/schema/tx 
                                  http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!-- 配置DataSource的基本信息,需要注入四大参数 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1122"></property>
        </bean>
        <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate -->
        <bean id="accountDao" class="com.tcxpz.spring_peo13.proxy.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 -->
        <bean id="accountService" class="com.tcxpz.spring_peo13.proxy.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!-- ************************************************************************ -->    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- <tx:method> 给切入点方法添加事务详情 name:方法名称, *表示任意方法名称, save* 以save开头 propagation 
                    : 设置传播行为 isolation : 隔离级别 read-only:是否只读 -->
                <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
                    read-only="false" />
            </tx:attributes>
        </tx:advice>
        <!--  aop 编写,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
        <aop:config>
            <!-- 切入点 -->
            <aop:pointcut expression="execution(* com.tcxpz.spring_peo13.proxy.*.*(..))"
                id="txPointCut" />
            <!-- 切面:将切入点与通知整合 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
        </aop:config>
    </beans>

       测试结果与上面的项目相同,总之,对事务进行了很好的管理。

       5.2 基于Annotation方式

      Spring的声明式事务管理还可以通过Annotation注解的方式,这种方式非常简单,我们需要做以下两件事:

      (1) 在Spring容器中注册驱动

      (2) 在需要使用事务的业务类或者方法上添加注解@Transactional,这种方式的事务详情是通过@Transactional的参数进行配置的。

      

      

      applicationContext.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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                  http://www.springframework.org/schema/beans/spring-beans.xsd
                                  http://www.springframework.org/schema/aop 
                                  http://www.springframework.org/schema/aop/spring-aop.xsd
                                  http://www.springframework.org/schema/context 
                                  http://www.springframework.org/schema/context/spring-context.xsd
                                  http://www.springframework.org/schema/tx 
                                  http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!-- 配置DataSource的基本信息,需要注入四大参数 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property>
            <property name="user" value="root"></property>
            <property name="password" value="1122"></property>
        </bean>
        <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate -->
        <bean id="accountDao" class="com.tcxpz.spring_peo13.proxy.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 -->
        <bean id="accountService" class="com.tcxpz.spring_peo13.proxy.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!-- ************************************************************************ -->    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>

    总结:

      本篇博客主要讲了Spring事务管理,首先讲解了Spring事务管理的核心接口,这个只需要稍稍了解就行,毕竟后面用不着

    然后通过银行转账的案例,分别介绍了没有进行事务管理,手动方式进行事务管理,基于TransactionProxyFactoryBean的事务管理和基于AOP的声明式事务管理几种方式。

    其中,基于AOP的声明式事务管理又有xml和annotation两种方式,这个是最重要的,必须掌握。

  • 相关阅读:
    Xaml下
    Xmal
    ItemTemplate
    spring aop
    Struts2
    jsp的标签库
    数据库连接池
    request初探
    kubectl 命令大全
    win10常见问题归总
  • 原文地址:https://www.cnblogs.com/tcxpz/p/9771580.html
Copyright © 2011-2022 走看看