一、事务控制

  • JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案
  • Spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar 中
  • Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现

二、事务控制的 API

1、PlatformTransactionManager 接口

此接口是 spring 的事务管理器(平台事务管理器),它里面提供了我们常用的操作事务的方法:

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

真正的事务管理对象:

  • org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 MyBatis 进行持久化数据时使用
  • org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本进行持久化数据时使用
2、TransactionDefinition

它是事务的定义信息对象,里面有如下方法:

  • String getName() 获取事务对象名称
  • int getIsolationLevel() 获取事务的隔离级别
  • int PropagationBehavior() 获取事务的传播行为
  • int getTimeout() 获取事务的超时时间
  • boolean isReadOnly() 获取事务是否只读
3、事务的隔离级别

事务的隔离级别反映了事务提交并发访问时的处理态度

  • ISOLATION_DEFAULT : 默认级别,归属下列某一种
  • ISOLATION_READ_UNCOMMITTED : 可以读取未提交的数据
  • ISOLATION_READ_COMMITTED : 只能读取已提交的数据
  • ISOLATION_REPEATABLE_READ : 是否读取替他事务修改后的数据(MySQL的默认级别)
  • ISOLATION_SERIALAZABLE : 是否读取替他事务提交添加后的数据
4、事务的传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
5、超时时间

默认值是-1,没有超时限制。如果有,以秒为单位进行设置

6、TransactionStatus 接口

此接口提供的是事务具体的运行状态,描述了某个时间点上事务对象的状态信息方法有:

  • void flush() :刷新事务
  • boolean hasSavePoint() : 获取事务是否存在存储点
  • boolean isCompleted() : 获取事务是否完成
  • boolean isNewTransaction() : 获取事务是否为新事务
  • boolean isRollBackOnly() : 获取事务是否回滚
  • void setRollbackOnly() : 设置事务回滚

三、示例:未开启事务的转账

在这里插入图片描述
第一步:封装 Account.java 实体类来映射数据库表

package cn.lemon.domain;

import java.io.Serializable;

public class Account implements Serializable {
    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;
    }

    @Override
    public String toString() {
        return "账户信息{" +
                "编号=" + id +
                ", 姓名='" + name + '\'' +
                ", 余额=" + money +
                '}';
    }
}

第二步:新建持久层(dao)接口以及实现类

package cn.lemon.dao;

import cn.lemon.domain.Account;

import java.util.List;

public interface IAccountDao {

    void updateAccount(Account account);

    Account findByIdAccount(Integer accountId);
}
package cn.lemon.dao.impl;

import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void updateAccount(Account account) {
        try{
            jdbcTemplate.update("update account set name = ?, money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findByIdAccount(Integer accountId) {
        try{
            return jdbcTemplate.queryForObject("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

第三步:新建 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dateSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db_account"></property>
        <property name="username" value="root"></property>
        <property name="password" value="lemon"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dateSource"></property>
    </bean>
    <context:component-scan base-package="cn.lemon"></context:component-scan>
</beans>

第四步:新建业务层(service)接口以及实现类

package cn.lemon.servcie;

public interface IAccountService {
    /**
     * 执行转账操作
     * @param inId 转入的id
     * @param outId 转出的id
     * @param money 转入转出的金额
     */
    void transfer(Integer inId, Integer outId, Double money);
}
package cn.lemon.servcie.impl;

import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import cn.lemon.servcie.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao iAccountDao;

    @Override
    public void transfer(Integer inId, Integer outId, Double money) {
        Account in = iAccountDao.findByIdAccount(inId);
        Account out = iAccountDao.findByIdAccount(outId);

        in.setMoney(in.getMoney() + money);
        iAccountDao.updateAccount(in);

        //int d = 1/0; //模拟异常

        out.setMoney(out.getMoney() - money);
        iAccountDao.updateAccount(out);
    }
}

第五步:测试类

package cn.lemon.servcie.impl;

import cn.lemon.servcie.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class AccountServiceImplTest {
    @Autowired
    private IAccountService iAccountService;

    @Test
    public void testtrnsfer() {
        iAccountService.transfer(1,2,100d);
    }
}

分析结果:
当在第四步的实现类中,开启异常时,会发生转账失败,也就是id为1的账户增加了100(转入成功),而id为2的账户却没有减少(转出失败),这不是我们想要的结果

四、Spring 基于 XML 的事务控制

第一步: 配置事务管理器

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

第二步:配置事务的通知引用事务管理器

    <!--配置事务的通知,事务增强-->
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager"></tx:advice>

第三步:配置事务的属性

        <!--在 tx:advice 标签内部 配置事务的属性 -->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" read-only="false"/><!--这是转账的方法 transfer-->
            <tx:method name="*" propagation="REQUIRED" read-only="true"></tx:method><!--这是除了转账之外的其他方法-->
        </tx:attributes>
  • read-only:是否是只读事务。默认 false,不只读。
  • isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
  • propagation:指定事务的传播行为。
  • timeout:指定超时时间。默认值为:-1。永不超时。
  • rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
  • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。

第四步:配置 AOP 切入点表达式

    <!--配置AOP-->
    <aop:config proxy-target-class="true">
        <!--配置切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut>
    </aop:config>

第五步:配置切入点表达式和事务通知的对应关系

        <!-- 建立事务的通知和切入点表达式的关系 织入事务增强-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>

完整配置
在这里插入图片描述

五、Spring 基于 XML &注解的事务控制

第一步:删除配置文件 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db_account?serverTimezone=GMT%2B8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="lemon"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--注解搜索的包-->
    <context:component-scan base-package="cn.lemon"></context:component-scan>
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="transfer"/>
            <tx:method name="*" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>
    <aop:config proxy-target-class="true">
        <aop:pointcut id="pointCut" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>
-->
    <!--开启事务的注解支持-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"></tx:annotation-driven>
</beans>

第二步:在业务层(service)的实现类中使用@Transactional 注解

package cn.lemon.service.impl;

import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import cn.lemon.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao iAccountDao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(Integer inId, Integer outId, Double money) {
        Account in = iAccountDao.findByIdAccount(inId);
        Account out = iAccountDao.findByIdAccount(outId);
        in.setMoney(in.getMoney() + money);
        iAccountDao.updateAccount(in);

        //int i = 1/0;//模拟异常

        out.setMoney(out.getMoney() - money);
        iAccountDao.updateAccount(out);
    }
}

六、Spring 基于注解的事务控制

第一步:修改上面的代码,即:删除 applicationContext.xml 文件,添加连接数据库的属性文件 JdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///db_account?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=lemon

第二步:写配置类 SpringConfiguration.java 以及子配置类 JdbcConfig.java

package cn.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration//定义本类是一个配置类
@Import(JdbcConfig.class)//定义子配置类
@ComponentScan("cn.lemon")//定义注解要搜索的包
@PropertySource("classpath:/jdbc.properties")//读取属性文件
@EnableTransactionManagement(proxyTargetClass = true)//开启事务对注解的支持,,子类代理,不写的话为接口代理
public class SpringConfiguration {
}
package cn.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;


@Configuration//定义本类是一个配置类,是可以省略的
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
// *  Bean
// *      作用:用于把当前方法的返回值作+为bean对象存入spring的ioc容器中
// *      属性:
//             name:用于指定bean的id。当不写时,默认值是当前方法的名称
// *      细节:
//             当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
//             查找的方式和Autowired注解的作用是一样的

    @Bean
    public DataSource dataSource() {// 在spring 中产生对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {//配置事务管理器
        return new DataSourceTransactionManager(dataSource);
    }
}

第三步:修改测试类

package cn.lemon.service.impl;

import cn.config.SpringConfiguration;
import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceImplTest {
    @Autowired
    private IAccountService iAccountService;

    @Test
    public void transfer() {
        iAccountService.transfer(2,1,100d);
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐