事务

事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单位,要不全部成功,要不全部不成功。

事务的特性

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这个四个属性统称为ACID特性。

  • 原子性(atomicty): 一个事务是一个不可分割的工作单位,事务中包括的诸多操作,要么做,要么不做。

  • 一致性(consistency):事务必须是使数据库从一个一致性变到另一个一致性。

  • 隔离性(isotation):一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。

  • 持久性(durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据就应该是永久性的。接下来其他操作或故障不应该对其有任何影响。

      举个例子:银行里有4个账号,这四个账号的钱加起来有10000元。这四个账号进行相互转账,但是加起来的钱必须是10000元,这是事务的一致性。
      A转账500到账号B——>A=A-500,B=B+500,这个操作必须捆绑在一起,要么执行,要么不执行。事务的一致性。
      C转账1000到账号D——>C=C-100,D=D+1000,A和B之中的转账,不影响C和D之间的转账,这是事务的隔离性。
    

并发事务可能引起的问题

  • 脏读(dirty read): 一个事务读取了另一个事务尚未提交的信息。
  • 不可重复读(non-repeatable read):一个事务的操作导致另一个事务前后读取都不同的数据
  • 幻读(phantom read): 一个事务的操作导致另一个事务前后查询的的结果数据
    事务A:把数据库中某个字段进行统一更改,事务B插入一条数据。启动事务A启动,启动事务B,发现事务B插入的这一条数据未进行修改,这就是所谓的幻读,幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

事务的隔离级别

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。

隔离级脏读可能性不可重复读可能性幻读可能性加锁读
READ_UNCOMMITTED
READ_COMMITED
REPEATABLE_READ
SERLALIZABLE
隔离级说明
READ_UNCOMMITTED读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用。
READ_COMMITED读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读。
REPEATABLE_READ重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
SERLALIZABLE串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

Mybatis事务

Mybatis的包下事务接口Transaction:

public interface Transaction {
	//创建
    Connection getConnection() throws SQLException;

	//提交
    void commit() throws SQLException;

	//回滚
    void rollback() throws SQLException;

	//关闭
    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
}

在Mybatis中实现这个接口的类有2个,JdbcTransaction和ManagedTransaction。
Mybatis的事务管理分为两种形式:
1、使用JDBC的事务管理机制,即使用java.sql.Connection对象来完成事务。
2、使用MANAGED的事务管理机制:这种机制Mybatis 自身不会实现事务管理,而是让程序的容器如(JBOSS、Weblogic)来实现事务管理
在这里插入图片描述事务的配置、创建和使用

1、事务的配置
我们在使用Mybatis时,一般会在MybatisXML配置文件中定义类似如下信息。
<environment>节点定义了连接某个数据库的信息,其子节点<transactionManager> 的type 会决定我们用什么类型的事务管理机制。
2、事务的创建
Mybatis事务的创建是交给TransactionFactory事务工厂来创建的,如果我们将<transactionManager>的type配置为“JDBC”,那么,Mybatis初始化解析<environment>节点时,会根据type="JDBC"创建一个JdbcTransactionFactory工厂,其源码如下:

    /**
     * 解析<transactionManager>节点,创建对应的TransactionFactory
     * @param context
     * @return
     * @throws Exception
     */
  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      /*
            在Configuration初始化的时候,会通过以下语句,给JDBC和MANAGED对应的工厂类
            typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
            typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
            下述的resolveClass(type).newInstance()会创建对应的工厂实例
       */
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

Environment表示着一个数据库的连接,生成后的Environment对象会被设置到Configuration实例中,以供后续的使用。
在这里插入图片描述
在Mybatis中Configuration这个类非常复杂,Mybatis中所有的配置都在这个类中。
在这里插入图片描述
查看Environment类与String一样被final修饰。
Environment源码:

package org.apache.ibatis.mapping;
import javax.sql.DataSource;
import org.apache.ibatis.transaction.TransactionFactory;
/**
* 环境
* 决定加载哪种环境(开发环境/生产环境)
*/
public final class Environment {
 //环境id
 private final String id;
 //事务工厂
 private final TransactionFactory transactionFactory;
 //数据源
 private final DataSource dataSource;
 public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
   if (id == null) {
     throw new IllegalArgumentException("Parameter 'id' must not be null");
   }
   if (transactionFactory == null) {
       throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
   }
   this.id = id;
   if (dataSource == null) {
     throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
   }
   this.transactionFactory = transactionFactory;
   this.dataSource = dataSource;
 }
 //一个静态内部类Builder
 //建造模式
 //用法应该是new Environment.Builder(id).transactionFactory(xx).dataSource(xx).build();
 public static class Builder {
     private String id;
     private TransactionFactory transactionFactory;
     private DataSource dataSource;
   public Builder(String id) {
     this.id = id;
   }
   public Builder transactionFactory(TransactionFactory transactionFactory) {
     this.transactionFactory = transactionFactory;
     return this;
   }
   public Builder dataSource(DataSource dataSource) {
     this.dataSource = dataSource;
     return this;
   }
   public String id() {
     return this.id;
   }
   public Environment build() {
     return new Environment(this.id, this.transactionFactory, this.dataSource);
   }
 }
 public String getId() {
   return this.id;
 }
 public TransactionFactory getTransactionFactory() {
   return this.transactionFactory;
 }
 public DataSource getDataSource() {
   return this.dataSource;
 }
}

3、事务工厂的使用
事务工厂TransactionFactory定义了创建Transaction的两个方法:一个是通过指定的Connection对象来创建Transaction,另一个是通过数据源DataSource来创建Transaction。JDBC和MANAGED两种Transaction相对应,TransactionFactory有两个对应的实现子类。
TransactionFactory源码

public interface TransactionFactory {
    void setProperties(Properties var1);

    Transaction newTransaction(Connection var1);

    Transaction newTransaction(DataSource var1, TransactionIsolationLevel var2, boolean var3);
}

在这里插入图片描述4、事务Transaction的创建
通过事务工厂TransactionFactory很容易获取到Transaction对象实例。我们以JdbcTransaction为例,看一下JdbcTransactionFactory是怎样生成JdbcTransaction的,代码如下:

public class JdbcTransactionFactory implements TransactionFactory {
 
  public void setProperties(Properties props) {
  }
 
    /**
     * 根据给定的数据库连接Connection创建Transaction
     * @param conn Existing database connection
     * @return
     */
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }
 
    /**
     * 根据DataSource、隔离级别和是否自动提交创建Transacion
     *
     * @param ds
     * @param level Desired isolation level
     * @param autoCommit Desired autocommit
     * @return
     */
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

如上说是,JdbcTransactionFactory会创建JDBC类型的Transaction,即JdbcTransaction。类似地,ManagedTransactionFactory也会创建ManagedTransaction。下面我们会分别深入JdbcTranaction 和ManagedTransaction,看它们到底是怎样实现事务管理的。
5、JdbcTransaction
JdbcTransaction直接使用JDBC的提交和回滚事务管理机制 。它依赖与从dataSource中取得的连接connection 来管理transaction 的作用域,connection对象的获取被延迟到调用getConnection()方法。如果autocommit设置为on,开启状态的话,它会忽略commit和rollback。
直观地讲,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能,JdbcTransaction只是相当于对java.sql.Connection事务处理进行了一次包装(wrapper),Transaction的事务管理都是通过java.sql.Connection实现的。JdbcTransaction的代码实现如下:

/**
 * @see JdbcTransactionFactory
 */
/**
 * @author Clinton Begin
 */
public class JdbcTransaction implements Transaction {
 
  private static final Log log = LogFactory.getLog(JdbcTransaction.class);
 
  //数据库连接
  protected Connection connection;
  //数据源
  protected DataSource dataSource;
  //隔离级别
  protected TransactionIsolationLevel level;
  //是否为自动提交
  protected boolean autoCommmit;
 
  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommmit = desiredAutoCommit;
  }
 
  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }
 
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }
 
    /**
     * commit()功能 使用connection的commit()
     * @throws SQLException
     */
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }
 
    /**
     * rollback()功能 使用connection的rollback()
     * @throws SQLException
     */
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }
 
    /**
     * close()功能 使用connection的close()
     * @throws SQLException
     */
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }
 
  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }
 
  protected void resetAutoCommit() {
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      log.debug("Error resetting autocommit to true "
          + "before closing the connection.  Cause: " + e);
    }
  }
 
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }
 
}

6、ManagedTransaction
ManagedTransaction让容器来管理事务Transaction的整个生命周期,意思就是说,使用ManagedTransaction的commit和rollback功能不会对事务有任何的影响,它什么都不会做,它将事务管理的权利移交给了容器来实现。看如下Managed的实现代码大家就会一目了然:

/**
 * 
 * 让容器管理事务transaction的整个生命周期
 * connection的获取延迟到getConnection()方法的调用
 * 忽略所有的commit和rollback操作
 * 默认情况下,可以关闭一个连接connection,也可以配置它不可以关闭一个连接
 * 让容器来管理transaction的整个生命周期
 * @see ManagedTransactionFactory
 */
/**
 * @author Clinton Begin
 */
public class ManagedTransaction implements Transaction {
 
  private static final Log log = LogFactory.getLog(ManagedTransaction.class);
 
  private DataSource dataSource;
  private TransactionIsolationLevel level;
  private Connection connection;
  private boolean closeConnection;
 
  public ManagedTransaction(Connection connection, boolean closeConnection) {
    this.connection = connection;
    this.closeConnection = closeConnection;
  }
 
  public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
    this.dataSource = ds;
    this.level = level;
    this.closeConnection = closeConnection;
  }
 
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }
 
  public void commit() throws SQLException {
    // Does nothing
  }
 
  public void rollback() throws SQLException {
    // Does nothing
  }
 
  public void close() throws SQLException {
    if (this.closeConnection && this.connection != null) {
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + this.connection + "]");
      }
      this.connection.close();
    }
  }
 
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }
 
}

作者:亦山
来源:CSDN
原文:https://blog.csdn.net/luanlouis/article/details/37992171
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值