Mybatis and spring jdbc persistence layer framework transaction support analysis

Mybatis and spring jdbc persistence layer framework transaction support analysis

​ The transaction support in the persistence layer framework refers to how the persistence layer framework supports database transactions. Let's first sort out the main line of native database transaction operations. It is through several key methods defined in the Connection interface under the java.sql package. The implementation includes several core methods of setAutoCommit, commit, and rollback; first turn off auto-commit by setting setAutoCommit false, and then commit the transaction by explicitly calling the commit method; the same Connection interface also defines the method of setting the transaction isolation level .

​ After we sort out the interface methods of the underlying transaction mode, we need to explore how the persistence layer framework implements database transactions. It is very clear. We only need to find where the persistence layer framework calls the setAutoCommit, commit, The rollback method is known, and then we follow this line of thinking to analyze how mybatis implements transactions, and how spring jdbc implements transactions. This is a methodology we analyzed.

1. Mybatis transaction support source code analysis

1. Examples

Let's write an example first to see how to operate mybatis transaction

private static void simpleExecutorTest() {
    
    
        long start = System.currentTimeMillis();
        // 然后根据 sqlSessionFactory 得到 session,关闭自动提交
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.SIMPLE,false);

        // 获取学生
        Student student3 = session.selectOne("getStudent", 3);
        System.out.println(student3);

        // 获取学生
        Student student4 = session.selectOne("getStudent", 4);
        System.out.println(student4);

        // 获取学生
        Student student5 = session.selectOne("getStudent", 5);
        System.out.println(student5);

        // 修改学生
        student3.setName("修改的学生3");
        session.update("updateStudent", student3);

        // 修改学生
        student4.setName("修改的学生4");
        session.update("updateStudent", student4);

        // 修改学生
        student5.setName("修改的学生5");
        session.update("updateStudent", student5);

        // 手动提交
        session.commit();

        long end = System.currentTimeMillis();
        System.out.println("SimpleExecutor 更新的执行时间为:" + (end - start));
    }

First, when opening the session openSession through sqlSessionFactory, we need to set the automatic commit to false, and then finally commit the transaction through the session.commit() method. If the commit is not executed, the actual database update operation is not committed.

2. Source code analysis

How to turn off the automatic submission of the openSession method, let’s go in layer by layer and see who the false parameter is passed to. Eventually we will find that it is passed to JdbcTransaction. JdbcTransaction is used to get the connection. Let’s see Get the connection method

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

In the end we found that this place called the setAutoCommit method of connection; the same commit method can also be analyzed in this way.

Two, spring jdbc transaction support source code analysis

1. Examples

Let’s also look at a usage example first,

Configuration

<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--直接配置数据库连接-->
    <bean id="testDbDs" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!-- DAL客户端接口实现 -->
    <bean id="sqlSession"
          class="com.handserome.daat.session.DefaultSqlSession">
        <property name="sqlMapConfigLocation" value="classpath*:conf/sqlMap/sqlMap_*.xml"/>
        <property name="dataSource" ref="testDbDs"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="testDbDs"/>
    </bean>

    <bean id="studentDao" class="com.handserome.test.dao.StudentDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <context:annotation-config />
    <context:component-scan base-package="com.handserome.test.dao" />

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="testDbDs"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

dao layer implementation class

public class StudentDaoImpl implements StudentDao {
    
    

    private NamedParameterJdbcTemplate jdbcTemplate;

    @Transactional
    @Override
    public void insertTransactionTest() {
    
    
        insert();
        insert1();
    }

    public void insert() {
    
    
        String sql = "insert into student(student_id, name, create_time) values (:studentId, :name, :createTime)";
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("studentId", "454");
        paramMap.put("name", "bbbb");
        paramMap.put("createTime", new Timestamp(System.currentTimeMillis()));
        // 返回的是更新的行数
        int count = jdbcTemplate.update(sql, paramMap);
        System.out.println(count);
    }

    public void insert1() {
    
    
        String sql = "insert into student(student_id, name, create_time) values (:studentId, :name, :createTime)";
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("studentId", "454");
        paramMap.put("name", "bbbb");
        paramMap.put("createTime", new Timestamp(System.currentTimeMillis()));
        // 返回的是更新的行数
        int count = jdbcTemplate.update(sql, paramMap);
        System.out.println(count);
    }

    public NamedParameterJdbcTemplate getJdbcTemplate() {
    
    
        return jdbcTemplate;
    }

    public void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) {
    
    
        this.jdbcTemplate = jdbcTemplate;
    }
}

When using spring transaction, just add an @Transactional annotation to achieve transaction processing. Unlike mybatis, we don’t see where it sets the automatic commit false and where the commit method is called. For this we only need to find Where to call these two methods can sort out its context.

2. Source code analysis

First of all, in the configuration file, you can see that several things are configured, 1. The transaction manager DataSourceTransactionManager is configured 2. The package path scanning is turned on 3. The annotation use is turned on;

The configured transaction manager DataSourceTransactionManager must be used to manage transactions, so after a little thought, you will know that it must contain some transaction operation methods, such as opening a transaction, committing a transaction, and rolling back a transaction. We can analyze one for the specifics.

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

		try {
    
    
			if (txObject.getConnectionHolder() == null ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
    
    
				Connection newCon = this.dataSource.getConnection();
				if (logger.isDebugEnabled()) {
    
    
					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);

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
    
    
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
    
    
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				// 关闭自动提交
				con.setAutoCommit(false);
			}
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
    
    
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the session holder to the thread.
			if (txObject.isNewConnectionHolder()) {
    
    
				TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Exception ex) {
    
    
			DataSourceUtils.releaseConnection(con, this.dataSource);
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}

​ We can see that it first gets the connectionHolder from the transaction object, then gets the connection through the connectionHolder, and then calls the setAutoCommit method of the connection; see if it feels like mybatis, it is all obtained from the transaction Go to the connection, and then call the setAutoCommit method of the connection.

​ The analysis shows that this is not enough. We need to think further, when will the doBegin method of DataSourceTransactionManager be called? What is the relationship between calling the insertTransactionTest method in the instance and it? We only need to make a breakpoint in the doBegin method, look at the call chain stack, the method backtracking will be clear.

Insert picture description here

​ From the thread stack, we can see that the upper layer is called by the JdkDynamicAopProxy inoke method, which is implemented by the jdk dynamic proxy. The StudentDaoImpl object is wrapped. Its insertTransactionTest method is actually completed by the JdkDynamicAopProxy dynamic proxy. The dynamic proxy enhances insertTransactionTest. Method, the doBegin method of DataSourceTransactionManager is finally called before it is executed. Regarding how the dynamic proxy object is constructed, you can see the creation and initial loading process of the spring container, which is not discussed here. This is related to the opening of package path scanning and annotation use in the configuration.

Guess you like

Origin blog.csdn.net/khuangliang/article/details/108458906