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两种方式,这个是最重要的,必须掌握。

猜你喜欢

转载自www.cnblogs.com/tcxpz/p/9771580.html
今日推荐