Стратегии работы с транзакциями: Oбзор моделей транзакций и стратегий их использования

Узнайте о трех моделях транзакций и стратегиях их использования

Многие путают модели транзакций и стратегии использования транзакций. Во второй статье серии Стратегии работы с транзакциями описываются три модели, которые поддерживаются платформой Java, а также четыре основных стратегии, использующие эти модели. Автор статьи Марк Ричардс поясняет, как работают транзакционные модели на примерах кода из спецификаций Spring Framework и Enterprise JavaBeans (EJB) 3.0. Он также рассказывает о том, как данные модели могут являться основой для разработки стратегий использования транзакций, начиная от базовых сценариев обработки до реализации высокоскоростных систем выполнения транзакций.

Марк Ричардс, директор и старший технический архитектор, Collaborative Consulting, LLC

Mark RichardsМарк Ричардс (Mark Richards) – директор и старший технический архитектор в компании Collaborative Consulting, LLC. Он является автором второго издания книги "Java Message Service" (O'Reilly, 2009 г.), а также "Java Transaction Design Strategies" (C4Media Publishing, 2006 г.). Кроме того, он участвовал в написании ряда других книг, в том числе "97 Things Every Software Architect Should Know" (O'Reilly, 2009 г.), "NFJS Anthology Volume 1" (Pragmatic Bookshelf, 2006 г.) и "NFJS Anthology Volume 2" (Pragmatic Bookshelf, 2007 г.). Он обладает сертификатами архитектора и разработчика от компаний IBM, Sun, The Open Group и BEA. Он регулярно выступает с докладами на симпозиумах серии "No Fluff Just Stuff", а также на других конференциях и собраниях пользователей по всему миру.



02.06.2011

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

Об этой серии

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

Платформой Java поддерживаются следующие модели транзакций:

  • локальная модель;
  • программная модель;
  • декларативная модель.

Эти модели задают основные принципы поведения транзакций на платформе Java и то, как они должны реализовываться. При этом модели описывают только правила и семантику обработки транзакций, оставляя вопросы применения модели на усмотрение разработчика. Вам придется самостоятельно решать следующие вопросы: в каких случаях нужно использовать атрибут REQUIRED, а в каких MANDATORY? Когда следует задавать директивы отката транзакций? Как сделать выбор между программной и декларативной моделями транзакций? Как оптимизировать транзакции для высокопроизводительных систем? Модели транзакций сами по себе не дают ответов на эти вопросы. Для этого вам следует либо разработать собственную стратегию использования транзакций, либо принять на вооружение одну из четырех основных стратегий, которые будут рассмотрены ниже.

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

Локальная модель транзакций

Локальная модель транзакций получила свое имя из-за того, что управление всеми транзакциями осуществляется менеджером ресурсов базы данных, а не контейнером или инфраструктурой, в которой выполняется ваше приложение. В этой модели вы сами управляете только соединениями, но не транзакциями. Как говорилось в статье Распространенные ошибки, вы не можете использовать локальную модель при выполнении изменений в базе данных через инфраструктуру объектно-реляционного отображения, например Hibernate, TopLink или Java Persistence API (JPA). Тем не менее вы можете применять эту модель при использовании объектов доступа к данным, инфраструктур JDBC и хранимых процедур.

Локальная транзакционная модель может использоваться двумя способами: либо путем программного управления соединениями, либо оставив это на усмотрение базы данных. Во втором случае необходимо установить свойство autoCommit JDBC-объекта Connection в true (это значение используется по умолчанию). Таким образом вы указываете системе управления базой данных (СУБД), что необходимо подтверждать транзакцию после выполнения каждой операции вставки, изменения или удаления записи, либо откатывать ее в случае ошибки. Этот подход иллюстрируется в листинге 1, в котором приводится фрагмент кода для вставки торгового приказа в таблицу TRADE.

Листинг 1. Пример использования локальных транзакций при выполнении одной операции изменения данных
public class TradingServiceImpl {
   public void processTrade(TradeData trade) throws Exception {
      Connection dbConnection = null;
      try {
         DataSource ds = (DataSource)
              (new InitialContext()).lookup("jdbc/MasterDS");
         dbConnection = ds.getConnection();
         dbConnection.setAutoCommit(true);
	 Statement sql = dbConnection.createStatement();
	 String stmt = "insert into TRADE ...";
	 sql.executeUpdate(stmt1);
      } finally {
         if (dbConnection != null)
            dbConnection.close();
      }
   }
}

Обратите внимание, что в листинге 1 свойство autoCommit устанавливается в значение true. Это является указанием СУБД, что локальная транзакция должна подтверждаться после выполнения каждого оператора БД. Этот подход работает отлично, если выполняется только одна операция в базе данных на каждую логическую единицу работы (LUW). Однако представьте, что метод processTrade(), показанный в листинге 1, также должен обновлять баланс счета в таблице ACCT, чтобы отразить сумму торгового приказа. В этом случае выполняются две независимые друг от друга операции, причем вставка записи в таблицу TRADE будет подтверждена до изменения записи в таблице ACCT. Если вторая операция завершится неудачно, то не будет возможности откатить результат первой, что приведет к рассогласованию данных.

В ответ на подобные ситуации был предложен второй подход – программное управление транзакциями. В этом случае свойство autoCommit объекта Connection должно равняться false, и вам придется самостоятельно подтверждать или откатывать транзакции. Пример приведен в листинге 2.

Листинг 2. Пример использования локальных транзакций при выполнении нескольких операций изменения данных
public class TradingServiceImpl {
   public void processTrade(TradeData trade) throws Exception {
      Connection dbConnection = null;
      try {
         DataSource ds = (DataSource)
             (new InitialContext()).lookup("jdbc/MasterDS");
         dbConnection = ds.getConnection();
         dbConnection.setAutoCommit(false);
	 Statement sql = dbConnection.createStatement();
	 String stmt1 = "insert into TRADE ...";
	 sql.executeUpdate(stmt1);
	 String stmt2 = "update ACCT set balance...";
	 sql.executeUpdate(stmt2);
	 dbConnection.commit();
      } catch (Exception up) {
         dbConnection.rollback();
         throw up;
      } finally {
         if (dbConnection != null)
            dbConnection.close();
      }
   }
}

Обратите внимание, что в листинге 2 свойство autoCommit устанавливается в значение false, указывающее СУБД, что управление соединением будет осуществляться в коде, а не в базе данных. В этом случае необходимо вызывать метод commit() объекта Connection при успешном завершении операции. В случае же выброса исключения следует вызывать метод rollback(). Таким образом можно координировать выполнение двух операций внутри одной логической единицы работы.

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


Программная модель транзакций

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

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

Несмотря на то, что в Spring Framework и EJB 3.0 под программной моделью транзакций подразумевается одно и то же, ее реализация оказывается различной. Мы сначала рассмотрим реализацию модели в EJB 3.0, а затем выполним те же операции обновления базы данных при помощи Spring.

Программные транзакции в EJB 3.0

В EJB 3.0 вы должны запросить транзакцию у менеджера транзакций (другими словами, контейнера) через интерфейс JNDI (Java Naming and Directory Interface) по имени javax.transaction.UserTransaction. Получив ссылку на объект UserTransaction, вы можете вызвать метод begin() для начала новой транзакции, commit() – для подтверждения транзакции и rollback()- для отката транзакции в случае возникновения ошибки. При работе с этой моделью контейнер не будет автоматически подтверждать и откатывать транзакции, поэтому все действия по описанию поведения Java-метода, выполняющего изменения в базе данных, ложатся на плечи разработчика. Пример использования программной модели в EJB 3.0 и JPA приведен в листинге 3.

Листинг 3. Пример работы с программными транзакциями в EJB 3.0
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class TradingServiceImpl implements TradingService {
   @PersistenceContext(unitName="trading") EntityManager em;

   public void processTrade(TradeData trade) throws Exception {
      InitialContext ctx = new InitialContext();
      UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction");
      try {
         txn.begin();
	 em.persist(trade);
	 AcctData acct = em.find(AcctData.class, trade.getAcctId());
	 double tradeValue = trade.getPrice() * trade.getShares();
	 double currentBalance = acct.getBalance();
	 if (trade.getAction().equals("BUY")) {
	    acct.setBalance(currentBalance - tradeValue);
         } else {
            acct.setBalance(currentBalance + tradeValue);
         }
         txn.commit();
      } catch (Exception up) {
         txn.rollback();
         throw up;
      }
   }
}

При использовании программной модели транзакций внутри контейнера Java EE с компонентами сессии, не сохраняющими свое состояние, необходимо указать контейнеру, что используются программные транзакции. Для этого следует использовать аннотацию @TransactionManagement, задав BEAN в качестве типа транзакции. Если данная аннотация не используется, то контейнер будет полагать, что применяется декларативное управление транзакциями (тип CONTAINER, являющийся типом транзакций по умолчанию для EJB 3.0). Задавать тип транзакции не обязательно, если вы работаете на клиентском уровне вне контекста компонента сессии, не сохраняющего состояние.

Программные транзакции в Spring

Spring Framework поддерживает два способа реализации программной модели транзакций. Можно использовать либо класс TransactionTemplate, либо напрямую работать с менеджером транзакций платформы в Spring. Поскольку я не являюсь горячим сторонником анонимных внутренних классов и плохо читаемого кода, то мы рассмотрим только второй подход для иллюстрации программной модели транзакций в Spring.

В Spring присутствует, по крайней мере, девять платформенных менеджеров транзакций. Из них наиболее часто используются DataSourceTransactionManager, HibernateTransactionManager, JpaTransactionManager и JtaTransactionManager. Поскольку в наших примерах используется JPA, то конфигурация будет показана для класса JpaTransactionManager.

Для конфигурирования менеджера JpaTransactionManager в Spring необходимо определить объект класса org.springframework.orm.jpa.JpaTransactionManager в XML-контексте приложения и добавить в него ссылку на фабрику менеджеров сущностей JPA (JPA Entity Manager Factory). Затем в случае, если объект, содержащий логику вашего приложения, также управляется Spring, следует добавить в него ссылку на менеджер транзакций. Пример приведен в листинге 4.

Листинг 4. Описание менеджера транзакций JPA в Spring
<bean id="transactionManager"
         class="org.springframework.orm.jpa.JpaTransactionManager">
   <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="tradingService" class="com.trading.service.TradingServiceImpl">
   <property name="txnManager" ref="transactionManager"/>
</bean>

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

Теперь вы можете использовать платформенный менеджер для получения ссылки на транзакцию. После выполнения всех изменений можно вызвать метод commit() для ее подтверждения либо rollback() – для отката. Пример подобной работы приведен в листинге 5.

Листинг 5. Пример использования менеджера транзакций JPA в Spring
public class TradingServiceImpl  {
   @PersistenceContext(unitName="trading") EntityManager em;

   JpaTransactionManager txnManager = null;
   public void setTxnManager(JpaTransactionManager mgr) {
      txnManager = mgr;
   }

   public void processTrade(TradeData trade) throws Exception {
      TransactionStatus status =
         txnManager.getTransaction(new DefaultTransactionDefinition());
      try {
         em.persist(trade);
	 AcctData acct = em.find(AcctData.class, trade.getAcctId());
	 double tradeValue = trade.getPrice() * trade.getShares();
	 double currentBalance = acct.getBalance();
	 if (trade.getAction().equals("BUY")) {
	    acct.setBalance(currentBalance - tradeValue);
	 } else {
	    acct.setBalance(currentBalance + tradeValue);
	 }
         txnManager.commit(status);
      } catch (Exception up) {
         txnManager.rollback(status);
         throw up;
      }
   }
}

На примере, показанном в листинге 5, видно различие между Spring Framework и EJB 3.0. В Spring ссылка на транзакцию запрашивается путем вызова метода getTransaction() платформенного менеджера транзакций. После этого транзакция выполняется. Подробная информация о транзакции и ее поведении хранится в анонимном классе DefaultTransactionDefinition. Он содержит данные об имени транзакции, уровне изоляции, режиме распространения (он задается атрибутом транзакции) и значении тайм-аута, если таковой задан. В данном примере используются значения по умолчанию, т.е. именем транзакции является пустая строка, уровень изоляции определяется СУБД (как правило, это READ_COMMITTED), режимом распространения является PROPAGATION_REQUIRED, а тайм-аут также зависит от СУБД. Кроме того, обратите внимание, что методы commit() и rollback() вызываются у менеджера транзакций, а не у самой транзакции, как в случае EJB.


Декларативная модель транзакций

Наиболее часто применяемой моделью транзакций на платформе Java является декларативная модель, также известная как модель транзакций, управляемых контейнером (Container Managed Transactions – CMT). При работе с этой моделью контейнер самостоятельно начинает, подтверждает и откатывает транзакции. Задачей разработчика является только описание поведения транзакций. Большинство ошибок, рассмотренных в первой статье серии, связаны с использованием именно декларативной модели.

Для описания поведения транзакций в Spring Framework и EJB 3.0 используются аннотации. В Spring аннотация называется @Transactional, а в EJB 3.0 – @TransactionAttribute. При использовании декларативной модели контейнер не будет автоматически откатывать транзакции при выбросе контролируемых исключений. Разработчику следует указать, когда и в каких именно случаях выброс таких исключений должен приводить к откату транзакции. В Spring Framework это делается при помощи свойства rollbackFor аннотации @Transactional. В EJB для этого служит метод setRollbackOnly() класса SessionContext.

Пример использования декларативной модели транзакций в EJB приведен в листинге 6.

Листинг 6. Пример использования декларативных транзакций в EJB 3.0
@Stateless
public class TradingServiceImpl implements TradingService {
   @PersistenceContext(unitName="trading") EntityManager em;
   @Resource SessionContext ctx;

   @TransactionAttribute(TransactionAttributeType.REQUIRED)
   public void processTrade(TradeData trade) throws Exception {
      try {
         em.persist(trade);
	 AcctData acct = em.find(AcctData.class, trade.getAcctId());
	 double tradeValue = trade.getPrice() * trade.getShares();
	 double currentBalance = acct.getBalance();
	 if (trade.getAction().equals("BUY")) {
	    acct.setBalance(currentBalance - tradeValue);
	 } else {
	    acct.setBalance(currentBalance + tradeValue);
	 }
      } catch (Exception up) {
         ctx.setRollbackOnly();
         throw up;
      }
   }
}

В листинге 7 иллюстрируется работа с декларативной моделью транзакций в Spring Framework.

Листинг 7. Пример использования декларативных транзакций в Spring
public class TradingServiceImpl {
   @PersistenceContext(unitName="trading") EntityManager em;

   @Transactional(propagation=Propagation.REQUIRED,
                  rollbackFor=Exception.class)
   public void processTrade(TradeData trade) throws Exception {
      em.persist(trade);
      AcctData acct = em.find(AcctData.class, trade.getAcctId());
      double tradeValue = trade.getPrice() * trade.getShares();
      double currentBalance = acct.getBalance();
      if (trade.getAction().equals("BUY")) {
         acct.setBalance(currentBalance - tradeValue);
      } else {
         acct.setBalance(currentBalance + tradeValue);
      }
   }
}

Атрибуты транзакций

Кроме директив отката необходимо задать атрибут транзакции, который определяет ее поведение. Платформа Java поддерживает шесть атрибутов транзакций вне зависимости от того, используете вы Spring Framework или EJB 3.0:

  • Required
  • Mandatory
  • RequiresNew
  • Supports
  • NotSupported
  • Never

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

Если к методу methodA() применяется атрибут Required и метод вызывается в области видимости ранее начатой транзакции, то именно она будет использоваться при выполнении метода. В противном случае метод methodA() начнет новую транзакцию. Если метод запустил новую транзакцию, то она должна завершиться (т.е. быть подтверждена или отменена) самим методом. Это наиболее часто используемый атрибут, являющийся атрибутом по умолчанию в EJB 3.0 и Spring. К сожалению, во многих ситуациях он применяется некорректно, что приводит к проблемам с согласованностью и целостностью данных. Использование этого атрибута будет обсуждаться более подробно в следующих статьях серии, посвященных стратегиям работы с транзакциями.

Если к методу methodA() применяется атрибут Mandatory и метод вызывается в области видимости ранее начатой транзакции, то она, как и ранее, будет использоваться при выполнении метода. Однако если метод вызывается вне контекста транзакции, то будет выброшено исключение типа TransactionRequiredException, сигнализирующее о том, что транзакция должна быть начата до вызова метода methodA(). Этот атрибут используется в стратегии клиентского дирижирования, рассматриваемой в следующем разделе статьи.

Атрибут RequiresNew представляет особый интерес. Более чем в половине случаев мне приходится констатировать, что разработчики неверно понимают или используют этот атрибут. Если он применяется к методу methodA(), то новая транзакция начинается (и, соответственно, должна быть закончена в данном методе) всегда, вне зависимости от того, был ли вызван метод в контексте существующей транзакции или нет. Это означает, что если methodA() был вызван в контексте некой транзакции (назовем ее Transaction1), то она будет прервана, и будет начата новая транзакция (Transaction2). При завершении метода methodA() транзакция Transaction2 либо подтверждается, либо откатывается, после чего возобновляется выполнение Transaction1. Такая схема работы очевидным образом нарушает принцип ACID (атомарность, согласованность, изолированность, стойкость), присущий транзакциям (особенно атомарность). Другими словами, операции изменения данных в БД более не содержатся внутри одной единицы работы. Если транзакцию Transaction1 придется откатить, то результаты Transaction2 все равно останутся подтвержденными. Если так, то зачем же нужен этот атрибут? Как объяснялось в первой статье, он должен использоваться только для операций, которые должны производиться вне зависимости от исхода первой транзакции (Transaction1), например, для ведения аудита или журналирования.

Атрибут Supports является еще одним примером режима распространения, который большинство разработчиков либо не до конца понимают, либо не ценят. Если он применяется к методу methodA(), который вызывается в области видимости существующей транзакции, то метод будет выполнен внутри этой транзакции. Если же метод methodA() вызывается вне контекста транзакции, то транзакция не будет начата вовсе. Этот атрибут, как правило, используется для операций чтения данных из базы. Однако почему бы в этом случае не использовать атрибут NotSupported (описываемый в следующем абзаце)? Это будет означать, что метод будет выполняться вне контекста транзакции. Ответ достаточно прост. Если выполнять запрос внутри транзакции, то данные будут читаться из лога транзакций базы данных, т.е. будут видны только что сделанные изменения. Если же запрос выполняется вне транзакции, то ему будут доступны только неизмененные данные. Допустим, что вы добавляете новый торговый приказ в таблицу TRADE и сразу за этим, не заканчивая транзакцию, запрашиваете полный список всех приказов. В этом случае еще не подтвержденный приказ попадет в результаты запроса. Если бы использовался атрибут NotSupported, то в результаты попали бы только записи из таблицы, а не из лога транзакций, поэтому неподтвержденный заказ был бы не виден. Это далеко не всегда является нежелательным эффектом – все зависит от конкретной ситуации и бизнес-логики приложения.

Атрибут NotSupported означает, что метод не должен выполняться внутри транзакции, ни новой, ни уже существующей. Если этот атрибут указан для метода methodA(), вызванного в контексте транзакции, то она будет приостановлена до момента завершения метода. После выхода из метода выполнение транзакции будет возобновлено. Данный атрибут имеет смысл использовать в ограниченном числе случаев, причем, как правило, они связаны с вызовом хранимых процедур. Если хранимая процедура вызывается в контексте существующей транзакции, но при этом содержит строку BEGIN TRANS, или если базой данных является Sybase, работающая в несвязанном (unchained) режиме, то будет сгенерировано исключение, говорящее о том, что новая транзакция не может быть начата. Другими словами, вложенные транзакции не поддерживаются. Практически все контейнеры используют JTS (Java Transaction Service) в качестве реализации транзакций по умолчанию, и именно он (а не сама платформа Java) не поддерживает вложенные транзакции. Если у вас нет возможности изменить код хранимой процедуры, то можно использовать атрибут NotSupported для приостановки текущей транзакции, чтобы избежать исключения. При этом теряется свойство атомарности изменений, поскольку операции с базой данных более не являются частью одной LUW. Таким образом, использование этого атрибута имеет не только хорошие, но плохие стороны, но зато он может помочь вам быстро выбраться из сложной ситуации.

Наибольший интерес, вероятно, представляет атрибут Never. Он ведет себя практически так же, как NotSupported, за одним важным исключением: если метод, отмеченный данным атрибутом, вызывается в контексте транзакции, то выбрасывается исключение, сигнализирующее о том, что в момент вызова этого метода транзакция недопустима. Я смог придумать только одну ситуацию, при которой этот атрибут имеет смысл использовать: тестирование. Он позволяет легко и быстро проверить факт существования транзакции в момент вызова некоторого метода. Вызвав метод с атрибутом Never и получив в ответ исключение, вы можете быть точно уверены, что ранее была начата транзакция. В противном случае транзакции не существовало. Таким образом можно убедиться в надежной работе выбранной стратегии использования транзакций.


Стратегии использования транзакций

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

  • стратегия клиентского дирижирования (client orchestration transaction strategy);
  • стратегия на основе интерфейсного слоя (API layer transaction strategy);
  • стратегия с высокой степенью параллелизма (High Concurrency transaction strategy);
  • высокопроизводительная стратегия (High-Speed Processing transaction strategy).

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

Стратегия клиентского дирижирования применяется в ситуациях, когда несколько клиентских обращений к серверу или модели составляют одну единицу работы. В качестве клиента может выступать инфраструктура Web-приложений, приложение-портал, настольное приложение либо, в некоторых случаях, даже система автоматизации деловых процедур (workflow) или компонент для управления бизнес-процессами (BPM). По сути, именно клиент определяет последовательность действий, необходимых для каждого конкретного запроса. Например, для отправки торгового приказа может быть необходимо сохранить его в базе данных, а затем обновить состояние счета клиента, отразив в нем сумму сделки. Если приложение предоставляет слишком "мелкозернистый" интерфейс, то приходится вызывать оба метода по отдельности. В этом случае транзакция должна определяться на клиентской стороне, чтобы гарантировать атомарность обработки запроса.

Стратегия на основе интерфейсного слоя применяется, когда приложение предоставляет меньшее количество методов, которые выступают в качестве основных точек доступа к серверной функциональности (при желании такие точки можно называть сервисами). При этом клиенты (в том числе клиенты Web-приложений и сервисов, клиенты систем обработки сообщений и даже настольные приложения) выполняют вызов только одного серверного метода для обработки запроса. Если применить эту стратегию к предыдущему примеру с отправкой торгового приказа, то следует создать один метод (назвав его, например, processTrade()), который будет вызываться клиентами. Он должен содержать всю логику сохранения приказа и обновления состояния счета. Своим названием стратегия обязана тому факту, что, как правило, клиентские приложения получают доступ к серверной функциональности через интерфейсы. Данная стратегия является одной из наиболее часто используемых в настоящее время.

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

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


Заключение

Как видите, разработка эффективной стратегии использования транзакций далеко не всегда представляет собой тривиальную задачу. Обеспечение целостности и согласованности данных требует серьезного обдумывания различных вариантов работы, используемых моделей, инфраструктур, конфигураций и методов. За долгие годы работы с приложениями, использующими транзакции, я понял, что хотя общее количество комбинаций различных моделей, методов и вариантов конфигурирования огромно, на практике имеет смысл применять лишь относительно небольшое число из них. Четыре разработанных мною стратегии использования транзакций, которые будут подробно рассматриваться в следующих статьях, должны охватывать большинство случаев, с которыми можно столкнуться при создании бизнес-ориентированных приложений на платформе Java. При этом необходимо оговориться: ни одна из стратегий не является своего рода "серебряной пулей" – простым и единственно верным решением проблемы. В некоторых случаях для реализации той или иной стратегии требуется рефакторинг исходного кода или дизайна приложения. В подобной ситуации вы просто должны спросить себя: "Насколько важно поддерживать целостность и согласованность моих данных?". Как правило, риски и возможные потери, связанные с искажением данных, перевешивают затраты, необходимые на рефакторинг приложения.

Ресурсы

Научиться

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=676758
ArticleTitle=Стратегии работы с транзакциями: Oбзор моделей транзакций и стратегий их использования
publish-date=06022011