Говоря о 7 видах характеристик распространения в транзакции Spring

Spring

Какова характеристика распространения транзакции?

Проще говоря, когда в системе два метода транзакций (мы временно вызываем метод А и метод Б), если в методе А вызывается метод Б, токакой вид бизнеса, которая называется характеристикой распространения транзакции

Например, метод A вызывает метод B (метод B должен использовать аннотацию транзакции), тогда транзакция B может быть транзакцией, вложенной в A, или транзакция B не использует транзакцию, или использует ту же транзакцию, что и транзакция A, это может быть достигнуто путем определение характеристик распространения транзакций

Как настроить функцию распространения транзакций?

первое использованиеorg.springframework.transaction.annotationв упаковке@TransactionalАннотация, объявляйте свойство распространения в нем (значение по умолчанию распространяется. Required):

    @Transactional(propagation = Propagation.REQUIRED)

Каковы характеристики распространения транзакций?

В настоящее время веснаTransactionDefinitionВ классе определены следующие 7 характеристик распространения, и далее мы проанализируем конкретные характеристики:

  • PROPAGATION_REQUIRED: Если внешней транзакции нет, активно создайте транзакцию, в противном случае используйте внешнюю транзакцию.
  • PROPAGATION_SUPPORTS: Если внешней транзакции нет, не открывайте транзакцию, в противном случае используйте внешнюю транзакцию.
  • PROPAGATION_MANDATORY: если внешней транзакции нет, генерировать исключение, в противном случае использовать внешнюю транзакцию
  • PROPAGATION_REQUIRES_NEW: Всегда активно открывать транзакцию; если есть внешняя транзакция, приостановить внешнюю транзакцию
  • PROPAGATION_NOT_SUPPORTED: Всегда не запускать транзакцию; если есть внешняя транзакция, приостановить внешнюю транзакцию
  • PROPAGATION_NEVER: Всегда не запускать транзакцию; если есть внешняя транзакция, генерируется исключение
  • PROPAGATION_NESTED: Если внешней транзакции нет, активно создайте транзакцию, в противном случае создайте вложенную подтранзакцию.

Зачем указывать функцию распространения транзакций?

Кто-то может подумать, а зачем указывать функцию распространения, разве нельзя запускать транзакцию для всех исполнений методов? Я не буду пока объяснять это здесь, прочитав следующую часть, я думаю, что ответ будет.

Каковы характеристики каждой функции распространения транзакции?

Перед спецификацией давайте сделаем некоторые приготовления.

Сначала определите две таблицыuserиnote, пользовательская таблица имеетidиnameДва столбца данных, таблица заметок имеетidиcontentдва столбца данных

Затем создайте новый проект springboot, создайте соответствующий класс User/Note, а также интерфейсы dao и service и другие части (для удобства демонстрации класс Service, который я создал напрямую, без использования интерфейса), не будут перечислены по одному.

Затем наступает момент, мы сначала определяем в UserServiceinsertUserметод:

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        // 插入用户之后,我们插入一条用户笔记
        noteService.insertNote(name + "'s note");
    }

в соответствующем NoteServiceinsertNoteМетоды, как показано ниже:

    @Transactional(rollbackFor = Exception.class)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

Теперь давайте определим еще один метод тестирования, обратите внимание на отключение автоматического отката:

    @Test
    @Rollback(value = false)
    public void test() {
        userService.insertUser("hikari");
    }

Взгляните на текущий результат выполнения:

REQUIRED

Если внешней транзакции нет, открыть транзакцию активно, в противном случае использовать внешнюю транзакцию.

Хотя этот тип является функцией распространения по умолчанию, давайте укажем его вручную, имея в виду, что функция распространения действует навнутренний метод, поэтому наше дополнение к внешнему методу недопустимо:

    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

Но в настоящее время эти два метода не имеют никакого взаимодействия, поэтому мы вручную создаем точечные исключения:

Внешний метод закрывает транзакцию, а внутренний метод генерирует исключение.

    // @Transactional(rollbackFor = Exception.class)    // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }

    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        
        throw new RuntimeException();   // ←
    }

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

Мы можем обнаружить, что внешний метод не использует транзакцию (в пользовательской таблице есть данные), а внутренний метод использует транзакцию (таблица заметок откатывается, поэтому данных нет)

Внешний метод выдает исключение

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

результат операции:

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

SUPPORTS

Если внешней транзакции нет, не открывайте транзакцию, иначе используйте внешнюю транзакцию.

Чтобы не устать от чтения,存在外层事务则使用同一个事务Эта функция не будет демонстрироваться, мы продемонстрируем предыдущую функцию:

Внешний метод закрывает транзакцию, а внутренний метод генерирует исключение.

//  @Transactional(rollbackFor = Exception.class)   // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.SUPPORTS)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();   // ←
    }

Результат выглядит следующим образом:

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

MANDATORY

Если внешней транзакции нет, создайте исключение, в противном случае используйте внешнюю транзакцию.

Внешний метод закрывает транзакцию

//  @Transactional(rollbackFor = Exception.class)   // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.MANDATORY)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

результат операции:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

Указывает, что должна существовать внешняя транзакция для выполнения

Как и выше,«Если транзакция внешнего метода существует, внутренний метод использует ту же транзакцию»Эта функция здесь не повторяется.

REQUIRES_NEW

Всегда открывать транзакцию активно; если есть внешняя транзакция, приостановить внешнюю транзакцию

Внешний метод выдает исключение

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRES_NEW)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

результат операции:

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

Но некоторые люди могут подумать, что если внутренний метод выдает исключение, транзакцию внешнего метода нельзя откатывать, верно? Жаль, что не так, не ведитесь на дела,Внутренний метод выдает исключение (не пойманное с помощью try-catch), что эквивалентно выдаче исключения внешним методом., поэтому транзакция внешнего метода все равно будет отброшена

NOT_SUPPORTED

Всегда не запускайте транзакцию; если есть внешняя транзакция, приостановите внешнюю транзакцию

внутренний метод выдает исключение

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NOT_SUPPORTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();   // ←
    }

результат операции:

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

NEVER

Всегда открывать транзакцию; если есть внешний слой транзакции, генерируется исключение

Внешний метод запускает транзакцию

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NEVER)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

результат операции:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

иMANDATORYХарактеристики прямо противоположные,MANDATORYэто когда внешний метод не имеет транзакции для создания исключения, иNEVERЭто когда внешний метод имеет транзакцию, которая выдает исключение

NESTED

Если внешней транзакции нет, она будет активно создавать транзакцию, в противном случае — создавать вложенную подтранзакцию.

Внешний метод выдает исключение

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }

результат операции:

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

Некоторые люди могут столкнуться с этой ошибкой, потому что jpa/hibernate не поддерживает функцию «точки сохранения», иNESTEDзависит от"точка сохранения"Механизм, вы можете создать «временную точку» перед выполнением внутреннего метода, а затем открыть вложенную подтранзакцию.Если подтранзакция ненормальна, она будет отброшена непосредственно к этой временной точке, не вызывая общую транзакцию. быть отброшенным.

Но это не беда, мы можем использовать jdbcTemplate, модифицированный метод выглядит следующим образом:

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userDao.insertUser(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteDao.insertNote(note);
    }

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

NESTED/REQUIRES_NEWиREQUIREDКакая разница?NESTEDиREQUIRES_NEWКакая разница?

NESTED/REQUIRES_NEWиREQUIREDразница

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

Разница в том, что если во внешнем методе есть транзакция,REQUIREDбудет использовать ту же транзакцию, что и внешний метод, иNESTEDСоздается вложенная подтранзакция, и наиболее важное различие между двумя методами заключается в следующем:Если внутренний метод выдает исключение при использованииREQUIREDметод, даже если исключение будет перехвачено во внешнем методе, это все равно приведет к откату внешней транзакции (поскольку используется та же транзакция);NESTEDилиREQUIRES_NEWТаким образом, пока исключение перехвачено во внешнем методе, это не приведет к откату внешней транзакции.

    @Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        try {
            noteService.insertNote(name + "'s note");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();
    }

результат операции:

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

NESTEDиREQUIRES_NEWразница

Оба эти метода эквивалентны открытию новой транзакции, но самое важное различие между ними заключается в том,NESTEDявляется вложенной подтранзакцией, если внешняя транзакция откатывается, подтранзакция будет откатываться вместе, иREQUIRES_NEWметод не будет

сцены, которые будут использоваться

Неважные предметы/обычные сценарии/лень так много думать

Ничего не заполнять / ОБЯЗАТЕЛЬНО

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

REQUIRES_NEW

Внутренний метод зависит от внешнего метода, но внешний метод не хочет подвергаться влиянию внутреннего метода.

Вставьте запись в таблицу журнала после вставки информации о пользователе.

NESTED

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

SUPPORTS

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

NOT_SUPPORTED

Внутренние методы не разрешают транзакции, а внешние методы не позволяют разрешать транзакции.

NEVER

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

MANDATORY