После столь долгого использования @Transactional вы действительно понимаете?

Java

транзакция базы данных

Транзакция базы данных (Transaction, сокращенно TX) — это логическая единица в процессе выполнения системы управления базой данных, атомарная единица работы, которая может быть зафиксирована или отменена. Когда транзакция вносит несколько изменений в базу данных, либо все изменения завершаются успешно, когда транзакция фиксируется, либо все изменения отменяются при откате транзакции.

Транзакции баз данных (включая, помимо прочего, реляционные) обычно имеют следующие четыре характеристики, называемыеКИСЛОТНЫЕ свойства

ACID

  • атомарность: Транзакция выполняется как единое целое, и либо все операции над базой данных, содержащиеся в ней, либо не выполняются.
  • Последовательность: Транзакция должна гарантировать, что состояние базы данных изменится с одного согласованного состояния на другое. Значение _согласованного состояния_ состоит в том, что данные в базе данных должны удовлетворять ограничениям целостности.
  • Изоляция: когда несколько транзакций выполняются одновременно, выполнение одной транзакции не должно влиять на выполнение других транзакций.
  • Долговечность: изменения в базе данных совершенными транзакциями должны постоянно храниться в базе данных.

Транзакции в Mysql

START TRANSACTION
    [transaction_characteristic [, transaction_characteristic] ...]

transaction_characteristic: {
    WITH CONSISTENT SNAPSHOT
  | READ WRITE
  | READ ONLY
}

BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}

  • START TRANSACTIONилиBEGINНачать новую транзакцию.
  • COMMITПодтвердить текущую транзакцию.
  • ROLLBACKОткатить текущую транзакцию.
  • SET autocommitОтключите или включите режим автоматической фиксации по умолчанию для текущего сеанса.

По умолчанию Mysql находится в режиме автоматической фиксации, и все операторы фиксируются немедленно.
**

Транзакции в JDBC

JDBCЭто интерфейс прикладной программы на языке Java, используемый для стандартизации того, как клиентские программы получают доступ к базе данных, и предоставляет методы для запроса и обновления данных в базе данных. JDBC, также торговая марка Sun Microsystems (теперь часть Oracle), предназначена для реляционных баз данных.

Как упоминалось выше, Mysql автоматически фиксируется по умолчанию, поэтому на первом этапе транзакции транзакции в JDBC необходимо отключить автоматическую фиксацию:

con.setAutoCommit(false);

Совершить транзакцию:

con.commit();

Откатить транзакцию:

con.rollback();

Пример полного потока (взято из документации Oracle JDBC):

public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
    throws SQLException {

    PreparedStatement updateSales = null;
    PreparedStatement updateTotal = null;

    String updateString =
        "update " + dbName + ".COFFEES " +
        "set SALES = ? where COF_NAME = ?";

    String updateStatement =
        "update " + dbName + ".COFFEES " +
        "set TOTAL = TOTAL + ? " +
        "where COF_NAME = ?";

    try {
        con.setAutoCommit(false);
        updateSales = con.prepareStatement(updateString);
        updateTotal = con.prepareStatement(updateStatement);

        for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
            updateSales.setInt(1, e.getValue().intValue());
            updateSales.setString(2, e.getKey());
            updateSales.executeUpdate();
            updateTotal.setInt(1, e.getValue().intValue());
            updateTotal.setString(2, e.getKey());
            updateTotal.executeUpdate();
            con.commit();
        }
    } catch (SQLException e ) {
        JDBCTutorialUtilities.printSQLException(e);
        if (con != null) {
            try {
                System.err.print("Transaction is being rolled back");
                con.rollback();
            } catch(SQLException excep) {
                JDBCTutorialUtilities.printSQLException(excep);
            }
        }
    } finally {
        if (updateSales != null) {
            updateSales.close();
        }
        if (updateTotal != null) {
            updateTotal.close();
        }
        con.setAutoCommit(true);
    }
}


Зачем нужен транзакционный менеджер

Без менеджера транзакций наша программа могла бы выглядеть так:

Connection connection = acquireConnection();
try{
    int updated = connection.prepareStatement().executeUpdate();
    connection.commit();
}catch (Exception e){
    rollback(connection);
}finally {
    releaseConnection(connection);
}

Также возможны такие «элегантные транзакции»:

execute(new TxCallback() {
    @Override
    public Object doInTx(Connection var1) {
        //do something...
        return null;
    }
});
public void execute(TxCallback txCallback){
    Connection connection = acquireConnection();
    try{
        txCallback.doInTx(connection);
        connection.commit();
    }catch (Exception e){
        rollback(connection);
    }finally {
        releaseConnection(connection);
    }
}

# lambda版
execute(connection -> {
    //do something...
    return null;
});

Однако два вышеуказанных метода очень неудобны для некоторых сложных сценариев. В реальных бизнес-сценариях часто бывает более сложная бизнес-логика, код многословен и логическая ассоциация сложна.Если большая операция заполнена таким кодом, я думаю, что разработчики могут сойти с ума. Не говоря уже о настраиваемых уровнях изоляции и обработке вложенных/независимых транзакций.

Введение в менеджер транзакций Spring

Spring — самая мощная среда в Java, и управление транзакциями также является одной из ее основных функций. Spring предоставляет унифицированную абстракцию для управления транзакциями, которая имеет следующие преимущества:

  • Согласованная модель программирования для различных API транзакций, таких как Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA) и Java Data Objects (JDO).
  • Поддержка декларативного управления транзакциями (форма аннотации)
  • Более простые API для программного управления транзакциями по сравнению со сложными API транзакций, такими как JTA.
  • Легко интегрируется с абстракцией уровня данных Spring (например, Spring — Hibernate/Jdbc/Mybatis/Jpa...)

Менеджер транзакций Spring — это просто интерфейс/абстракция. Различные структуры уровня БД (фактически, не только структуры класса БД, но теоретически те, которые поддерживают модели транзакций, могут использовать эту абстракцию), возможно, все должны реализовать этот стандарт, чтобы работать лучше. являетсяorg.springframework.transaction.support.AbstractPlatformTransactionManager, чей код находится вspring-txВ модуле, таком как Hibernate, реализация такова:org.springframework.orm.hibernate4.HibernateTransactionManager

Как пользоваться

Транзакции естественным образом управляют бизнесом.В бизнес-процессе часто надеются обеспечить атомарность как всех успехов, так и всех неудач.

Так что транзакция вообще грузится@ServiceСлой, Служба вызывает несколько операций для работы с базой данных (например, Dao), и транзакция автоматически фиксируется после завершения Службы, и транзакция откатывается, если возникает исключение.

Это также основной принцип использования управления транзакциями Spring.

аннотация

Добавлено в заголовки классов, управляемых Spring.@TransactionalAnnotation, вы можете включить управление транзакциями для всех методов этого класса. После того, как транзакция открыта, операциям в методе не нужно вручную открывать/фиксировать/откатывать транзакцию, и всем может управлять Spring.

@Service
@Transactional
public class TxTestService{
    
    @Autowired
    private OrderRepo orderRepo;

    public void submit(Order order){
        orderRepo.save(order);
    }
}

Так же можно настроить только на метод, приоритет настройки метода больше чем у класса

@Service
public class TxTestService{
    
    @Autowired
    private OrderRepo orderRepo;


    @Transactional
    public void submit(Order order){
        orderRepo.save(order);
    }
}

TransactionTemplate

TransactionTemplate на самом деле мало чем отличается от использования аннотаций. Его основные функции также реализуются TransactionManager. Вот еще одна запись.

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        //获取事务信息
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            //执行业务代码
            result = action.doInTransaction(status);
        }
        //处理异常回滚
        catch (RuntimeException ex) {
            // Transactional code threw application exception -> rollback
            rollbackOnException(status, ex);
            throw ex;
        }
        catch (Error err) {
            // Transactional code threw error -> rollback
            rollbackOnException(status, err);
            throw err;
        }
        catch (Exception ex) {
            // Transactional code threw unexpected exception -> rollback
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        //提交事务
        this.transactionManager.commit(status);
        return result;
    }
}

XML-конфигурацияtx:advice

Слишком стар, чтобы объяснять

Уровень изоляции

Уровень изоляции транзакций — одна из важнейших функций базы данных, которая гарантирует, что такие проблемы, как грязное чтение/фантомное чтение, не возникнут. Как структура управления транзакциями, она, естественно, поддерживает эту конфигурацию.В аннотации @Transactional есть конфигурация изоляции, которая может легко настроить уровень изоляции каждой транзакции, что эквивалентноconnection.setTransactionIsolation()

Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}

Поведение распространения

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

На самом деле такое поведение распространения не имеет ничего общего с функцией базы данных, а представляет собой всего лишь механизм, разработанный менеджером транзакций для обработки сложных операций.

Например сейчас есть такая сцена звонка,A Service -> B Service -> C Service, но надеюсь, что A/B находится внутри транзакции, C является независимой транзакцией, и если C не удастся, это не повлияет на транзакцию, в которой находится AB.

На этом этапе с этим можно справиться путем распространения поведения; настройте транзакцию службы C как@Transactional(propagation = Propagation.REQUIRES_NEW)Только что

Spring поддерживает следующие способы распространения:

REQUIRED

По умолчанию текущая транзакция (и ресурсы транзакции, привязанные к текущему потоку) используется первой.Если транзакции нет, будет открыта новая транзакция.

SUPPORTS

Текущая транзакция (и ресурсы транзакции, привязанные к текущему потоку) используется первой, и если транзакции нет, она будет выполняться в нетранзакционном режиме.

MANDATORY

Текущая транзакция используется первой, если транзакции не существует, выдается исключение

REQUIRES_NEW

Создать новую транзакцию, приостановить, если есть текущая транзакция (Приостановить)

NOT_SUPPORTED

Выполняется без транзакций, приостанавливает текущую транзакцию, если она существует.

NEVER

Выполняется без транзакций, выдает исключение, если текущая транзакция существует.

стратегия отката

В @Transactional есть 4 атрибута для настройки стратегий отката, разделенных на стратегию отката и стратегию без отката.

По умолчанию два исключения, RuntimeException и Error, вызывают откат транзакции, а обычное исключение (которое требует Catch) не откатывается.

Rollback

Настройте класс исключения, который необходимо откатить

# 异常类Class
Class<? extends Throwable>[] rollbackFor() default {};
# 异常类ClassName,可以是FullName/SimpleName
String[] rollbackForClassName() default {};

NoRollback

Для некоторой бизнес-логики, требующей специальной обработки, например вставки каких-то таблиц журналов, или неважных бизнес-процессов, я надеюсь, что даже если и будет ошибка, это не повлияет на отправку транзакции.

Этого можно добиться, настроив NoRollbackFor, чтобы некоторые исключения не влияли на состояние транзакции.

# 异常类Class
Class<? extends Throwable>[] noRollbackFor() default {};
# 异常类ClassName,可以是FullName/SimpleName
String[] noRollbackForClassName() default {};

только чтение управления

Установить флаг только для чтения текущей транзакции, что эквивалентноconnection.setReadOnly()

Объяснение ключевых терминов

имя существительное концепция
PlatformTransactionManager Менеджер транзакций, который управляет методами жизненного цикла транзакций, называется TxMgr.
TransactionAttribute Атрибуты транзакции, включая уровень изоляции, поведение распространения, доступ только для чтения и другую информацию, называемую TxAttr.
TransactionStatus Статус транзакции, включая текущую транзакцию, ожидание и другую информацию, называемую TxStatus.
TransactionInfo Информация о транзакции, включая TxMgr, TxAttr, TxStatus и другую информацию, называемую TxInfo.
TransactionSynchronization Обратный вызов синхронизации транзакций, включая несколько методов перехвата, называемых TxSync/синхронизацией транзакций.
TransactionSynchronizationManager Диспетчер синхронизации транзакций, поддерживает текущие ресурсы транзакций потока, информацию и коллекцию TxSync.

Фундаментальный

public void execute(TxCallback txCallback){
    //获取连接
    Connection connection = acquireConnection();
    try{
        //执行业务代码
        doInService();
        //提交事务
        connection.commit();
    }catch (Exception e){
        //回滚事务
        rollback(connection);
    }finally {
        //释放连接
        releaseConnection(connection);
    }
}

Основным принципом управления транзакциями Spring является приведенный выше код: получить соединение -> выполнить код -> зафиксировать/откатить транзакцию. Spring просто абстрагирует этот процесс, и все операции, связанные с транзакциями, передаются TransactionManager для реализации, а затем инкапсуляции.ввод шаблонавыполнить

Напримерorg.springframework.transaction.support.TransactionTemplateРеализация:

@Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        }
        else {
            //通过事务管理器获取事务
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                //执行业务代码
                result = action.doInTransaction(status);
            }
            //处理异常回滚
            catch (RuntimeException ex) {
                // Transactional code threw application exception -> rollback
                rollbackOnException(status, ex);
                throw ex;
            }
            catch (Error err) {
                // Transactional code threw error -> rollback
                rollbackOnException(status, err);
                throw err;
            }
            catch (Exception ex) {
                // Transactional code threw unexpected exception -> rollback
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            //提交事务
            this.transactionManager.commit(status);
            return result;
        }
    }

Транзакции в виде аннотаций (@Transactional), механизм реализации тот же.На базе АОП Spring вышеописанный режим Шаблона заменен на автоматический АОП, а в АОП Interceptor(org.springframework.transaction.interceptor.TransactionInterceptor) для выполнения этого процесса:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
            throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        //获取事务管理器
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(method, targetClass);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            //创建事务
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                //执行被“AOP”的代码
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                //处理异常回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                //清除资源
                cleanupTransactionInfo(txInfo);
            }
            
            //提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
   ....
}

Ключ к распространению транзакций / сохранению одной и той же транзакции в сложных процессах:

Для более сложных бизнес-процессов будут вызовы между различными классами.Как Spring поддерживает одну и ту же транзакцию?

На самом деле основной принцип очень прост, вам нужно только неявно сохранить текущую транзакцию (Connection) в менеджер транзакций, а последующие методы можно получить из менеджера транзакций перед выполнением операции JDBC:

НапримерHibernateTemplateсерединаSessionFactoryсерединаgetCurrentSession,здесьgetCurrentSessionполучен от (возможно, косвенного) менеджера транзакций Spring

Менеджер транзакций Spring будет обрабатывать связанные временные ресурсы (соединение и т. д.)org.springframework.transaction.support.TransactionSynchronizationManager, поддерживаемый ThreadLocal

public abstract class TransactionSynchronizationManager {

    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<String>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<Boolean>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<Integer>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<Boolean>("Actual transaction active");
    ...
}

Для некоторых сложных сценариев вложенные транзакции + независимые транзакции, включая приостановку и возобновление, связанные ресурсы также хранятся в **TransactionSynchronizationManager** для облегчения обработки вложенных транзакций.

Например, когда A->B, метод A уже начал транзакцию и привязал текущий ресурс транзакции вTransactionSynchronizationManager, то перед выполнением B он обнаружит, есть ли текущая транзакция; метод обнаружения должен начинаться сTransactionSynchronizationManagerНайдите и определите состояние. Если оно уже есть в транзакции, выполните другую логику в соответствии с различными конфигурациями поведения распространения. Обработка поведения распространения, такого как REQUIRES_NEW, будет более сложной, что потребует «приостановки» и возобновления (возобновления). ) принцип работы аналогичен, поэтому я не буду здесь слишком много объяснять

Общая проблема

Транзакция не вступает в силу

Есть следующий код, запись - тестовый метод, в методе testTx настроена аннотация @Transactional, после вставки данных выбрасывается исключение RuntimeException, но после выполнения метода откат вставленных данных не происходит, а вставка прошла успешно

public void test(){
    testTx();
}

@Transactional
public void testTx(){
    UrlMappingEntity urlMappingEntity = new UrlMappingEntity();
    urlMappingEntity.setUrl("http://www.baidu.com");
    urlMappingEntity.setExpireIn(777l);
    urlMappingEntity.setCreateTime(new Date());
    urlMappingRepository.save(urlMappingEntity);
    if(true){
        throw new RuntimeException();
    }
}

Причина, по которой это не вступает в силу, заключается в том, что метод/класс записи не имеет аннотации @Transaction.Поскольку диспетчер транзакций Spring также реализован на основе АОП, будь то Cglib (ASM) или динамический прокси-сервер Jdk, он по существу механизм подкласса. ;Вызов метода между одним и тем же классом будет напрямую вызывать код этого класса и не будет выполнять код динамического прокси; поэтому в этом примере, поскольку метод входаtestПрокси-аннотация не добавлена, поэтомуtextTxАннотации транзакций, добавленные к методам, не вступят в силу.

Аннулирование транзакции после асинхронности

Например, в методе транзакции включена библиотека операций дочернего потока, тогда транзакция дочернего потока отличается от транзакции основного потока.

Поскольку в диспетчере транзакций Spring ресурсы, связанные с транзакциями (подключение, сеанс, статус транзакции и т. д.), хранятся в TransactionSynchronizationManager, который хранится через ThreadLocal. Если он является многопоточным, транзакция не может быть гарантирована.

# TransactionSynchronizationManager.java
private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");

Не удалось зафиксировать транзакцию

org.springframework.transaction.UnexpectedRollbackException: 
Transaction silently rolled back because it has been marked as rollback-only

Это исключение вызвано вызовом между несколькими методами транзакции в рамках одной транзакции, и дочерний метод создает исключение, но игнорируется родительским методом.

Поскольку дочерний метод генерирует исключение, диспетчер транзакций Spring помечает текущую транзакцию как состояние сбоя и готов к откату, но когда дочерний метод выполняется и извлекается из стека, родительский метод игнорирует исключение и это будет нормально после выполнения метода.При фиксации менеджер транзакций проверяет статус отката и выдает это исключение, если есть флаг отката. Для получения подробной информации см.org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit

Образец кода:

A -> B
# A Service(@Transactional):
public void testTx(){
    urlMappingRepo.deleteById(98l);
    try{
        txSubService.testSubTx();
    }catch (Exception e){
        e.printStackTrace();
    }
}

# B Service(@Transactional)
public void testSubTx(){
    if(true){
        throw new RuntimeException();
    }
}

Ссылаться на