Spring事务增强

3 篇文章 0 订阅
2 篇文章 0 订阅
  • Spring中处理事务的Aop增强
  1. PlatformTransactionManager (事务管理器,也就是事务切面)
public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}
  1. TransactionStatus (保存了当前事务的一些信息,比如回滚点,只读事务,最重要的是保存了当前连接)
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    boolean hasSavepoint();

    void flush();
}

3.TransactionSynchronizationManager 保存了每个线程的连接

public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");

    public TransactionSynchronizationManager() {
    }
  • 配置编程式事务
  1. 创建配置类

配置类需要标注@EnableTransactionManagement,并给容器中注入一个 TransactionManager ,此处注入的是DataSourceTransactionManager 。

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.aop")
public class AnnoConfig {

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setUrl("jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai");
        return dataSource;
    }

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

    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource);
        return tm;
    }

}
  1. 创建数据库表
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `username` varchar(255) DEFAULT NULL,
  `banlance` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('A', '0');
INSERT INTO `account` VALUES ('B', '200');

  1. 创建AccountService类
@Service
public class AccountService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void transfer(String from, String to, Integer money) {
        String sqlfrom = "update account set banlance = banlance - ? where username = ?";
        jdbcTemplate.update(sqlfrom, money, from);
        String sqlto = "update account set banlance = banlance + ? where username = ?";
        jdbcTemplate.update(sqlto, money, to);
    }


    public Map<String, Object> getAccount(String username){
        String sql = "select * from account where username = ?";
        Map<String, Object> stringObjectMap = jdbcTemplate.queryForMap(sql, username);
        return stringObjectMap;
    }

}
  • 测试事务

测试类

public class SpringAopApplicationTests {

    private ApplicationContext getContext() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnoConfig.class);
        return context;
    }


    @Test
    public void tx() {
        AccountService accountService = getContext().getBean(AccountService.class);
        accountService.transfer("A", "B", 50);
    }
  1. 首先看一下从容器中拿到的AccountService类型

在这里插入图片描述
此时的AccountService就是一个原始类型。因为该类没有方法标注@Transactional注解。给transfer方法标注该注解后再测试

在这里插入图片描述
此时被cglib创建了代理对象。

尝试执行transfer方法

在这里插入图片描述
此时首先来到TransactionManager的getTransaction方法中,这个方法用于获取一个TransactionStatus,等被代理对象的方法执行完成以后需要将这个TransactionStatus中的connection进行commit,如果出线异常,则需要rollback。

该方法传入了一个TransactionDefinition,事务定义信息,也就是@Transactional注解中可以配置的那些属性。如果只标注了一个@Transactional,那就相当于直接new了一个 DefaultTransactionDefinition。

在这里插入图片描述
此时调了DataSourceTransactionManager中的doGetTransaction方法。

该方法返回了一个DataSourceTransactionObject,该txObject中持有一个ConnectionHolder,xxxHolder一看就是持有一个数据库连接。

紧接着又调用了
AbstractPlatformTransactionManager的startTransaction方法

在这里插入图片描述
该方法中最关键的是调用了this.doBegin方法

此时进入了子类的DataSourceTransactionManager的doBegin方法。

 protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        Connection con = null;

        try {
            if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = this.obtainDataSource().getConnection();
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }

                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            txObject.setReadOnly(definition.isReadOnly());
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }

                con.setAutoCommit(false);
            }

            this.prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);
            int timeout = this.determineTimeout(definition);
            if (timeout != -1) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }

            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
            }

        } catch (Throwable var7) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.obtainDataSource());
                txObject.setConnectionHolder((ConnectionHolder)null, false);
            }

            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
        }
    }

由于当前线程中还没有创建连接,所以ConnectionHolder中的连接为null。此时事务管理器从数据源中获取了一个连接。

在这里插入图片描述
紧接着执行了 我们最熟悉的一行代码

 con.setAutoCommit(false)

最后执行了

   if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
            }

在bindResource中执行了

 public static void bindResource(Object key, Object value) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            map = new HashMap();
            resources.set(map);
        }

        Object oldValue = ((Map)map).put(actualKey, value);
        if (oldValue instanceof ResourceHolder && ((ResourceHolder)oldValue).isVoid()) {
            oldValue = null;
        }

        if (oldValue != null) {
            throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
        }
    }

这个方法非常关键,将新创建出来的连接作为value,将数据源作为key放入了threadlocal中。

在这里插入图片描述

当整个方法执行完成以后,执行增强的commit方法,此时进入了DataSourceTransactionManager.doCommit()

    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");
        }

        try {
            con.commit();
        } catch (SQLException var5) {
            throw this.translateException("JDBC commit", var5);
        }
    }

在出现异常以后执行doRollback

 protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }

        try {
            con.rollback();
        } catch (SQLException var5) {
            throw this.translateException("JDBC rollback", var5);
        }
    }

那么既然spring能控制事务,说明transfer方法中用的连接肯定是同一个连接,下面深究一下jdbcTemplate的update方法。

发现这个update方法最终调用了execute方法,而execute方法中有一行

Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
那关键就看怎么获取到的这个连接

在这里插入图片描述
进入 这个方法

 public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        } catch (SQLException var2) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", var2);
        } catch (IllegalStateException var3) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + var3.getMessage());
        }
    }

    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
            logger.debug("Fetching JDBC Connection from DataSource");
            Connection con = fetchConnection(dataSource);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                try {
                    ConnectionHolder holderToUse = conHolder;
                    if (conHolder == null) {
                        holderToUse = new ConnectionHolder(con);
                    } else {
                        conHolder.setConnection(con);
                    }

                    holderToUse.requested();
                    TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                    holderToUse.setSynchronizedWithTransaction(true);
                    if (holderToUse != conHolder) {
                        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                    }
                } catch (RuntimeException var4) {
                    releaseConnection(con, dataSource);
                    throw var4;
                }
            }

            return con;
        } else {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }

            return conHolder.getConnection();
        }
    }

发现这个方法又调了doGetConnection,在该方法里有一行关键代码

ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);

而TransactionSynchronizationManager.getResource(dataSource);正是从当前线程中获取之前前置增强放入的那个连接。此时是根据数据源作为key获取的,也就是说如果jdbcTemplate和DataSourceTransactionManager注入的不是同一个数据源,此时事务就不生效了。

拿到连接以后,在同一个方法中调用的多次update,就会一直用这一个连接,那么此时如果抛出异常,就可以进行回滚了。

  • 若TransactionSynchronizationManager.getResource(dataSource);未获取数据源

则JDBCTemplate就会从自己的数据源中获取一个数据源

    private static Connection fetchConnection(DataSource dataSource) throws SQLException {
        Connection con = dataSource.getConnection();
        if (con == null) {
            throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
        } else {
            return con;
        }
    }

这也从侧面解释了为什么将数据源交给事务管理器后还需要交给JDBCTemplate。如果是JDBCTemplate自己获取数据源的话,默认从连接池中获取的数据源都是自动提交的。所以不会回滚事务。

总结:Spring为一些第三方的基于JDBC的操作数据库的框架提供了一个统一的事务管理抽象(PlatformTransactionManager ),只要实现了该接口 就可以切入方法,进行事务控制,当然,Spring也提供了一些默认的实现,JDBC和Mybatis都适用于DataSourceTransactionManager。

如果某日自己可以封装一个orm框架,那么就可以写一个自己的PlatformTransactionManager 来进行事务控制。

在执行事务方法执行前,Spring解析了@Transactional注解中的各个属性信息,封装成一个TransactionDefinition,调用事务管理器的getTransaction方法,这个方法会放入当前线程的threadlocal中一个connection,并返回TransactionStatus,TransactionStatus中持有ConnectionHolder,可以提交或回滚当前事务。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值