MybatisおよびSpring jdbc永続層フレームワークのトランザクションサポート分析

MybatisおよびSpring jdbc永続層フレームワークのトランザクションサポート分析

永続層フレームワークでのトランザクションサポートとは、永続層フレームワークがデータベーストランザクションをどのようにサポートするかを指します。まず、ネイティブデータベーストランザクション操作の主要な行を整理します。これは、java.sqlパッケージのConnectionインターフェースで定義されているいくつかの主要なメソッドを通じて行われます。実装には、setAutoCommit、commit、rollbackのいくつかのコアメソッドが含まれます。まず、setAutoCommitをfalseに設定して自動コミットをオフにしてから、commitメソッドを明示的に呼び出してトランザクションをコミットします。同じ接続インターフェースで、トランザクションの分離レベルを設定するメソッドも定義されています。

基礎となるトランザクションモードのインターフェースメソッドを分類した後、永続化層フレームワークがデータベーストランザクションを実装する方法を探る必要があります。それは非常に明確です。永続化層フレームワークがsetAutoCommit、commit、ロールバック方式は既知であり、この考え方に従って、mybatisがトランザクションを実装する方法、およびSpring jdbcがトランザクションを実装する方法を分析しました。

1. Mybatisト​​ランザクションサポートのソースコード分析

1.例

mybatisト​​ランザクションの操作方法を確認するために、最初に例を書いてみましょう

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

まず、sqlSessionFactoryを介してセッションopenSessionを開く場合、自動コミットをfalseに設定し、最後にsession.commit()メソッドを介してトランザクションをコミットする必要があります。コミットが実行されない場合、実際のデータベース更新操作はコミットされません。

2.ソースコード分析

openSessionメソッドの自動送信をオフにする方法、レイヤーごとに進んでfalseパラメーターが渡されるユーザーを確認しましょう。最終的には、JdbcTransactionに渡されることがわかります。JdbcTransactionは接続を取得するために使用されます。接続方法を取得する

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

最終的に、この場所が接続のsetAutoCommitメソッドを呼び出していることがわかりました。同じ方法でこのcommitメソッドを分析することもできます。

2、Spring jdbcトランザクションサポートのソースコード分析

1.例

最初に使用例も見てみましょう

構成

<?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レイヤー実装クラス

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

Springトランザクションを使用する場合は、@ Transactionalアノテーションを追加するだけでトランザクション処理を実現できます。mybatisとは異なり、自動コミットがfalseに設定されている場所やcommitメソッドが呼び出されている場所はわかりません。これを行う必要があるのは、これらの2つのメソッドを呼び出す場所は、そのコンテキストを整理できます。

2.ソースコード分析

まず、構成ファイルで、いくつかの構成が行われていることがわかります。1.トランザクションマネージャーDataSourceTransactionManagerが構成されている2.パッケージパススキャンがオンになっている3.注釈の使用がオンになっている。

構成済みのトランザクションマネージャーDataSourceTransactionManagerを使用してトランザクションを管理する必要があるため、少し考えると、トランザクションを開く、トランザクションをコミットする、トランザクションをロールバックするなど、いくつかのトランザクション操作メソッドが含まれていることがわかります。1つを分析して詳細を調べることができます。

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

最初にトランザクションオブジェクトからconnectionHolderを取得し、次にconnectionHolderを介して接続を取得してから、接続のsetAutoCommitメソッドを呼び出します。mybatisのように見えるかどうか、すべてトランザクションから取得されていることを確認してください。接続に移動し、接続のsetAutoCommitメソッドを呼び出します。

分析の結果、これだけでは不十分であることがわかります。DataSourceTransactionManagerのdoBeginメソッドが呼び出されるのはいつですか?インスタンスでのinsertTransactionTestメソッドの呼び出しとそれとの関係は何ですか?doBeginメソッドでブレークポイントを作成するだけでよいので、コールチェーンスタックを確認します。メソッドのバックトラックは明確になります。

ここに画像の説明を挿入

スレッドスタックから、jdk動的プロキシによって実装されるJdkDynamicAopProxy inokeメソッドによって上位層が呼び出されていることがわかります。StudentDaoImplオブジェクトはラップされています。そのinsertTransactionTestメソッドは、実際にはJdkDynamicAopProxy動的プロキシによって完了されます。動的プロキシは、insertTransactionTestを強化します。メソッド、DataSourceTransactionManagerのdoBeginメソッドは、実行される前に最後に呼び出されます。動的プロキシオブジェクトの構築方法については、ここでは説明しませんが、Springコンテナの作成と初期読み込みプロセスを確認できます。これは、パッケージパススキャンの開始と構成での注釈の使用に関連しています。

おすすめ

転載: blog.csdn.net/khuangliang/article/details/108458906