Spring学习笔记-事务管理

Spring支持两种事务管理的方式:

编程式的事务管理,在实际应用中很少使用,通过TransactionTemplate手动管理事务。

声明式的事务管理,使用XML配置声明,开发中推荐使用(代码侵入性最小),Spring的声明式事务是通过AOP实现的。

什么是事务

事务指的是逻辑上的一组操作,这组操作要么全都成功,要么全都失败。最典型的例子就是银行转账的问题。

事务的特性:

原子性:事务是不可分割的工作单位

一致性:事务前后数据的完整性必须保持一致

隔离性:多个并发事务数据要相互隔离

持久性:事务一旦提交,它对数据库的改变是永久性的

Spring事务接口介绍

Spring事务管理高层抽象接口主要包括3个接口:

1、PlatformTransactionManager事务管理器

Spring为不同的持久层框架提供了不同的PlatformTransactionManager接口实现。如JDBC、Mybatis使用DataSourceTransactionManager,Hibernate用HibernateTransactionManager等。

2、TransactionDefinition事务定义信息

包含(隔离级别、传播行为、超时、是否只读)等一些信息。

若不考虑隔离性,将会造成脏读、不可重复读、幻读等。

事务的传播行为主要解决service层里方法的相互调用,解决事务是如何传递的。

3、TransactionStatus事务具体运行状态


事务隔离级别:

spring 关闭事务管理 spring事务管理 详解_bc

事务的传播行为:

spring 关闭事务管理 spring事务管理 详解_Source_02

测试环境搭建

applicationContext.xml

<context:annotation-config/>

<context:component-scan base-package="com"></context:component-scan>

<!--引入配置文件-->
<!--1.配置数据库相关参数–>-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--2.数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--配置连接池属性-->
    <property name="driverClass" value="${jdbc_driver}" />

    <!-- 基本属性 url、user、password -->
    <property name="jdbcUrl" value="${jdbc_url}" />
    <property name="user" value="${jdbc_username}" />
    <property name="password" value="${jdbc_password}" />

    <!--c3p0私有属性-->
    <property name="maxPoolSize" value="30"/>
    <property name="minPoolSize" value="10"/>
    <!--关闭连接后不自动commit-->
    <property name="autoCommitOnClose" value="false"/>

    <!--获取连接超时时间-->
    <property name="checkoutTimeout" value="1000"/>
    <!--当获取连接失败重试次数-->
    <property name="acquireRetryAttempts" value="2"/>
</bean>

 

Dao层

@Repository
public interface AccountDao {
    void outMoney(String out, Double money);

    void inMoney(String in, Double money);
}

 

@Repository("AccountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    @Autowired
    private ComboPooledDataSource dataSource;

    /**解决
     * java.lang.IllegalArgumentException:
     * 'dataSource' or 'jdbcTemplate' is required
     * 的问题。
     * 继承了JdbcDaoSupport注入dataSource出现null的情况
     */
    @PostConstruct
    private void initialize() {
        setDataSource(dataSource);
    }

    public void outMoney(String out, Double money) {
        String sql = "update test set money = money - ? where name = ?";
        this.getJdbcTemplate().update(sql, money, out);
    }

    public void inMoney(String in, Double money) {
        String sql = "update test set money = money + ? where name = ?";
        this.getJdbcTemplate().update(sql, money, in);
    }
}

Service层

@Service
public interface AccountService {
    void transfer(String out, String in, Double money);
}

 

@Service("AccountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    public void transfer(String out, String in, Double money) {
        System.out.println(out+"向"+in+"转账"+money);
        accountDao.outMoney(out,money);
        //出现异常
        //若没有事务处理,这时out账户钱转出,但是in账户钱未转入
        int i = 1/0;
        accountDao.inMoney(in,money);
    }
}

 

Main.java

在未进行事务处理时,会出现aaa中钱减少,bbb中钱未增加的情况。

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        AccountService accountService = (AccountService) context.getBean("AccountService");
        accountService.transfer("aaa","bbb",100.0);
    }
}

 

编程式事务控制

Spring提供了事务管理的模板TransactionTemplate,在哪里使用事务就在哪里注入这个模板。

在applicationContext.xml文件中添加如下代码,配置事务管理器和模板。

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务管理器的模板:Spring为了简化事务管理的代码而提供的类-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

 

编写AccountServiceImpl2.java,进行编程式事务处理

@Service("AccountService2")
public class AccountServiceImpl2 implements AccountService {
    @Autowired
    private AccountDao accountDao;

    //注入事务管理模板
    @Autowired
    private TransactionTemplate transactionTemplate;

    //使用匿名内部类,传入的参数需要final
    public void transfer(final String out, final String in, final Double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                //使用匿名内部类实现编程式事务的处理
                System.out.println(out + "向" + in + "转账" + money);
                accountDao.outMoney(out, money);
                //出现异常
                //若没有事务处理,这时out账户钱转出,但是in账户钱未转入
                int i = 1 / 0;
                accountDao.inMoney(in, money);
            }
        });

    }
}

 

测试

AccountService accountService2 = (AccountService) context.getBean("AccountService2");
accountService2.transfer("aaa","bbb",100.0);

出现异常时不会出现aaa中钱减少而bbb中钱未增加的情况,出现异常进行了事务的一个回滚。

 

这种方式修改代码较多,不推荐这种方式,推荐使用下面的声明式事务的方式。

声明式事务控制

基于AOP思想进行处理。

方法一,基于TransactionProxyFactoryBean

配置applicationContext.xml,添加TransactionProxyFactoryBean的相关配置。

这里是通过代理实现功能的增强,基于AOP拦截规则。

这里说明一下prop的格式 :

PROPAGATION,ISOLATION,readOnly,-Exception,+Exception分别代表

传播行为(7种),隔离级别,只读事务,发生这些异常回滚,发生这些异常提交事务。

<!--声明式事务-->

<!--方法一:基于TransactionProxyFactoryBean-->
<!--配置业务层代理-->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!--配置代理的目标对象-->
    <property name="target" ref="AccountService"/>
    <!--注入事务管理器-->
    <property name="transactionManager" ref="transactionManager"/>
    <!--注入事务属性-->
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <!--这里配置对应的方法进行AOP拦截,*拦截所有,这里测试transfer方法-->
            <!--设置隔离级别-->
            <prop key="transfer">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

AccountServiceImpl不用修改任何代码,在测试类中获得的bean是代理类的对象。

AccountService accountService3 = (AccountService) context.getBean("accountServiceProxy");
accountService3.transfer("aaa","bbb",100.0);

方法二,使用XML配置声明事务(原始方式)

配置applicationContext.xml,添加声明事务。

<!--方法二:使用XML配置声明事务(原始方式)-->
<!--配置事务的通知:(事务的增强)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--下面属性分别为传播行为,隔离级别,只读,发生这些异常回滚,发生这些异常提交事务-->
        <tx:method name="transfer"
                   propagation="REQUIRED"
                   isolation="DEFAULT"
                   read-only="false"
                   rollback-for=""
                   no-rollback-for=""/>
    </tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
    <!--切入点配置-->
    <aop:pointcut id="pointcut1" expression="execution(* com.service.AccountService+.*(..))"/>
    <!--配置切面-->
    <!--在pointcut1这个切入点上使用txAdvice这个增强-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>

也是基于AOP拦截规则,其他不用修改任何代码,在测试时直接调用原来的方法。

AccountService accountService = (AccountService) context.getBean("AccountService");
accountService.transfer("aaa","bbb",100.0);

方法三,使用注解

配置applicationContext.xml,添加基于注解的声明事务

<!--方法三:配置基于注解的声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

然后在需要进行事务处理的类或方法上使用@Transactional注解

//同样有传播行为,隔离级别等属性进行设置
@Transactional(propagation = Propagation.REQUIRED,
                isolation = Isolation.DEFAULT)
public void transfer(String out, String in, Double money) {
    System.out.println(out+"向"+in+"转账"+money);
    accountDao.outMoney(out,money);
    //出现异常
    //若没有事务处理,这时out账户钱转出,但是in账户钱未转入
    int i = 1/0;
    accountDao.inMoney(in,money);
}

 

总结

编程式事务管理:手动编写代码,很少使用

声明式事务管理:

l  方法一:基于TransactionProxyFactoryBean方式,需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean进行增强,很少使用。

l  方法二:基于AspectJ的XML方式,一旦配置好,类上不需要添加任何东西,经常使用。

l  方法三:基于注解方式,配置简单,需要在业务层上添加@Transactional注解,经常使用。