From 892469689c48828e06fb7022bcd354a6d30abd1f Mon Sep 17 00:00:00 2001 From: Konstantin Krivopustov Date: Mon, 10 Dec 2012 14:39:02 +0000 Subject: [PATCH] Refs #1633 CUBA documentation (transactions) --- doc/content/manual/ru/chapter_framework.xml | 203 +++++++++++++++++++- doc/content/manual/ru/manual.xml | 9 + 2 files changed, 205 insertions(+), 7 deletions(-) diff --git a/doc/content/manual/ru/chapter_framework.xml b/doc/content/manual/ru/chapter_framework.xml index f66b642491..66c67edf99 100644 --- a/doc/content/manual/ru/chapter_framework.xml +++ b/doc/content/manual/ru/chapter_framework.xml @@ -3052,17 +3052,206 @@ query.setParameter(1, customer);
Управление транзакциями - Транзакция Middleware напрямую отображается на транзакцию БД. - Управление транзакциями производится только на уровне сервисов или ниже. +
+ Программное управление транзакциями + Программное управление транзакциями осуществляется с помощью интерфейса com.haulmont.cuba.core.Transaction, ссылку на который можно получить методами createTransaction() или getTransaction() интерфейса инфраструктуры + Persistence + . + Метод createTransaction() создает новую транзакцию и возвращает интерфейс Transaction. Последующие вызовы методов commit(), commitRetaining(), end() этого интерфейса управляют созданной транзакцией. Если в момент создания существовала другая транзакция, то она будет приостановлена, и возобновлена после завершения созданной. + Метод getTransaction() вызывает либо создание новой, либо присоединение к текущей транзакции. Если в момент вызова существовала активная транзакция, то метод успешно завершается, и последующие вызовы commit(), commitRetaining(), end() не оказывают никакого влияния на существующую транзакцию. Однако если end() вызван без предварительного вызова commit(), то текущая транзакция помечается как RollbackOnly. + Пример ручного управления транзакцией:@Inject +private Persistence persistence; +... +Transaction tx = persistence.createTransaction(); +try { + EntityManager em = persistence.getEntityManager(); + Customer customer = new Customer(); + customer.setName("John Smith"); + em.persist(customer); + + tx.commit(); +} finally { + tx.end(); +} + Интерфейс Transaction имеет также метод execute(), принимающий на вход класс-действие, которое нужно выполнить в данной транзакции. Это позволяет организовать управление транзакциями в функциональном стиле, например:persistence.createTransaction().execute(new Transaction.Runnable() { + public void run(EntityManager em) { + // transactional code here + } +}); + Если транзакционный блок должен вернуть результат, класс-действие должен реализовывать интерфейс Transaction.Callable. Если результат не требуется, как в приведенном примере, то класс-действие удобно наследовать от абстрактного класса Transaction.Runnable. + Следует иметь в виду, что метод execute() у некоторого экземпляра Transaction можно вызвать только один раз, так как после выполнения кода класса-действия транзакция завершается. +
+
+ Декларативное управление транзакциями + Любой метод управляемого бина Middleware можно пометить аннотацией @org.springframework.transaction.annotation.Transactional, что вызовет автоматическое создание транзакции при вызове этого метода. В таком методе не нужно вызывать Persistence.createTransaction(), а можно сразу получать EntityManager и работать с ним. + Для аннотации @Transactional можно указать параметры. Основным параметром является режим создания транзакции - Propagation. Значение REQUIRED соответствует getTransaction(), значение REQUIRES_NEW - createTransaction(). По умолчанию REQUIRED. - Любой клиент, вызывая методы сервиса, должен понимать, что каждый метод будет выполняться в отдельной транзакции. -Используется только ручное (не декларативное) управление транзакциями. - Управление транзакциями является частью бизнес-логики. -При использовании ORM изменения в БД производятся при коммите транзакции, который в случае декларативного управления происходит за рамками прикладного кода (в интерцепторах контейнера), что крайне неудобно при отладке приложения. - При использовании ORM доменные объекты ведут себя принципиально по-разному внутри и вне транзакции + Декларативное управление транзакциями позволяет уменьшить количество boilerplate кода, однако имеет следующий недостаток: коммит транзакции проиходит вне прикладного кода, что часто затрудняет отладку, т.к. скрывается момент отправки изменений в БД и перехода сущностей в состояние Detached. Кроме того, следует иметь в виду, что декларативная разметка сработает только в случае вызова метода контейнером, т.е. вызов транзакционного метода из другого метода того же самого объекта не приведет к старту транзакции. + + В связи с этим рекомендуется применять декларативное управление транзакциями только для простых случаев типа метода сервиса, читающего некоторый объект и возвращающего его на клиента. +
+
+ Примеры взаимодействия транзакций +
+ Откат вложенной транзакции + Если вложенная транзакция создана через getTransaction(), то ее откат приведет к невозможности коммита охватывающей транзакции. Например:void methodA() { + Transaction tx = persistence.createTransaction(); + try { + // (1) вызываем метод, создающий вложенную транзакцию + methodB(); + + // (4) в этот момент будет выброшено исключение, т.к. транзакция + // помечена как rollback only + tx.commit(); + } finally { + tx.end(); + } +} + +void methodB() { + Transaction tx = persistence.getTransaction(); + try { + // (2) допустим здесь возникло исключение + tx.commit(); + } catch (Exception e) { + // (3) обрабатываем его и выходим + return; + } finally { + tx.end(); + } +} + Если же транзакция в methodB() будет создана через createTransaction(), то ее откат не окажет никакого влияния на коммит охватывающей транзакции в methodA(). +
+
+ Чтение и изменение данных во вложенной транзакции + Рассмотрим сначала зависимую вложенную транзакцию, создаваемую через getTransaction():void methodA() { + Transaction tx = persistence.createTransaction(); + try { + EntityManager em = persistence.getEntityManager(); + + // (1) загружаем сущность, в которой name == "old name" + Employee employee = em.find(Employee.class, id); + assertEquals("old name", employee.getName()); + + // (2) присваиваем новое значение полю + employee.setName("name A"); + + // (3) вызываем метод, создающий вложенную транзакцию + methodB(); + + // (8) здесь происходит коммит изменений в БД, и в ней + // окажется значение "name B" + tx.commit(); + + } finally { + tx.end(); + } +} + +void methodB() { + Transaction tx = persistence.getTransaction(); + try { + // (4) получаем тот же экземпляр EntityManager, что и methodA + EntityManager em = persistence.getEntityManager(); + + // (5) загружаем сущность с тем же идентификатором + Employee employee = em.find(Employee.class, id); + + // (6) значение поля новое, т.к. мы работаем с тем же + // персистентным контекстом, и обращения к БД вообще + // не происходит + assertEquals("name A", employee.getName()); + employee.setName("name B"); + + // (7) в этот момент реально коммита не происходит + tx.commit(); + } finally { + tx.end(); + } +} + Теперь рассмотрим тот же самый пример с независимой вложенной транзакцией, создаваемой через createTransaction(): void methodA() { + Transaction tx = persistence.createTransaction(); + try { + EntityManager em = persistence.getEntityManager(); + + // (1) загружаем сущность, в которой name == "old name" + Employee employee = em.find(Employee.class, id); + assertEquals("old name", employee.getName()); + + // (2) присваиваем новое значение полю + employee.setName("name A"); + + // (3) вызываем метод, создающий вложенную транзакцию + methodB(); + + // (8) здесь возникнет исключение из-за оптимистичной блокировки + // и коммит не пройдет вообще + tx.commit(); + + } finally { + tx.end(); + } +} + +void methodB() { + Transaction tx = persistence.createTransaction(); + try { + // (4) создается новый экземпляр EntityManager, т.к. это + // новая транзакция + EntityManager em = persistence.getEntityManager(); + + // (5) загружаем сущность с тем же идентификатором + Employee employee = em.find(Employee.class, id); + + // (6) значение поля старое, т.к. произошла загрузка из БД + // старого экземпляра сущности + assertEquals("old name", employee.getName()); + + employee.setName("name B"); + + // (7) здесь происходит коммит изменений в БД, и в ней + // окажется значение "name B" + tx.commit(); + + } finally { + tx.end(); + } +} + В последнем случае исключение в точке (8) возникнет только если сущность является оптимистично блокируемой, т.е. если она реализует интерфейс Versioned. +
+
+
+ Таймаут транзакции + Для создаваемой транзакции может быть указан таймаут в секундах, при превышении которого транзакция будет прервана и откачена. Таймаут транзакции ограничивает максимальную длительность запросов к базе данных. + При программном управлении транзакциями таймаут включается путем передачи объекта TransactionParams в метод Persistence.createTransaction(). Например:Transaction tx = persistence.createTransaction(new TransactionParams().setTimeout(2)); + При декларативном управлении транзакциями используется параметр timeout аннотации @Transactional, например:@Transactional(timeout = 2) +public void someServiceMethod() { +... + Таймаут по умолчанию может быть задан в свойстве приложения + cuba.defaultQueryTimeoutSec + . +
+ Особенности реализации для различных СУБД + PostgreSQL + К сожалению, JDBC драйвер PostgreSQL не поддерживает метод setQueryTimeout() интерфейса java.sql.Statement, поэтому в начале каждой транзакции, для которой определен таймаут (любым способом, включая ненулевое значение свойства + cuba.defaultQueryTimeoutSec + ), выполняется дополнительный оператор в БД: set local statement_timeout to {value}. При этом в случае превышения таймаута запрос будет прерван самим сервером БД. + Для снижения нагрузки от этих дополнительных операторов рекомендуется поступать следующим образом: + + Таймаут по умолчанию устанавливать не на Middleware с помощью свойства cuba.defaultQueryTimeoutSec, в на самом сервере PostgreSQL в файле postgresql.conf, например statement_timeout = 3000 (это в миллисекундах). + + + Для методов, которым требуется большее время таймаута (отчеты и пр.), явно указывать желаемый таймаут в параметрах транзакции. + + + Microsoft SQL Server + Драйвер JTDS поддерживает метод setQueryTimeout() интерфейса java.sql.Statement, поэтому для EntityManager просто устанавливается стандартное свойство javax.persistence.query.timeout, которое соответствующим образом влияет на JDBC запросы. +
+
DataService и DataWorker + Управляемый бин DataWorker является универсальным средством для загрузки графов сущностей из базы данных, и для сохранения изменений, произведенных в Detached экземплярах сущностей. Сервис DataService является фасадом для вызова DataWorker с клиентского уровня приложения. TODO
Запросы с distinct diff --git a/doc/content/manual/ru/manual.xml b/doc/content/manual/ru/manual.xml index 9226f67687..1df1804a3a 100644 --- a/doc/content/manual/ru/manual.xml +++ b/doc/content/manual/ru/manual.xml @@ -245,6 +245,15 @@ Используется во всех стандартных блоках. + + cuba.defaultQueryTimeoutSec + + Задает таймаут транзакции по умолчанию. + Значение по умолчанию: 0, означает что таймаут отсутствует. + Интерфейс: ServerConfig + Используется в блоке Middleware. + + cuba.groovyEvaluatorImport