《Java Spring框架》Spring事务管理

1、  事务相关知识:

 什么是事务:把多条数据库操作捆绑到一起执行,要么都成功,要么都失败;

 事务的原则ACID:

       原子性:事务包含的所有操作,要么全部成功,要么全部失败回滚,成功全部应用到数据库,失败不能对数据库有任何影响;

       一致性:事务在执行前和执行后必须一致;例如A和B一共有100块钱,无论A、B之间如何转账,他们的钱始终相加都是100;

       隔离性:多用户并发访问同一张表时,数据库为每一个用户开启新的事务,该事务不能被其他事务所影响,相互有隔离;

       持久性:一个事务一旦提交,则对数据库中数据的改变是永久的,即便系统故障也不会丢失;

并发可能引起的问题:

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

        不可重复读:一个事务读取到另一个事务已提交(Update操作)的数据,导致前后读取不一致;

        幻读(虚读):一个事务中读取到别的事务插入(Insert操作)的数据,导致前后读取不一致;

事务的隔离级别:根据实际情况选择;

         Serializable串行化:可避免脏读、不可重复读和幻读;

         Repeatable read可重复读:可避免脏读、不可重复读;(MySql默认值)

         Read committed读已提交:可避免脏读;

         Read uncommitted读未提交:任何情况都无法保证;

2、  Spring-aop事务-搭建环境;

事务基本操作:打开事务、提交事务、回滚事务;

Spring中利用接口来管理不同框架的事务操作;

     通过实现PlatformTransactionManager接口支持不同的框架完成各自的事务处理;

     为不同平台提供对应的事务管理器的实现:

     JDBC&Mybatis:DataSourceTransactionManager;

Spring-aop事务通过配置事务的隔离级别、事务传播行为、是否只读来操作;

      隔离级别:串行化、可重复读、读已提交、读未提交;

      是否只读:true:不可改变数据库中的数据,查询操作推荐;  false:可以改变数据库数据;

      事务传播行为:事务方法嵌套调用的规则: xService.x();  ->     yService.y();

            REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置;

            REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务;

            SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行;

            NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(暂停);

            MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常;

            NEVER:以非事务方式执行,如果当前存在事务,则抛出异常;

            NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

3、  Spring-aop事务 – 从麻烦的事务代码中走出之xml配置版aop事务;

使用经典的转账案例进行测试,准备数据:bean、service、dao;

使用事务需要额外导入tx包和tx约束;

配置事务核心管理器: DataSourceTransactionManager;

配置事务通知 tx:Advice;

配置aop;

根据以上知识点,我们来实现spring的事务管理。

数据库创建一张表:

《Java Spring框架》Spring事务管理

 代码:

/**
 * 账户
 * @author hubt11585
 */
public class Account {
    private Integer id;
    private String name;
    private Double money;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
}
public interface AccountDao {
    //扣款
    void subMoney(Integer id, Double money);
    //加款
    void addMoney(Integer id, Double money);
}
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    @Override
    public void subMoney(Integer id, Double money) {
        String sql = "update account set money = money - ? where id = ?";
        getJdbcTemplate().update(sql, money, id);
    }

    @Override
    public void addMoney(Integer id, Double money) {
        String sql = "update account set money = money + ? where id = ?";
        getJdbcTemplate().update(sql, money, id);
    }
}
/**
 * 账户service
 * @author Joey
 *
 */
public interface AccountService {
    
    //转账接口
    void transferAccounts();
}
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.tao.dao.AccountDao;

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true)
public class AccountServiceImpl implements AccountService {

    //账户dao
    private AccountDao ad;
    public void setAd(AccountDao ad) {
        this.ad = ad;
    }

    @Override
    @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false)
    public void transferAccounts() {
        //转账逻辑
        
        //先从A账户扣款
        ad.subMoney(1, 50d);
        int a = 1/0;      // 除以0会出现异常
        //再给B账户加款
        ad.addMoney(2, 50d);
    }
}
import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.tao.service.AccountService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TxTest {
    
    @Resource(name="accountService")
    private AccountService as;
    @Test
    public void Test1() {
        as.transferAccounts();
    }
}

配置文件: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: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-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- 依赖关系 dao ->  -> dataSource -->
    <!-- 读取配置文件 -->
    <context:property-placeholder location="db.properties"/>
    
    <!-- 配置 dataSource -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

    <!-- dao -->
    <bean name="accountDao" class="com.tao.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- service -->
    <bean name="accountService" class="com.tao.service.AccountServiceImpl">
        <property name="ad"  ref="accountDao"/>
    </bean>
    
    <!-- 配置事务核心管理器 不同平台不一样 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 事务通知 -->
    <tx:advice id="txAdivce" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transferAccounts" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="save*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="delete*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="update*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="select*" isolation="DEFAULT"  propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置aop -->
    <aop:config>
        <aop:pointcut expression="execution(* com.tao.service.*ServiceImpl.*(..))" id="txPc"/>
        <aop:advisor advice-ref="txAdivce" pointcut-ref="txPc"/>
    </aop:config>
</beans>

运行结果:

《Java Spring框架》Spring事务管理

会出现异常

《Java Spring框架》Spring事务管理表里面数据被回滚,并没有出现执行了异常前面部分,效果达到。

以下是注解版代码:

只需要调整一下部分代码。

配置文件中:开启事务:

<!-- 开启注解事务 -->   <tx:annotation-driven/>

完整配置文件如下:

<?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-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- 依赖关系 dao ->  -> dataSource -->
    <!-- 读取配置文件 -->
    <context:property-placeholder location="db.properties"/>
    
    <!-- 配置 dataSource -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- dao -->
    <bean name="accountDao" class="com.tao.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- service -->
    <bean name="accountService" class="com.tao.service.AccountServiceImpl">
        <property name="ad"  ref="accountDao"/>
    </bean>
    
    <!-- 配置事务核心管理器 不同平台不一样 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 开启注解事务 -->
    <tx:annotation-driven/>
    
</beans>

代码:AccountServiceImpl类上(整个类下所有方法有效)或者方法上加上注解即可实现统一事务。

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.tao.dao.AccountDao;

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true)
public class AccountServiceImpl implements AccountService {

    //账户dao
    private AccountDao ad;
    public void setAd(AccountDao ad) {
        this.ad = ad;
    }

    @Override
    @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false)
    public void transferAccounts() {
        //转账逻辑
        
        //先从A账户扣款
        ad.subMoney(1, 50d);
        int a = 1/0;      // 除以0会出现异常
        //再给B账户加款
        ad.addMoney(2, 50d);
    }
}

运行结果:

《Java Spring框架》Spring事务管理

会出现异常

《Java Spring框架》Spring事务管理表里面数据被回滚,并没有出现执行了异常前面部分,效果达到。

 总结:通过spring来控制事务,方便整洁。

相关推荐