Мысли об источниках данных в управлении транзакциями Spring+Mybatis

Java Spring MyBatis

Ранее коллега задавал мне вопрос: в нашем проекте открытие и закрытие транзакций обрабатывается Spring, но определенные операторы SQL выполняются Mybatis. Итак, вопрос в том, как Mybatis гарантирует, что выполняемая им инструкция SQL находится в контексте транзакции Spring?

Оригинальный адрес:у-у-у. Краткое описание.com/afraid/6 ах 880-х 20 ах...

Примечание. В этой статье основное внимание уделяется не анализу принципа реализации транзакций Spring, но она требует, чтобы читатели заранее понимали некоторые аспекты принципа транзакций Spring, чтобы ее было легче читать.

Большинство основных сред разработки компании теперь используют spring + mybatis для работы с базой данных, и все операции с транзакциями передаются Spring для управления. Когда нам нужна операция базы данных с контекстом транзакции, наш подход заключается в написании метода для работы с базой данных и добавлении аннотации @Transactional к методу.

Внимательно подумайте об этом процессе, @Transactional обрабатывается Spring, Spring получает соединение с базой данных из источника данных (обычно это пул соединений с базой данных, такой как druid, c3p0 и т. д.), а затем выполняет setAutoCommit перед входом в логическая операция метода (false) и, наконец, выполнение операции фиксации или отката, соответственно, в случае успешной обработки или возникновения исключения.

Затем возникает проблема, открытие и закрытие транзакции выполняется после того, как соединение с базой данных получено весной, но оператор обновления или вставки, который мы фактически выполняем, управляется mybatis для получения соединения с базой данных.Возможно, что если вы хотите, чтобы Чтобы транзакция вступила в силу, Spring и mybatis должны использовать одно и то же соединение Какова реальная ситуация? Как их без проблем соединить? Давайте проанализируем это через исходный код.

Прежде всего, если мы хотим использовать mybatis весной, в дополнение к зависимости mybatis нам также нужно ввести пакет: mybatis-spring.

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>x.x.x</version>
</dependency>

Можно предположить, что этот пакет зависимостей должен быть ключом к бесшовному соединению между Spring и Mybatis.

Вообще говоря, наши конфигурационные файлы в проекте часто выглядят так:

<!--会话工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

<!--spring事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>

<!--使用注释事务 -->
<tx:annotation-driven  transaction-manager="transactionManager" />

Примечание:
1. Источник данных dataSource, используемый фабрикой сеансов sqlSessionFactory и менеджером транзакций Spring, transactionManager, должен быть одним и тем же.
2. Тип sqlSessionFactory здесь — org.mybatis.spring.SqlSessionFactoryBean, который предоставляется представленным нами пакетом mybatis-spring.

Из названия видно, что SqlSessionFactoryBean является фабричным компонентом, а это означает, что реальный экземпляр, который он передает Spring, предоставляется методом getObject(), поэтому давайте взглянем на его исходный код инициализации реального экземпляра:

@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      //可以看出逻辑都在这里面
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

@Override
public void afterPropertiesSet() throws Exception {
    //此处省略一些校验逻辑
    //...
    this.sqlSessionFactory = buildSqlSessionFactory();
}

//最后来看这个最核心的方法
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    //...
    //省略一些其他初始化信息,我们重点关注事务处理逻辑

    if (this.transactionFactory == null) {
      //可以看出,mybatis中把事务操作交给了SpringManagedTransactionFactory去做
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

   //省略后续逻辑
   //...
}

Давайте посмотрим на исходный код класса SpringManagedTransactionFactory:

public class SpringManagedTransactionFactory implements TransactionFactory {

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(Connection conn) {
    throw new UnsupportedOperationException("New Spring transactions require a DataSource");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setProperties(Properties props) {
    // not needed in this version
  }

}

Кода очень мало, а эффективен только один метод. Кажется, он все ближе и ближе к успеху. Продолжайте следить, чтобы увидеть исходный код SpringManagedTransaction:

@Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  }

Остальные части этого класса опускаем, ориентируемся на место получения соединения, здесь самое критичное местоthis.connection = DataSourceUtils.getConnection(this.dataSource);,
Полное имя DataSourceUtils: org.springframework.jdbc.datasource.DataSourceUtils. Да, это класс, предоставляемый Spring. Согласно нашему предыдущему предположению, после того, как Spring открывает транзакцию, Mybatis хочет сделать свой собственный оператор SQL в контексте эта транзакция Чтобы работать, вы должны получить то же соединение с базой данных, что и открытая транзакция Spring. Поскольку класс DataSourceUtils предоставляется Spring, он кажется похожим на результат, который мы начали догадываться. Давайте посмотрим на исходный код DataSourceUtils для проверять:

//获取数据库连接最终落在该方法上,我删除一些不重要的代码
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        //TransactionSynchronizationManager重点!!!有没有很熟悉的感觉??
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
            Connection con = fetchConnection(dataSource);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                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);
                }
            }
            return con;
        } else {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                conHolder.setConnection(fetchConnection(dataSource));
            }

            return conHolder.getConnection();
        }
    }

Вы чувствуете себя очень сердечно, когда видите TransactionSynchronizationManager? Студенты, знакомые с исходным кодом управления транзакциями Spring, сразу подумают, что после того, как Spring открывает транзакцию, здесь ставится соответствующее подключение к базе данных.Я перехватываю исходный код и смотрю:

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

Этот код находится конкретно в методе doBegin в классе org.springframework.jdbc.datasource.DataSourceTransactionManager, который мы настроили выше. Что касается принципа реализации класса TransactionSynchronizationManager, думаю, вы уже догадались, да, это класс ThreadLocal классической библиотеки классов в Java! ! !

Наконец, добавьте изображение, чтобы проиллюстрировать логику получения источника данных процесса транзакции spring + mybatis:

Spring-Mybatis事务处理过程
Процесс транзакции Spring-Mybatis