Рубрики
Без рубрики

Точки сохранения транзакций в весеннем JDBC

Точки сохранения позволяют создавать маркеры в транзакции, к которым вы можете откатиться, без них… С тегами spring, sql, java, kotlin.

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

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

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

  • Использование JdbcTemplate
  • Использование Шаблона транзакции
  • @Транзакционная аннотация

Точки сохранения с помощью JdbcTemplate

Давайте начнем с JdbcTemplate :

@Component
class PersonManager(private val jdbcTemplate: JdbcTemplate) {

  fun process() {
    jdbcTemplate.execute { connection: Connection ->
      connection.autoCommit = false

      connection.save(1, dan)
      connection.save(2, laura)

      val savepoint = connection.setSavepoint()
      try {
        connection.save(1, george)
      } catch (e: SQLException) {
        log.info("There was an exception, rolling back to savepoint: ${e.message}")
        connection.rollback(savepoint)
      } finally {
        connection.releaseSavepoint(savepoint)
      }
    }
  }

  private fun Connection.save(id: Int, person: Person) {
    prepareStatement("INSERT INTO people(id, name, age) VALUES (?, ?, ?)").apply {
      setInt(1, id)
      setString(2, person.name)
      setInt(3, person.age)
    }.executeUpdate()
  }
}

Который выводит следующее (то же самое выводится во всех следующих разделах этого поста):

There was an exception, rolling back to savepoint: ERROR: duplicate key value violates unique constraint "people_pkey"
People => [Person(id=1, name=Dan, age=26), Person(id=2, name=Laura, age=25)]

Включение точек сохранения с помощью JdbcTemplate требует некоторых усилий. Вам нужно убедиться, что вы отключили Соединение ‘s Автоматическая фиксация режим, в противном случае вы увидите ошибку, подобную следующей:

Caused by: org.springframework.jdbc.UncategorizedSQLException: ConnectionCallback; uncategorized SQLException; SQL state [25P01]; error code [0]; 
    Cannot establish a savepoint in auto-commit mode.; nested exception is org.postgresql.util.PSQLException: Cannot establish a savepoint in auto-commit mode.
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:89) ~[spring-jdbc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1443) ~[spring-jdbc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:336) ~[spring-jdbc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at dev.lankydan.jdbc.PersonManager.processWithJdbcTemplate(Application.kt:73) ~[main/:na]
    at dev.lankydan.jdbc.PersonManager$$FastClassBySpringCGLIB$$56c95970.invoke() ~[main/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:685) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at dev.lankydan.jdbc.PersonManager$$EnhancerBySpringCGLIB$$d54410c6.processWithJdbcTemplate() ~[main/:na]
    at dev.lankydan.jdbc.Application.run(Application.kt:32) ~[main/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    ... 6 common frames omitted

После этого вы можете создать точку сохранения, используя Connection.setsavepoint .

Затем операторы SQL могут выполняться как обычно. Любые фрагменты кода, которые, по вашему мнению, могут вызвать проблемы (но приведут только к мягким сбоям), должны быть обернуты блоком try/catch. Если возникнут какие-либо ошибки, они могут быть обработаны в рамках catch. Это то, что демонстрирует приведенный выше пример кода.

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

Перед совершением транзакции стоит освободить точку сохранения с помощью releasesavepoint . Говоря это, не все драйверы JDBC на самом деле поддерживают освобождение точек сохранения. Javadocs отмечают, что Исключение SQLFeatureNotSupportedException создается драйверами, которые не поддерживают отпустите Точку сохранения . Таким образом, вам, возможно, придется следить за этими потенциальными ошибками.

Точки сохранения с таблицей транзакций

Шаблон транзакции работает аналогично JdbcTemplate но с большим акцентом на поддержку транзакций название выдает это, верно?

@Component
class PersonManager(
  private val jdbcTemplate: JdbcTemplate,
  private val transactionTemplate: TransactionTemplate
) {

  fun process() {
    transactionTemplate.execute { status: TransactionStatus ->
      jdbcTemplate.save(1, dan)
      jdbcTemplate.save(2, laura)

      val savepoint = status.createSavepoint()
      try {
        jdbcTemplate.save(1, george)
      } catch (e: DataAccessException) {
        // [DataAccessException] because spring converts the underlying [SQLException] inside of [update]
        log.info("There was an exception, rolling back to savepoint: ${e.message}")
        status.rollbackToSavepoint(savepoint)
      } finally {
        status.releaseSavepoint(savepoint)
      }
    }
  }

  private fun JdbcTemplate.save(id: Int, person: Person) {
    update("INSERT INTO people(id, name, age) VALUES (?, ?, ?)") { statement ->
      statement.setInt(1, id)
      statement.setString(2, person.name)
      statement.setInt(3, person.age)
    }
  }
}

Этот код очень похож на тот, что показан в JdbcTemplate пример. Результат в точности такой же, и между ними всего несколько различий. TransactionTemplate обеспечивает поддержку транзакций, которая не предлагается JdbcTemplate без кода, показанного ранее.

Чтобы использовать точки сохранения с TransactionTemplate , вы должны взаимодействовать со статусом транзакции , переданным в выполнить лямбда-выражение. Это позволяет создать точку сохранения с помощью Createsavepoint , откат с помощью rollbackToSavepoint а затем отпустите его, вызвав releaseSavepoint . На самом деле, больше нечего сказать.

Обратите внимание, что JdbcTemplate используется в Обратите внимание, что JdbcTemplate используется в

Сохраняйте баллы с помощью @Transactional

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

@Component
class PersonManager(
  private val jdbcTemplate: JdbcTemplate,
  private val transactionalAnnotationManager: TransactionalAnnotationPersonManager
) {

  @Transactional
  fun process() {
    jdbcTemplate.save(1, dan)
    jdbcTemplate.save(2, laura)

    try {
      transactionalAnnotationManager.processWithTransactionalAnnotationSavepoint()
    } catch (e: DataAccessException) {
      log.info("There was an exception, rolling back to savepoint: ${e.message}")
    }
  }
}

@Component
class TransactionalAnnotationPersonManager(private val jdbcTemplate: JdbcTemplate) {

  // By default, auto rollbacks for runtime exceptions but not checked exceptions
  // [DataAccessException] is a runtime exception, hence the rollback
  @Transactional(propagation = Propagation.NESTED)
  fun processWithSavepoint() {
    jdbcTemplate.save(1, george)
  }
}

private fun JdbcTemplate.save(id: Int, person: Person) {
  update("INSERT INTO people(id, name, age) VALUES (?, ?, ?)") { statement ->
    statement.setInt(1, id)
    statement.setString(2, person.name)
    statement.setInt(3, person.age)
  }
}

Этот пример совсем не похож на первые два.

@Transactional включает управление транзакциями, начиная с первой функции, вызываемой с аннотацией. Последующие вызовы функций JdbcTemplate будут выполняться в рамках транзакции, созданной функцией с аннотацией @Транзакционный . По-видимому, это рекомендуемый способ включения транзакций в Spring JDBC.

Включение точек сохранения с помощью @Transactional немного странно. Чтобы объяснить, почему, вам сначала понадобится немного фонового контекста. Когда компонент Spring содержит функцию с аннотацией @Транзакционный , создается экземпляр прокси-сервера, который стоит перед исходным классом ( PersonManager и Транзакционная аннотация Person Manager в приведенном выше примере). Вызовы этих классов вместо этого будут взаимодействовать с прокси-сервером, который обертывает исходные вызовы функций в транзакции. Прокси-сервер перехватывает вызовы только от внешних классов. Любые вызовы других функций внутри класса не будут применять перенос транзакций, который выполняет прокси-сервер.

Это затрудняет включение сохранения очков при использовании @Транзакционный . Если есть вызов функции, помеченной @Транзакционный (распространение. ВЛОЖЕННЫЙ) ((что означает использование точки сохранения), он ничего не будет делать, если не будет вызван из внешнего класса (вне прокси-сервера). Вот почему в этом примере есть два класса. Один из них создает оригинальную всеобъемлющую транзакцию. Другой создаст внутреннюю транзакцию (используя точку сохранения) при вызове из существующей транзакции.

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

Несмотря на наличие двух классов, объем кода не особенно плох. Тем не менее, на первый взгляд это немного сбивает с толку, и он многое скрывает за занавеской, размахивая руками и говоря “узрите волшебство весны” . В целом я нормально отношусь к магии Весны, но эта была для меня чересчур.

Резюме

Spring предоставляет вам несколько способов управления точками сохранения в транзакциях:

  • Использование JdbcTemplate и управление соединением дает вам
  • Использование TransactionTemplate и абстракция, которую он предоставляет по сравнению с вышеизложенным
  • Аннотирование функций с помощью @Transactional (распространение. ВЛОЖЕННЫЙ) чтобы весна окропила тебя волшебством

Если вам понравился этот пост или вы сочли его полезным (или и то, и другое), пожалуйста, не стесняйтесь подписываться на меня в Твиттере по адресу @Lankydandev и не забудьте поделиться со всеми, кто может найти это полезным!

Оригинал: “https://dev.to/lankydandev/transaction-savepoints-in-spring-jdbc-5202”