четверг, 21 января 2010 г.

Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1)

это перевод пятой главы книги Hibernate In Action 
Давайте поближе взглянем на один из главных вопросов в проектировании приложений: управление транзакциями. В этой главе, мы рассмотрим, как использовать Hibernate для управления транзакциями, как обрабатывается параллелизм, и как кэширование относится к обоим аспектам. Давайте взглянем на наше тестовое приложение.
Некоторая функциональность приложения требует, чтобы несколько различных вещей делались вместе. Например, когда аукцион заканчивается, наш приложение CaveatEmptor выполняет четыре различных задачи:
   1.    Отмечает лидирующую (наибольшее количество) ставку.
   2.    Назначает цену аукциона, установленную продавцом.
   3.    Назначает ставку успешного игрока, сделавшего лидирующую ставку.
   4.    Уведомляет продавца и успешных игроков.
Что произойдет, если мы не сможем установить ставку аукциона, из-за сбоя внешней системы кредитных карт?
Наши бизнес требования могут состоять в том, чтобы все составленные в список действия, должны  быть выполнены успешно или ни одно не должно быть выполнено. Если так, то мы назовем эти шаги транзакцией или единицей работы. Если хотя бы один шаг потерпит неудачу,  то вся единица работы должна потерпеть неудачу. Будем говорить, что транзакция атомарна. Несколько операций группируются вместе, как единые, неделимые единицы.
К тому же, транзакции разрешают многочисленным пользователям работать параллельно с одними данными без компромиссов целостности и корректности данных; различные транзакции не должны быть видимы и не должны оказывать влияние на другие параллеьно-запущенные транзакции. Несколько различных стратегий используются для реализации этого требования, которое называется изолированностью. Мы исследуем их в данной главе.
Транзакции также проявляют согласованность и долговечность. Согласованность означает, что любые транзакции работают с последовательным набором данных и оставляют данные в непротиворечивом состоянии, когда транзакция завершается.  Долговечность гарантирует, что когда одна транзакция завершается, все изменения, сделанные в течение этой транзакции, сохранятся и не будут потеряны, даже если система впоследствии даст сбой. Атомарность, последовательность, изолированность и долговечность вместе известны, как ACID критерии.
Мы начали эту главу с дискуссии о системном уровне  транзакций БД, где БД гарантирует ACID поведение. Мы взглянем на JDBC и JTA API и увидим, как Hibernate, работая как клиент с этим API, используется для контроля транзакций БД.
В онлайн-приложении, транзакции БД должны иметь чрезвычайно короткий срок жизни. Транзакции БД должны распространяться на одну серию операций с БД, чередующихся с бизнес-логикой.  Мы расширим ваше понимание транзакций, пониманием длинных транзакций приложения, где операции БД происходят в различных сериях операций, чередующихся с пользовательским взаимодействием. Есть несколько путей для реализации транзакций приложений в Hibernate приложениях, все они обсуждаются в данной главе. Заключительно, предмет кэширования ближе относится к транзакциям, чем может показаться на первый взгляд. Во второй половине этой главы, вооруженные пониманием транзакций, мы исследуем Hibernate’овскую сложную архитектуру кэширования. Вы узнаете, какие данные являются хорошим кандидатом на кэширование и как управлять параллелизмом КЭШа.
Давайте начнем с основ и увидим, как транзакции работают на нижнем уровне БД.

5.1 Понимание транзакций БД
БД реализует понятие единицы работы, как транзакцию БД (иногда называемую системная транзакция). Транзакции БД группируют операции доступа к данным. Транзакция гарантирует один из результатов работы: она или будет зафиксирована или откачена назад.  Следовательно,  транзакции БД действительно всегда атомарны.
Если несколько операций БД должны выполняться внутри транзакции, вы должны отметить границы единиц работы. Вы должны начать транзакцию и, в некоторой точке,  совершить изменения. Если ошибка случается (или во время выполнения операций или когда  применяются изменения), вы откатываете транзакцию, чтобы оставить данные в согласующемся состоянии. Это известно, как разграничение транзакции, и (в зависимости от используемого API) это включает большее или меньшее ручное вмешательство.
Вы возможно уже имеете опыт управления с двумя программными интерфейсами, с возможностью управления транзакциями: JDBC API и JTA.

5.1.1 JDBC и JTA транзакции

В неуправляемой среде, JDBC API используется для отметки границ транзакций. Вы начинаете транзакцию, вызывая setAutoCommit(false) на JDBC соединении и в конце, вызывая commit(). Вы можете, в любой момент выполнить немедленный откат, вызывая rollback().

FAQ
   
Что должен делать режим авто-коммита? Магическая установка, которая часто является источником недоразумений в JDBC соединениях - это режим автоматического коммита. Если соединение БД находится в режиме автоматического выполнения, то транзакция БД будет выполнена незамедлительно после каждого SQL выражения, и при начале новой транзакции. Это может быть полезно для расширенных запросов к БД и для расширенных обновлений.
Однако, режим авто-коммита чаще всего является не подходящим в приложениях. Приложение не выполняет продвинутые или любые незапланированные запросы; вместо этого, оно выполняет заранее запланированные последовательность связанных операций (которые, по определению, никогда не являются продвинутыми). Поэтому Hibernate автоматически запрещает режим авто-коммита сразу при установлении соединения (из провайдера соединений – это пул соединений). Если вы поддериживаете ваши собственные соединения, когда вы открываете Session, то на вашей ответственности лежит отключение авто-коммита!

Учтите, что некоторые СБД включают авто-коммит по умолчанию для каждого нового соединения, но другие не делают! Вы можете захотеть отключить авто-коммит в вашей глобальной системной конфигурации БД для того, чтобы быть уверенным, что вы никогда не столкнетесь с любыми проблемами. Вы можете разрешить авто-коммит только во время выполнения продвинутых запросов (например, в вашем инструменте для SQL запросов).
В системах, которые сохраняют данные в нескольких БД, обычная единица работы может включать доступ более чем к одному хранилищу данных. При этом, вы не можете достигать атомарности, используя один JDBC. Вам необходим менеджер транзакций с поддержкой для распределенных транзакций (двухфазный коммит). Вы связываетесь с менеджером транзакций, используя JTA.
В управляемой среде, JTA используется не только для распределенных транзакций, но также для декларативного контейнера управляемых транзакций(CMT). CMT позволяет вам избегать явного разграничения вызова транзакций в исходном коде вашего приложения; напротив, разграничение транзакции контролируется дескриптором, зависимым от среды развертывания. Этот дескриптор определяет порядок распространения контекста транзакции, когда один поток проходит через несколько различных EJB.
Мы не заинтересованы в деталях прямого JDBC или JTA разграничения транзакций. Вы будете использовать эти интерфейсы лишь косвенно.
Hibernate взаимодействует с БД через JDBC соединение;  следовательно, он должен поддерживать оба интерфейса. В автономных (или веб) приложениях доступны только JDBC транзакции; в сервере приложений Hibernate может использовать JTA. Поскольку мы хотели, чтобы код Hibernate приложения выглядел одинаково в управляемых и неуправляемых средах, то Hibernate предоставляет свой уровень абстракции, скрывая нижележащий интерфейс транзакций. Hibernate разрешает пользовательские расширения, так чтобы вы могли даже подключить адаптер для CORBA службы транзакций.
Управление транзакциями предоставляется разработчикам приложений через интерфейс Transaction. Вы не обязаны использовать этот интерфейс – Hibernate позволяет контролироваться JTA или JDBC транзакции напрямую, однако такое использование не приветствуется, и мы не будем обсуждать этот вариант.
Интерфейс Transaction предоставляет методы для объявление границ транзакций БД. Смотри листинг 5.1 для примера использования основных операций Transaction.
Listing 5.1 Использование Hibernate Transaction интерфейса
Session session = sessions.openSession();
Transaction tx = null;
try {
  tx = session.beginTransaction();
  concludeAuction();
  tx.commit();
} catch (Exception e) {
  if (tx != null) {
    try {
      tx.rollback();
    } catch (HibernateException he) {
       //log he and rethrow e
    }
  }
  throw e;
} finally {
  try {
    session.close();
  } catch (HibernateException he) {
  throw he;
  }
}

Вызов session.beginTransaction() отмечает начало транзакции БД.  В случае неуправляемой среды, этот вызов стартует JDBC транзакцию на JDBC соединении.  В случае управляемой среды, он начинает новую JTA транзакцию, если нет текущей транзакции JTA или присоединяется к текущей JTA транзакции. Это все обрабатывается Hibernate – вам не нужно заботиться о реализации этого.
Вызов tx.commit() синхронизирует состояние Session с БД. Hibernate затем коммитит соверщаемую транзакцию тогда и только тогда если beginTransaction() начал новую транзакцию (в обоих случаях). Если beginTransaction() не начал транзакцию БД, то commit() только синхронизирует состояние Session c БД; это оставлено на усмотрение ответственного участника (код, который начал транзакцию в первую очередь) к моменту завершения транзакции. Это согласуется с поведением, определенным JTA.
Если concludeActions() выбрасывает исключение, то мы должны принудительно  откатить транзакцию, вызывая tx.rollback(). Этот метод либо осуществляет немедленный откат или помечает транзакцию, как «только для отката» (если вы используете CMT).
FAQ
  
Быстрее ли откатываются немодифицирующие(read-only) транзакции? Если код транзакции считывает данные, но никогда их не модифицирует, должны ли вы откатывать транзакцию, вместо того, чтобы коммитить её? Будет ли это быстрее?
Видимо, некоторые разработчики решили, что такой подход быстрее в некоторых особых обстоятельствах, и эта вера в настоящее время распространилась на все сообщество. Мы проверили это предположение на наиболее популярных СУБД и не нашли никаких различий. Нам также не удалось обнаружить источник реальных цифр, указывающих на разницу в производительности. Также, не существует никаких причин, почему СУБД должны реализовывать такое поведение неоптимально – то есть, почему она не должна использовать быстрый внутренний алгоритм очистки транзакций. Всегда коммитьте вашу транзакцию и откатываете, если её не удалось совершить.
Критически важно закрывать Session в финальном блоке, с тем, чтобы обеспечить освобождение JDBC соединения и возвращения его в пул соединений (этот шаг является обязанностью самого приложения, даже в управляемой среде).
ПРИМЕЧАНИЕ
  
Пример в листинге 5.1 является стандартным для единицы работы Hibernate; поэтому, он включает в себя код проверки обработки исключений, для проверки HibernateException. Как вы можете видеть, даже откат Transaction и закрытие Session могут вызвать исключение. Вы не захотите, использовать этот пример в качестве шаблона в собственном приложении, так как лучше спрятать обработку ошибок с общим инфраструктурным кодом. Можно, например, использовать утилитарный класс для конвертации HibernateException в непроверяемое исключение времени выполнения, и скрывать детали отката транзакции и закрытия сессии. Мы обсудим этот вопрос о применении дизайна более подробно в главе 8, разделе 8.1, «Проектирование многослойных приложений».
Однако, есть один важный аспект, который вы должны знать: Session должна быть немедленно закрыта и отбрасываться (не использована повторно), когда происходит исключение.  Hibernate не может повторить неудавшуюся транзакцию. На практике, это не является проблемой, так как исключения БД, как правильно фатальны (например, нарушение ограничений) и нет четко определенного поведения для продолжения после неудавшейся транзакции. Приложение в режиме реальной работы(production) не должно бросать любых исключений БД.
Мы отметили, что вызов commit() синхронизирует состояние Session c БД. Это называется очистка (промывка, flushing)  процесса, которое вы автоматически инициируете, когда используете интерфейс Hibernate Transaction.

13 комментариев:

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

    Как-то сложновато описано. Мне даже показалось, что если человек знает JTA и JDBC, то он уж наверняка должен знать, что такое транзакция.
    Вобщем, статья вызывает какие-то противоречивые чувства))

    ОтветитьУдалить
  2. 1, 2 - Fixed

    Сложновато читается потому что я не владею английским языком настолько, чтобы читалось легко :). Но все же смысл по-моему понятен и многим будет полезно.

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

    ОтветитьУдалить
  3. Фишка Hibernate транзакций в том, что операции проходят в сессии(Session), а сессия также является кэшем(первого уровня), который периодически или по требованию синхронизируется с БД. Вообще, авторы Hibernate утверждают, что приложение, грамотно использующее ORM, будет значительно быстрее приложения использующего прямой JDBC, как раз из-за кэша, который при прямом JDBC нереализуем. Но обо всем этом в следующих записях.

    ОтветитьУдалить
  4. Спасибо за статью! Я давно хотел начать hibernate изучать, статьи меня еще больше мотивируют. Начну хотябы с них :-)

    ОтветитьУдалить
  5. Не за что, Александр! Толковых материалов по Hibernate немного, тем более на русском языке. А на английском, не у всех есть возможность читать. Понимать, то понимаешь, но скорость чтения английской книги может быть в 3 раза меньше.

    ОтветитьУдалить
  6. Спасибо за перевод.

    Две просьбы:
    1. Сделайте, пожалуйста, у всех статей цикла отдельный тег, например, как "Hibernate in Action" у этой
    2. Пожалуйста, вставьте в начало каждой статьи ссылки на другие

    а то неудобно искать и читать

    ОтветитьУдалить
  7. Андрей, спасибо за совет! Так и сделал.

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

    ОтветитьУдалить
  9. А "спасибо" забыл сказать ;)
    Спасибо - стало гораздо удобнее

    ОтветитьУдалить
  10. спасибо за затраченные усилия!

    ОтветитьУдалить
  11. Отличная статья спасибо

    ОтветитьУдалить
  12. Очень хорошая и полезная статья! спасибо!

    ОтветитьУдалить