Các chiến lược giao tác: : Tổng quan về các mô hình và chiến lược

Tìm hiểu về 3 mô hình giao tác và các chiến lược giao tác sử dụng những mô hình này

Một sai lầm thường gặp là nhầm lẫn giữa các mô hình giao tác với các chiến lược giao tác. Bài viết thứ hai này trong loạt bài Các chiến lược giao tác phác họa những nét chính của ba mô hình giao tác, mà nền Java™ hỗ trợ và giới thiệu bốn chiến lược giao tác chính sử dụng các mô hình này. Với một loạt ví dụ dùng Spring Framework và đặc tả Enterprise JavaBeans 3.0 (EJB), Mark Richards sẽ giải thích cách thức làm việc của các mô hình giao tác và cách thức chúng có thể tạo thành nền tảng cho sự phát triển các chiến lược giao tác từ xử lý giao tác cơ bản đến hệ thống xử lý giao tác tốc độ cao.

Mark Richards, Giám đốc và kiến trúc sư kỹ thuật cao cấp, Collaborative Consulting, LLC

Mark Richards là giám đốc và kiến trúc sư kỹ thuật cao cấp ở Collaborative Consulting, LLC. Mark là tác giả của của cuốn Java Message Service (O'Reilly, 2009) và Java Transaction Design Strategies (C4Media Publishing, 2006). Mark có chứng chỉ kiến trúc sư và người phát triển của IBM, Sun, The Open Group, và BEA. Mark cũng là diễn giả thường xuyên ở loạt hội nghị chuyên đề No Fluff Just Stuff và diễn thuyết ở nhiều hội nghị khác và các nhóm người dùng trên khắp thế giới.



04 07 2009

Rất thường xuyên, cả những người phát triển, nhà thiết kế và kiến trúc sư đều nhầm lẫn các mô hình giao tác với các chiến lược giao tác. Tôi hay hỏi kiến trúc sư hay người lãnh đạo kỹ thuật trong cuộc gặp khách hàng là hãy mô tả về chiến lược giao tác của dự án họ đang tiến hành. Tôi thường nhận được một trong ba kiểu trả lời. Đôi lúc là sự im lặng “À, ừm, chúng tôi thật sự không sử dụng giao tác trong các ứng dụng của mình”. Lúc khác thì là sự lúng túng “Ừm, tôi không chắc về điều anh định hỏi”. Tuy nhiên, thường thì tôi nhận được câu trả lời vững tin rằng “Chúng tôi đang sử dụng giao tác dạng khai báo” (declarative transactions). Nhưng như bạn sẽ thấy sau khi đọc bài viết này, thuật ngữ “giao tác khai báo” chính là để mô tả một mô hình giao tác mà không hề có nghĩa là một chiến lược giao tác.

Về loạt bài này

Các giao tác làm tăng chất lượng, tính toàn vẹn và tính nhất quán của dữ liệu của bạn, và khiến cho các trình ứng dụng của bạn vững chãi hơn. Việc triển khai thể hiện thành công các xử lý giao tác trong các ứng dụng Java không phải là một bài tập bình thường, và đây là nói về việc thiết kế cũng quan trọng ngang với nói về viết mã lệnh. Trong loạt bài mới này, Mark Richards sẽ hướng dẫn chúng ta thiết kế một chiến lược giao tác hiệu quả cho một loạt các trường hợp từ các trình ứng dụng đơn giản cho đến xử lý giao tác hiệu năng cao.

Ba mô hình giao tác được hỗ trợ trên nền Java là:

  • Mô hình giao tác cục bộ
  • Mô hình giao tác theo lập trình
  • Mô hình giao tác khai báo

Các mô hình này mô tả những điều căn bản về việc các giao tác này sẽ hành xử như thế nào trên nền Java và chúng được triển khai thực hiện như thế nào. Tuy nhiên, chúng chỉ đưa ra các quy tắc và ngữ nghĩa của xử lý giao tác. Mô hình giao tác được áp dụng ra sao lại tùy thuộc hoàn toàn vào chúng ta. Ví dụ, khi nào thì bạn nên dùng thuộc tính giao tác REQUIRED so với MANDATORY? Khi nào và ở đâu bạn cần định rõ những chỉ thị cuộn lùi giao tác? Khi nào bạn cần xem xét đến mô hình giao tác lập trình so với mô hình giao tác khai báo? Bạn sẽ tối ưu các giao tác ra sao để có được những hệ thống hiệu năng cao? Các mô hình giao tác tự chúng không thể trả lời những câu hỏi này. Đúng hơn, bạn phải giải quyết những vấn đề ấy hoặc bằng việc xây dựng chiến lược giao tác riêng của bạn hoặc bằng việc tuân theo một trong bốn chiến lược giao tác chính mà tôi giới thiệu trong bài viết này.

Như bạn đã thấy ở bài viết đầu tiên trong loạt bài này, nhiều cạm bẫy thường gặp trong giao tác có thể ảnh hưởng đến các hành vi giao tác, và do đó, làm giảm tính toàn vẹn và nhất quán của dữ liệu. Tương tự như vậy, việc thiếu một chiến lược giao tác hiệu quả cũng sẽ có ảnh hưởng tiêu cực đến tính toàn vẹn và nhất quán của dữ liệu. Các mô hình giao tác mà tôi mô tả trong bài viết này là những khối xây dựng nền tảng để phát triển một chiến lược giao tác hiệu quả. Hiểu sự khác nhau của các mô hình này và cách thức chúng hoạt động là tối quan trọng để hiểu về những chiến lược giao tác có sử dụng chúng. Sau khi mô tả ba mô hình giao tác, tôi sẽ giới thiệu bốn chiến lược giao tác áp dụng cho hầu hết các ứng dụng kinh doanh, từ những ứng dụng Web đơn giản đến các hệ thống lớn xử lý giao tác tốc độ cao. Các bài viết tiếp sau trong loạt bài về Các chiến lược giao tác sẽ mô tả các chiến lược này ở mức chi tiết hơn.

Mô hình giao tác cục bộ

Mô hình có tên là Mô hình giao tác cục bộ do xuất phát từ thực tế là các giao tác này bị quản lý bởi trình quản lý tài nguyên cơ sở dữ liệu mức dưới, chứ không phải bởi thùng chứa hay khung công tác mà trình ứng dụng đang chạy. Trong mô hình này, bạn quản lý các kết nối hơn là quản lý các giao tác. Như đã tìm hiểu trong “Hiểu những cạm bẫy trong giao tác”, bạn không thể dùng mô hình giao tác cục bộ khi bạn thực hiện cập nhật cơ sở dữ liệu bằng cách dùng khung công tác ánh xạ quan hệ - đối tượng như Hibernate, opLink hoặc Java Persistence API (JPA). Bạn vẫn còn có thể áp dụng mô hình này khi dùng đối tượng truy nhập dữ liệu (DAO) hoặc khung công tác dựa trên JDBC và các thủ tục cơ sở dữ liệu lưu sẵn.

Bạn có thể sử dụng mô hình giao tác cục bộ theo một trong hai cách sau: để cơ sở dữ liệu quản lý kết nối hoặc quản lý kết nối theo chương trình. Để cơ sở dữ liệu quản lý kết nối, bạn đặt thuộc tính autoCommit của đối tượng JDBC Connection là true (giá trị mặc định), điều này là để báo cho hệ quản trị cơ sở dữ liệu bên dưới (DBMS) giao kết mỗi giao tác sau khi hoàn tất thao tác chèn, cập nhật hay xóa hoặc là cuộn lùi để hủy bỏ công việc nếu thất bại. Kỹ thuật này được minh họa trong Liệt kê 1, thực hiện chèn một lệnh mua bán chứng khoán vào bảng TRADE:

Liệt kê 1. Các giao tác cục bộ với chỉ một thao tác cập nhật
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();
      }
   }
}

Trong Liệt kê 1, lưu ý rằng autoCommit được đặt giá trị true, để báo cho DBMS biêt rằng các giao tác cục bộ cần được giao kết sau mỗi câu lệnh cơ sở dữ liệu. Kỹ thuật này sẽ làm việc tốt nếu bạn chỉ có duy nhất một hành động duy trì cơ sở dữ liệu trong một đơn vị công việc logic (LUW). Tuy nhiên, giả sử phương thức processTrade() như trong Liệt kê 1 cũng cập nhật số dư tài khoản trong bảng ACCT để phản ánh giá trị của lệnh mua bán. Trong trường hợp này, hai hành động của cơ sở dữ liệu sẽ là độc lập với nhau, việc chèn vào bảng TRADE được giao kết với cơ sở dữ liệu trước việc cập nhật vào bảng ACCT. Nếu việc cập nhật vào bảng ACCT thất bại, sẽ không có cơ chế cuộn lùi để hủy bỏ lệnh chèn vào bảng TRADE, kết quả là dữ liệu mất tính nhất quán trong cơ sở dữ liệu.

Kịch bản này đưa tới kỹ thuật thứ hai: quản lý các kết nối theo chương trình. Trong kỹ thuật này, bạn đặt thuộc tính autoCommit của đối tượng Connectionfalse và tự mình thực hiện giao kết hoặc cuộn lùi kết nối một cách thủ công. Liệt kê 2 minh họa kỹ thuật này:

Liệt kê 2. Các giao tác cục bộ với nhiều cập nhật
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();
      }
   }
}

Trong Liệt kê 2, ta để ý thấy thuộc tính autoCommit được đặt là false, để báo cho DBMS nằm dưới biết kết nối sẽ được quản lý trong mã lệnh, chứ không phải là do cơ sở dữ liệu. Trong trường hợp này, bạn phải gọi phương thức commit() của đối tượng Connection nếu tất cả đều ổn; trái lại, gọi phương thức rollback() nếu xảy ra ngoại lệ. Bằng cách này, bạn có thể phối hợp 2 hành động của cơ sở dữ liệu trong cùng một đơn vị công việc.

Mặc dù mô hình giao tác cục bộ ngày nay có vẻ lỗi thời, nó vẫn là một phần tử quan trọng cho một trong những chiến lược giao tác chính mà tôi sẽ đề cập đến ở cuối bài viết này.


Mô hình giao tác theo lập trình

Mô hình giao tác theo lập trình mang tên như vậy bắt nguồn từ thực tế là người phát triển ứng dụng chịu trách nhiệm về việc quản lý giao tác. Trong mô hình giao tác theo lập trình, không giống như mô hình giao tác cục bộ, bạn quản lý các giao tác và chúng độc lập với những kết nối cơ sở dữ liệu nằm dưới.

Giống như ví dụ trong Liệt kê 2, trong mô hình này người phát triển ứng dụng chịu trách nhiệm lấy một giao tác từ trình quản lý giao tác, khởi động giao tác này, giao kết giao tác và, nếu có trường hợp ngoại lệ xảy ra, thì cuộn lùi để hủy giao tác. Như bạn có lẽ cũng đoán được, điều này sẽ dẫn tới các mã lệnh dễ mắc lỗi và chúng có xu hướng lẫn vào logic kinh doanh trong trình ứng dụng của bạn. Tuy nhiên, một số chiến lược giao tác đòi hỏi sử dụng mô hình giao tác theo lập trình.

Mặc dù các khái niệm là tương đương nhau, việc thực thi mô hình giao tác theo lập trình không giống nhau giữa Spring Framework và đặc tả EJB 3.0. Trước tiên, tôi sẽ minh họa cách thực thi mô hình này sử dụng EJB 3.0, sau đó cho thấy cũng các cập nhật cơ sở dữ liệu tương tự dùng Spring Framework.

Các giao tác theo lập trình với EJB 3.0

Trong EJB 3.0, bạn nhận được một giao tác từ trình quản lý giao tác (hay nói cách khác là thùng chứa) bằng cách thực hiện một tra cứu JNDI (Java Naming và Directory Interface) tìm một javax.transaction.UserTransaction. Khi bạn có một UserTransaction, bạn có thể gọi phương thức begin() để khởi động một giao tác, gọi phương thức commit() để giao kết giao tác này, và gọi phương thức rollback() cuộn lùi để hủy bỏ giao tác nếu xảy ra lỗi. Trong mô hình này, thùng chứa sẽ không tự động giao kết hoặc hủy bỏ giao tác; chính là trách nhiệm của người phát triển lập trình các hành vi này trong các phương thức Java thực hiện cập nhật cơ sở dữ liệu. Liệt kê 3 cho ta một ví dụ về mô hình giao tác theo lập trình của EJB 3.0 dùng JPA:

Liệt kê 3. Các giao tác theo lập trình sử dụng 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;
      }
   }
}

Khi sử dụng mô hình giao tác theo lập trình trên nền Java, trong môi trường thùng chứa Enterprise Edition (Java EE) với một bean phiên phi trạng thái (stateless session bean), bạn phải thông báo cho thùng chứa biết bạn đang dùng các giao tác theo lập trình. Bạn thông báo bằng cách dùng chú giải @TransactionManagement và thiết đặt kiểu của giao tác là BEAN. Nếu không sử dụng chú giải này, thùng chứa sẽ giả thiết là bạn đang dùng bộ quản lý (CONTAINER) giao tác khai báo, đây là kiểu giao tác mặc định của EJB 3.0. Khi bạn dùng các giao tác theo lập trình ở tầng ứng dụng khách bên ngoài bối cảnh của một bean phiên phi trạng thái, bạn không cần thiết đặt kiểu giao tác.

Các giao tác theo lập trình với Spring

Khung công tác Spring có hai cách thực thi mô hình giao tác theo lập trình. Cách thứ nhất là thông qua TransactionTemplate của Spring và cách thứ hai là sử dụng trực tiếp trình quản lý giao tác nền Spring. Vì tôi không hào hứng với các lớp bên trong không tên và những mã lệnh khó đọc, tôi sẽ dùng cách thứ hai để minh họa mô hình giao tác theo lập trình trong Spring.

Spring có ít nhất chín trình quản lý giao tác nền. Những cái thường dùng nhất là DataSourceTransactionManager, HibernateTransactionManager, JpaTransactionManager, và JtaTransactionManager. Mã lệnh ví dụ của tôi sử dụng JPA, do đó tôi sẽ đưa ra cấu hình dành cho JpaTransactionManager.

Để cấu hình JpaTransactionManager trong Spring, đơn giản chỉ cần định nghĩa một bean trong tệp XML về bối cảnh trình ứng dụng bằng cách dùng lớp org.springframework.orm.jpa.JpaTransactionManager và bổ sung thêm một tham chiếu đến bean JPA Entity Manager Factory. Sau đó, giả sử rằng Spring quản lý lớp đang chứa logic trình ứng dụng của bạn, nội xạ (tiêm) trình quản lý giao tác vào trong bean như trong Liệt kê 4.

Liệt kê 4. Định nghĩa trình quản lý giao tác Spring JPA
<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>

Nếu Spring không quản lý lớp ứng dụng, trong phương thức của mình bạn có thể nhận được một tham chiếu đến trình quản lý giao tác bằng cách dùng phương thức getBean() trong bối cảnh Spring.

Trong mã nguồn, bây giờ bạn có thể dùng trình quản lý nền để lấy một giao tác. Khi tất cả các cập nhật đã hoàn thành, bạn có thể gọi phương thức commit() để giao kết giao tác, hoặc phương thức rollback() cuộn lùi để hủy bỏ giao tác. Liệt kê 5 minh họa kỹ thuật này:

Liệt kê 5. Sử dụng trình quản lý giao tác Spring JPA
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;
      }
   }
}

Trong Liệt kê 5, chú ý sự khác nhau giữa khung công tác Spring và EJB 3.0. Trong Spring, ta lấy ra một giao tác (cũng là khởi động luôn) bằng cách gọi phương thức getTransaction() trên trình quản lý giao tác nền. Lớp không tên DefaultTransactionDefinition chứa các chi tiết về giao tác và cách hoạt động của nó, bao gồm tên của giao tác, mức phân lập, chế độ lan truyền (thuộc tính giao tác) và thời hạn chờ tối đa của giao tác (nếu có). Trong trường hợp này, tôi chỉ đơn giản là dùng các giá trị mặc định, tên là xâu rỗng, mức phân lập mặc định đối với DBMS nằm dưới (thường là READ_COMMITTED), thuộc tính giao tác là PROPAGATION_REQUIRED, và thời hạn chờ tối đa theo mặc định của DBMS. Ta cũng lưu ý rằng các giao thức commit() rollback() được gọi bằng cách sử dụng trình quản lý giao tác nền chứ không phải là từ giao tác (như trường hợp của EJB).


Mô hình giao tác khai báo

Mô hình giao tác khai báo, hay còn được gọi là Các giao tác do thùng chứa quản lý (Container Managed Transactions- CMT), là mô hình giao tác thường thấy nhất trên nền Java. Trong mô hình này, môi trường thùng chứa đảm nhiệm việc khởi động, giao kết và cuộn lùi để hủy bỏ giao tác. Người phát triển ứng dụng chỉ chịu trách nhiệm xác định cách hoạt động của giao tác. Hầu hết các lỗi vấp phải trong giao tác được thảo luận trong bài thứ nhất của loạt bài này có liên quan đến mô hình giao tác khai báo.

Cả khung công tác Spring lẫn EJB 3.0 đều dùng các chú giải để xác định cách hoạt động của giao tác. Spring dùng chú giải @Transactional, trong khi EJB 3.0 dùng chú giải @TransactionAttribute. Thùng chứa sẽ không tự động cuộn lùi giao tác khi có một ngoại lệ đã kiểm tra nếu bạn sử dụng mô hình giao tác khai báo. Người phát triển ứng dụng phải xác định khi nào và ở đâu thì cuộn lùi hủy bỏ giao tác khi một ngoại lệ đã kiểm tra xảy ra. Trong khung công tác Spring, bạn xác định bẳng cách dùng thuộc tính rollbackFor của chú giải @Transactional. Trong EJB, bạn xác định bằng cách gọi phương thức setRollbackOnly() trên SessionContext.

Liệt kê 6 minh họa cách sử dụng mô hình giao tác khai báo trong EJB:

Liệt kê 6. Các giao tác khai báo sử dụng 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;
      }
   }
}

Liệt kê 7 minh họa cách sử dụng mô hình giao tác khai báo trong khung công tác Spring:

Liệt kê 7. Các giao tác khai báo sử dụng 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);
      }
   }
}

Các thuộc tính giao tác

Ngoài các chỉ thị cuộn lùi, bạn cũng phải xác định các thuộc tính giao tác, chúng định nghĩa cách hành xử của các giao tác. Java hỗ trợ sáu kiểu thuộc tính giao tác, bất kể bạn sử dụng EJB hay khung công tác Spring:

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

Trong khi mô tả từng thuộc tính giao tác này, tôi sẽ sử dụng một phương thức tưởng tượng có tên là methodA(), được áp dụng thuộc tính giao tác đang nói.

Nếu thuộc tính giao tác dành cho phương thức methodA()Required và phương thức methodA() được gọi trong phạm vi một giao tác đang tồn tại, phạm vi này sẽ được sử dụng. Trái lại, phương thức methodA() sẽ khởi động một giao tác mới. Nếu giao tác được khởi động bởi phương thức methodA() thì nó cũng phải bị chấm dứt (giao kết hay cuộn lùi) bởi chính phương thức methodA(). Đây là thuộc tính giao tác thông dụng nhất và được đặt mặc định cho cả EJB 3.0 và Spring. Không may, trong nhiều trường hợp, nó được dùng không chính xác, dẫn tới kết quả là có vấn đề với tính toàn vẹn và nhất quán của dữ liệu. Đối với mỗi chiến lược giao tác mà tôi sẽ trình bày trong loạt bài viết này, tôi sẽ thảo luận việc sử dụng các thuộc tính giao tác này chi tiết hơn.

Nếu thuộc tính giao tác dành cho phương thức methodA()Mandatory và phương thức này được gọi trong phạm vi một giao tác đang tồn tại, phạm vi này sẽ được sử dụng. Tuy nhiên, nếu phương thức methodA() được gọi mà không có bối cảnh giao tác, thì sẽ xuất hiện thông báo lỗi TransactionRequiredException, chỉ báo rằng phải có một giao tác trước khi phương thức methodA() được gọi. Thuộc tính giao tác này được dùng trong chiến lược giao tác phối hợp phía khách (Client Orchestration) sẽ được mô tả ở phần tiếp theo của bài viết này.

Thuộc tính giao tác RequiresNew là một thuộc tính đáng chú ý. Rất thông thường, tôi nhận thấy thuộc tính này bị sử dụng sai hoặc hiểu nhầm. Nếu thuộc tính giao tác dành cho phương thức methodA()RequiresNew và phương thức này được gọi khi có hoặc không có bối cảnh giao tác, thì một giao tác mới sẽ luôn luôn được khởi động (và kết thúc) bởi phương thức methodA(). Điều này có nghĩa là nếu phương thức methodA() được gọi trong bối cảnh của một giao tác khác (ta tạm gọi là Transaction1), thì giao tác Transaction1 sẽ bị tạm dừng và một giao tác mới (tạm gọi là Transaction2) sẽ được khởi động. Khi phương thức methodA() kết thúc, Transaction2 sẽ hoặc là giao kết hoặc là cuộn lùi, và Transaction1 sẽ phục hồi lại. Điều này rõ ràng là vi phạm các tính chất ACID (atomicity, consistency, isolation, durability – không chia cắt được, nhất quán, cô lập, bền vững) của một giao tác (đặc biệt là thuộc tính nguyên tử - không chia cắt được). Nói cách khác, tất cả các thao tác cập nhật cơ sở dữ liệu không còn nằm trong một đơn vị công việc duy nhất nữa. Nếu Transaction1 bị cuộn lùi, những thay đổi bởi Transaction2 đã được giao kết vẫn sẽ còn lại. Nếu có trường hợp đó thì thuộc tính này tốt ở chỗ nào? Như đã nói trong bài đầu tiên của loạt bài này, thuộc tính giao tác này chỉ nên được dùng trong các thao tác cơ sở dữ liệu (như kiểm toán hoặc ghi nhật ký) mà độc lập với giao tác nền nằm dưới (trong trường hợp này là Transaction1).

Thuộc tính giao tác Supports là một dạng nữa mà tôi thấy hầu hết những người phát triển ứng dụng không hiểu đầy đủ và đánh giá đúng. Nếu thuộc tính giao tác Supports được áp dụng cho phương thức methodA() và phương thức này được gọi trong phạm vi một giao tác đang tồn tại, methodA() sẽ chạy dưới phạm vi của giao tác này. Tuy nhiên, nếu phương thức methodA() được gọi không ở trong bối cảnh giao tác thì sẽ không có giao tác nào được khởi động. Thuộc tính này được dùng chủ yếu cho các thao tác chỉ đọc cơ sở dữ liệu. Trong trường hợp đó, tại sao không xác định thuộc tính giao tác là NotSupported (sẽ đề cập đến trong phần tiếp theo) thay vào đó? Như vậy, thuộc tính này đảm bảo phương thức sẽ chạy mà không có giao tác. Câu trả lời rất đơn giản. Việc gọi phép truy vấn trong bối cảnh một giao tác đang tồn tại sẽ dẫn đến việc đọc dữ liệu từ bản ghi nhật ký giao tác cơ sở dữ liệu (hay nói cách khác là dữ liệu đã được cập nhật), trong khi đó việc chạy phép truy vấn mà không ở trong một phạm vi giao tác sẽ dẫn đến câu truy vấn đọc dữ liệu không thay đổi từ bảng. Ví dụ, nếu bạn chèn thêm một lệnh mua bán mới vào bảng TRADE và sau đó (trong cùng một giao tác) lấy ra danh sách tất cả các lệnh mua bán, thì lệnh mua bán chưa được giao kết cũng sẽ có mặt trong danh sách. Tuy nhiên, nếu bạn dùng thuộc tính giao tác là NotSupported thay thế, nó dẫn đến câu truy vấn cơ sở dữ liệu sẽ đọc dữ liệu từ bảng, chứ không phải từ bản ghi nhật ký giao tác. Bởi vậy, cũng trong ví dụ nêu trên, bạn sẽ không thấy xuất hiện lệnh mua bán chưa được giao kết. Đây không nhất thiết là điều tồi tệ, nó phụ thuộc vào từng ca sử dụng và vào logic nghiệp vụ.

Thuộc tính giao tác NotSupported xác định phương thức được gọi sẽ không sử dụng hay khởi động một giao tác, bất chấp đang có hay không có một giao tác. Nếu phương thức methodA() dùng thuộc tính NotSupported và nó được gọi trong bối cảnh một giao tác, thì giao tác này sẽ bị tạm dừng cho đến khi phương thức methodA() kết thúc. Khi phương thức methodA() kết thúc, giao tác ban đầu sẽ được phục hồi. Chỉ có ít trường hợp dùng đến thuộc tính giao tác này và chúng chủ yếu liên quan đến các thủ tục cơ sở dữ liệu đã lưu sẵn (stored procedure). Nếu bạn thử gọi một thủ tục cơ sở dữ liệu đã lưu sẵn trong phạm vi một giao tác đang tồn tại và thủ tục cơ sở dữ liệu lưu sẵn này chứa một lệnh BEGIN TRANS hoặc, trong trường hợp Sybase, chạy ở chế độ không xâu chuỗi, sẽ có báo lỗi chỉ ra rằng không khởi động được giao tác mới nếu đã tồn tại một giao tác khác. (Nói cách khác, không hỗ trợ các giao tác lồng nhau). Hầu hết tất cả các thùng chứa đều dùng dịch vụ giao tác Java (Java Transaction Service - JTS) như là cách triển khai thực hiện giao tác mặc định trên JTA. Chính là JTS –chứ không phải là bản thân nền Java – không hỗ trợ giao tác lồng nhau. Nếu bạn không thể sửa đổi các thủ tục cơ sở dữ liệu lưu sẵn, bạn có thể dùng thuộc tính NotSupported để tạm dừng bối cảnh giao tác đang tồn tại để tránh ngoại lệ tai hại này. Tuy nhiên, hậu quả của nó là bạn không còn có các cập nhật nguyên tử (atomic) vào cơ sở dữ liệu trong cùng một LUW. Đó là sự thỏa hiệp nhưng nó có thể giúp bạn nhanh chóng thoát ra khỏi tình huống khó khăn.

Thuộc tính giao tác Never có lẽ là đáng chú ý nhất. Nó cũng giống như thuộc tính giao tác NotSupported trừ một khác biệt quan trọng: Nếu có một giao tác đang tồn tại khi gọi phương thức sử dụng thuộc tính giao tác Never thì sẽ xuất hiện ngoại lệ chỉ ra rằng không được phép có một giao tác khi bạn gọi phương thức này. Ca sử dụng duy nhất của thuộc tính này mà tôi đã có thể nghĩ đến là dùng để kiểm thử. Nó cho bạn một cách dễ dàng và nhanh chóng khẳng định có tồn tại một giao tác hay không khi bạn gọi một phương thức cụ thể nào đó. Nếu bạn sử dụng thuộc tính giao tác Never và nhận được ngoại lệ khi gọi phương thức đang xét, bạn sẽ biết đang có giao tác hay không. Nếu phương thức đó được phép thực hiện, bạn biết không có giao tác. Đây là một cách thức tuyệt vời để đảm bảo rằng chiến lược giao tác của chúng ta là vững chắc.


Các chiến lược giao tác

Các mô hình giao tác mô tả trong bài viết này tạo nên nền tảng của các chiến lược giao tác mà tôi sắp giới thiệu sau đây. Việc hiểu biết một cách đầy đủ sự khác nhau giữa các mô hình và cách chúng hoạt động là rất quan trọng trước khi bạn đi vào xây dựng một chiến lược giao tác. Các chiến lược giao tác chính có thể sử dụng trong hầu hết các kịch bản ứng dụng kinh doanh là:

  • Chiến lược giao tác phối hợp phía khách
  • Chiến lược giao tác tầng API
  • Chiến lược giao tác tương tranh mức cao
  • Chiến lược giao tác xử lý tốc độ cao

Tôi sẽ tóm tắt từng chiến lược ở đây và sẽ thảo luận chi tiết hơn về chúng trong loạt bài tiếp sau.

Chiến lược giao tác phối hợp phía khách được sử dụng khi nhiều lời gọi dựa trên mô hình hay dựa trên máy chủ từ tầng khách hoàn thành chỉ một đơn vị công việc. Ở góc độ này tầng khách có thể hiểu là những lời gọi từ một khung công tác Web, một ứng dụng cổng web (portal), một máy để bàn hay trong một vài trường hợp, một thành phần quản lý tiến trình nghiệp vụ hay quản lý luồng công việc. Điều cốt yếu là tầng khách sở hữu luồng xử lý và “các bước” cần để hoàn thành một yêu cầu cụ thể. Ví dụ, để đặt một lệnh mua bán, giả sử rằng bạn cần phải chèn giao dịch mua bán này vào cơ sở dữ liệu, sau đó cập nhật số dư tài khoản của khách hàng để phản ánh giá trị giao dịch. Nếu API về tầng của ứng dụng quá mịn (fine-grained), bạn phải gọi cả hai phương thức từ tầng khách. Trong kịch bản đó, đơn vị công việc của giao tác phải nằm ở tầng khách để đảm bảo đơn vị công việc là nguyên tử.

Chiến lược giao tác tầng API được dùng tới khi có những phương thức ở mức thô hơn (coarse-grained) hành động như các điểm vào chính của chức năng nền mặt sau. (có thể gọi chúng là các dịch vụ nếu bạn thích). Trong kịch bản này, các trình khách (có thể là dựa trên nền Web, dựa trên nền dịch vụ Web, hoặc thậm chí là ứng dụng trên máy để bàn) thực hiện một lời gọi đến mặt sau để thực hiện một yêu cầu cụ thể. Sử dụng kịch bản lệnh mua bán chứng khoán trong đoạn trên, trong trường hợp này bạn sẽ có chỉ một phương thức điểm vào (gọi là processTrade() chẳng hạn) mà tầng khách sẽ gọi. Phương thức duy nhất này sẽ phải chứa đựng các phối hợp cần thiết để chèn một lệnh mua bán và cập nhật lại tài khoản. Tôi đã đặt cho chiến lược này cái tên như vậy vì trong hầu hết các trường hợp, chức năng xử lý mặt sau được trưng ra cho các trình ứng dụng phía khách thông qua việc sử dụng các giao diện hoặc một API. Đây là một trong những chiến lược giao tác phổ biến nhất.

Chiến lược giao tác tương tranh mức cao, một biến thể của chiến lược giao tác tầng API, được sử dụng trong các trình ứng dụng không thể hỗ trợ các giao tác chạy quá lâu từ tầng API (thường là vì lý do hiệu năng hay khả năng mở rộng). Đúng như cái tên của nó, chiến lược này thường được dùng chủ yếu trong các ứng dụng hỗ trợ mức tương tranh cao từ phối cảnh người sử dụng. Các giao tác có mức chi phí khá cao trên nền Java. Phụ thuộc vào cơ sở dữ liệu mà bạn đang dùng, chúng có thể gây ra khóa trong cơ sở dữ liệu, cướp phá các tài nguyên, làm chậm trình ứng dụng tính trên quan điểm thông lượng và trong một vài trường hợp thậm chí có thể gây ra khóa chết trong cơ sở dữ liệu. Ý tưởng chính đằng sau chiến lược giao tác này là rút ngắn phạm vi giao tác sao cho bạn tối thiểu việc khóa trong cơ sở dữ liệu, trong khi vẫn duy trì một đơn vị công việc nguyên tử cho bất cứ yêu cầu khách nào đã đưa ra. Trong một vài trường hợp, bạn có thể cần phải tái cấu trúc lại logic của trình ứng dụng để hỗ trợ chiến lược giao tác này.

Chiến lược giao tác xử lý tốc độ cao có lẽ là chiến lược giao tác cực đoan nhất. Bạn sử dụng chiến lược này khi cần có được thời gian xử lý ở mức nhanh nhất có thể được (và do đó thông lượng là cao nhất) của trình ứng dụng và vẫn duy trì tính nguyên tử của giao tác ở mức độ nhất định trong xử lý của bạn. Mặc dù chiến lược này có đôi chút rủi ro theo quan điểm về tính toàn vẹn và nhất quán dữ liệu, nếu triển khai thực hiện chính xác, nó sẽ là chiến lược giao tác nhanh nhất có thể được trên nền Java. Nó cũng là chiến lược giao tác khó nhất và cồng kềnh nhất khi triển khai thực hiện trong số 4 chiến lược đã giới thiệu ở đây.


Kết luận

Như đã trình bày trong phần tổng quan, việc xây dựng một chiến lược giao tác hiệu quả không phải luôn là một nhiệm vụ dễ dàng. Nhiều cân nhắc, tùy chọn, mô hình, khung làm việc, cấu hình và kỹ thuật tham gia vào giải quyết bài toán tính toàn vẹn và nhất quán của dữ liệu. Trong nhiều năm làm việc với các trình ứng dụng và với các giao tác, tôi nhận ra rằng mặc dù tổ hợp của các mô hình, tùy chọn, thiết đặt và cấu hình có thể làm đau đầu và quá sức, trong thực tế chỉ một vài tổ hợp của các tùy chọn và thiết đặt là có ý nghĩa trong hầu hết các trường hợp. Cả bốn chiến lược giao tác mà tôi đã xây dựng và sẽ thảo luận chi tiết trong loạt bài viết sau đây bao trùm hầu hết các kịch bản mà bạn có khả năng bắt gặp trong quá trình phát triển các ứng dụng kinh doanh trên nền Java. Xin có lời nhắc rằng: các chiến lược giao tác này không phải là giải pháp “viên đạn bạc” (silver bullet) đơn giản. Trong một số trường hợp, có thể cần phải tái cấu trúc lại mã nguồn hay thiết kế lại trình ứng dụng để triển khai thực hiện được các chiến lược này. Khi những tình huống đó xảy ra, đơn giản là bạn chỉ cần tự hỏi mình, “Tính toàn vẹn và nhất quán của dữ liệu quan trọng đến mức nào?” Trong hầu hết các trường hợp, công sức phải bỏ ra để tái cấu trúc không ăn thua gì so với những rủi ro và cái giá phải trả liên quan đến dữ liệu xấu.

Tài nguyên

Học tập

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Công nghệ Java
ArticleID=406450
ArticleTitle=Các chiến lược giao tác: : Tổng quan về các mô hình và chiến lược
publish-date=07042009