Mybatis事务的理解(Transaction以及DbUtil)

事务等级划分

首先要了解Mybatis对事务的操作,第一步是先了解数据库的事务隔离级别有哪些:

事务隔离级别脏读不可重复读幻读解释
读未提交(read-uncommitted)可以读到未提交的事物
不可重复读(read-committed)只能读提交的事物
可重复读(repeatable-read)事务提交前后都能读【MySql默认】
串行化(serializable)serializable时会锁表,是最安全的,也是日常开发基本不会用

Mybatis无疑也是实现了数据库的这几种隔离级别,并且Mybatis支持无事务管理操作。下面看Mybatis枚举写出的事务等级:

**TransactionIsolationLevel **(事务等级枚举)

public enum TransactionIsolationLevel {
  NONE(Connection.TRANSACTION_NONE),
//无事务
  READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
//不可重复读
  READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
//读未提交
  REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
//可重复读
  SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
//串行化

  private final int level;

  private TransactionIsolationLevel(int level) {
    this.level = level;
  }

  public int getLevel() {
    return level;
  }
}

Transaction** (事务接口)😗*

/*
抽取了事务的四个公共特性:获取连接,提交,回滚以及关闭连接
*/
public interface Transaction {

  Connection getConnection() throws SQLException;

  void commit() throws SQLException;

  void rollback() throws SQLException;

  void close() throws SQLException;

}

Transaction实现类:

在这里插入图片描述

一般我们在配置Mybatis(不整合spring)时,一般是在mybatisConfig.xml文件手动指定一种事务管理类型的,如下

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db/mysqlconn.properties"></properties>
	 <!-- 开启懒加载 -->
    <settings>
    	<setting name="lazyLoadingEnabled" value="true"/>
    	<setting name="aggressiveLazyLoading" value="false"/>
    <!-- 将other转为null值 -->	
    	<setting name="jdbcTypeForNull" value="NULL"/><!-- 不设置这个参数的话有一些数据库不能识别null,如oracle数据库 -->
    <!-- 指定日志为log4j -->
    	<setting name="logImpl" value="LOG4J"/>
    <!-- 开启二级缓存 -->
    	<setting name="cacheEnabled" value="true"/>
    </settings>
	<typeAliases>
		<package name="pojo" />
	</typeAliases>
	<environments default="development">
		<environment id="development">
			 <!-- 事务为JDBC事务管理 -->
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<package name="mapper" />
	</mappers>
</configuration> 


JDBC事务管理的源码(JdbcTranction):

 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
      openConnection();
    }
    return connection;
  }
  //基于connecttion对象的提交
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  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);
  }
}

可以看出JDBC的事物都是通过Connection这个对象来实现的。

ManagedTransaction(Manager事物管理):

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());
    }
  }

}

可以看出这个事物的主要功能是将事物交给其他容器(如Spring)去处理,可以这么理解:如果我们使用MyBatis构建本地程序,即不是WEB程序,若将type设置成"MANAGED",那么,我们执行的任何update操作,即使我们最后执行了commit操作,数据也不会保留,不会对数据库造成任何影响。因为我们将MyBatis配置成了“MANAGED”,即MyBatis自己不管理事务,而我们又是运行的本地程序,没有事务管理功能,所以对数据库的update操作都是无效的。

TransactionFactory(事物工厂):

public interface TransactionFactory {

  /**
   * Sets transaction factory custom properties.
   * @param props
   */
  void setProperties(Properties props);

  Transaction newTransaction(Connection conn);
  
  /**
   * Creates a {@link Transaction} out of a datasource.
   * @param dataSource DataSource to take the connection from
   * @param level Desired isolation level
   * @param autoCommit Desired autocommit
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

我们此时来分析Mybatis解析config.xml文件内容为<transactionManager type="JDBC" />是调用的方法:

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
	//此时获取了一个TransactionFactory对象,并且调用该方法创建一个Transaction对象。
	/*
		如果type = "JDBC",则MyBatis会创建一个JdbcTransactionFactory.class 实例;
		如果type="MANAGED",则MyBatis会创建一个MangedTransactionFactory.class实例
	*/
    TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

在这里插入图片描述

这个接口有三个实现类,我们来看JDBCTransactionFactory:

public class JdbcTransactionFactory implements TransactionFactory {

  public void setProperties(Properties props) {
  }

  public Transaction newTransaction(Connection conn) {
	//直接new了一个JdbcTransaction对象管理Connection对象
    return new JdbcTransaction(conn);
  }

  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

Connection连接对象的创建及方法实现

要理解这个java.sql.Connection对象,那就必须先看看我之前学习到的DBUtil.java这个工具类的代码

package com.edu.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DBUtil {
	
	//数据库连接基本信息
	private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
	private static final String DRIVER_URL="jdbc:mysql:///data?useUnicode=true&characterEncoding=UTF-8";
	private static final String DRIVER_USER="root";
	private static final String DRIVER_PWD="123456";
	protected Connection conn ;
	protected PreparedStatement pstmt ;
	protected ResultSet rs ;
	static {
		try {
			//注册驱动(com.mysql.jdbc.Driver这个类中的有一段静态代码块就是专门用于反射注册的)
			Class.forName(DRIVER_CLASS);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	// 数据连接工具类
	public Connection getConnection() {
		try {
			//我们来查看这个方法
			return DriverManager.getConnection(DRIVER_URL, DRIVER_USER, DRIVER_PWD) ;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}

Driver注册驱动静态代码块:

//静态字段初始化器
// 向DriverManager注册驱动
static {
   try {
      java.sql.DriverManager.registerDriver(new Driver());
   } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!");
   }
}
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* 注册尚未添加到我们的列表中的驱动程序*/
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // 不等于空,我就给你报错,为了兼容我原先的Drivermanager(你一个在列表中的驱动还添加进来,那肯定不行啊)
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

DriverManager.getConnection(…) 方法具体事项:

public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }

    return (getConnection(url, info, Reflection.getCallerClass()));
}
//-----具体调用(getConnection(url, info, Reflection.getCallerClass())---------
    //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
	         当callerCl为null时,我们应该检查应用程序的(间接调用该类的)类加载器,
			 以便可以从此处加载rt.jar外的JDBC驱动程序类。
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        //遍历试图建立连接的已加载的注册驱动程序。请记住第一个引发的异常,以便我们重新提出。
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // 如果调用者没有该驱动调用权限,则跳过它(无视该驱动)
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
//开始尝试连接
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
						//连接成功直接返回(从这可以看出来,当有多个驱动类进行驱动注册,那么我们也只能获取到最开始获得连接的那个驱动类的连接conn对象)
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        //一个连接都没拿到啊,给你报个错
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }
    }

因为我最主要就是连接mysql数据库,所以我们来看看Jdbc驱动拿到的连接对象,具体方法详情请看com.mysql.jdbc.MySQLConnection接口,基本所有对数据库的操作api都在这个接口里。
在这里插入图片描述

可以看出它是com.mysql.jdbc.JDBC4Connection对象,可以从下图看出层级关系挺复杂的。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

five-five

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值