Bộ sưu tập

Sử dụng Database Transaction trong Spring


https://codersontrang.com/2013/06/02/su%cc%89-du%cc%a3ng-database-transaction-trong-spring/

Transaction (database transaction) nói nôm na là một tổ hợp các thao tác trên database (insert, delete, update…). Một transaction sẽ đảm bảo việc hoặc là tất cả các thao tác trên database được thực hiện thành công, hoặc không một thao tác nào được thực hiện thành công cả. Bài viết này sẽ hướng dẫn cách sử dụng transaction trong Spring. Ví dụ mà chúng ta sử dụng sẽ là ví dụ trong bài viết “Sử dụng Spring MVC và Hibernate để xây dựng ứng dụng web”. Trong bài viết này ta sẽ thêm một phần để người dùng nhập vào một lớp mới và một sinh viên của lớp đó. Như vậy, nhìn qua ta có thể thấy ở đây chúng ta có hai thao tác với database. Thứ nhất là thêm một lớp vào bảng BATCH, thứ hai là thêm một sinh viên vào bảng STUDENT. Ta sẽ đặt hai thao tác này vào một transaction, và chỉ ra rằng khi một lỗi nào đó xảy ra trong quá trình xử lý transaction, mọi thao tác liên quan đến database sẽ không được thực hiện thành công.

Ta thêm vào controller phương thức addBatch(), phương thức này sẽ được gọi khi một yêu cầu thêm một batch mới được gửi tới từ phía client. File DemoController.java sẽ được sửa như dưới đây:

DemoController.java

 package springmvchibernatedemo.controller;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.stereotype.Controller;  
 import org.springframework.web.bind.annotation.RequestMapping;  
 import org.springframework.web.servlet.ModelAndView;  
 import springmvchibernatedemo.business.SchoolManager;  
 import springmvchibernatedemo.entity.Batch;  
 import springmvchibernatedemo.entity.Student;  
 import springmvchibernatedemo.model.AddBatchModel;  
 import springmvchibernatedemo.model.SchoolModel;  

 @Controller  
 public class DemoController {  
      @Autowired  
      private SchoolManager schoolManager;  
      @RequestMapping(value="/viewBatch")  
      public ModelAndView viewBatch(SchoolModel schoolModel){  
           ModelAndView mav = new ModelAndView("viewBatch", "model", schoolModel);  
           schoolManager.getBatchInfo(schoolModel);  
           return mav;  
      }  

      @RequestMapping(value="/addBatch")  
      public ModelAndView addBatch(AddBatchModel addBatchModel){  
           ModelAndView mav = new ModelAndView("addBatch", "model", addBatchModel);  
           Batch batch = addBatchModel.getBatch();  
           if(batch == null){  
                addBatchModel.setBatch(new Batch());  
                addBatchModel.setStudent(new Student());  
           }else{  
                Student student = addBatchModel.getStudent();  
                schoolManager.addBatch(batch, student);  
           }  
           return mav;  
      }
  
      public SchoolManager getSchoolManager() {  
           return schoolManager;  
      }  
      public void setSchoolManager(SchoolManager schoolManager) {  
           this.schoolManager = schoolManager;  
      }  
 }  

Tạo một model mới là AddBatchModel.java. Model này sẽ lưu thông tin về lớp và sinh viên của lớp mà chúng ta nhập từ màn hình.

AddBatchModel.java

 package springmvchibernatedemo.model;  
 import springmvchibernatedemo.entity.Batch;  
 import springmvchibernatedemo.entity.Student;  

 public class AddBatchModel {  
      private Batch batch;  
      private Student student;  

      public Batch getBatch() {  
           return batch;  
      }  
      public void setBatch(Batch batch) {  
           this.batch = batch;  
      }  
      public Student getStudent() {  
           return student;  
      }  
      public void setStudent(Student student) {  
           this.student = student;  
      }  
 }  

Với SchoolManager.java là lớp chứa các phương thức xử lý logic nghiệp vụ. Ta thêm phương thức addBatch(), phương thức này sẽ nhận vào một đối tượng của lớp Batch, và một đối tượng của lớp Student được lấy từ model, sau đó lưu những thông tin hai đối tượng này vào database.

SchoolManager.java

 package springmvchibernatedemo.business;  
 import java.util.List;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import springmvchibernatedemo.dao.BatchDAO;  
 import springmvchibernatedemo.dao.StudentDAO;  
 import springmvchibernatedemo.entity.Batch;  
 import springmvchibernatedemo.entity.Student;  
 import springmvchibernatedemo.model.SchoolModel;  

 public class SchoolManager {  
      @Autowired  
      private BatchDAO batchDAO;  
      @Autowired  
      private StudentDAO studentDAO;  

      public void getBatchInfo(SchoolModel schoolModel) {  
           Batch selectedBatch = schoolModel.getSelectedBatch();  
           List<Batch> batches = batchDAO.getBatchList();  
           if(selectedBatch != null){  
                Integer batchId = selectedBatch.getId();  
                for(Batch batch : batches){  
                     if(batch.getId().equals(batchId)){  
                          selectedBatch = batch;  
                          break;  
                     }  
                }  
           }else{  
                selectedBatch = batches.get(0);  
           }  
           schoolModel.setBatches(batches);  
           schoolModel.setSelectedBatch(selectedBatch);  
      }  
      public BatchDAO getBatchDAO() {  
           return batchDAO;  
      }  
      public void setBatchDAO(BatchDAO batchDAO) {  
           this.batchDAO = batchDAO;  
      }  
      public StudentDAO getStudentDAO() {  
           return studentDAO;  
      }  
      public void setStudentDAO(StudentDAO studentDAO) {  
           this.studentDAO = studentDAO;  
      }  
      public void addBatch(Batch batch, Student student) {  
           batchDAO.addBatch(batch);  
           student.setBatch(batch);   
           studentDAO.addStudent(student);  
      }  
 }  

Bên trong phương thức addBatch() của SchoolManager, ta lần lượt gọi hai phương thức addBatch()addStudent() từ các đối tượng batchDAOstudentDAO để lưu thông tin của lớp học và sinh viên vào database. Hai lớp BatchDAOStudentDAO sẽ giống như dưới đây.

BatchDAO.java

 package springmvchibernatedemo.dao;  
 import java.util.List;  
 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;  
 import springmvchibernatedemo.entity.Batch;  

 public class BatchDAO extends HibernateDaoSupport{  
      @SuppressWarnings("unchecked")  
      public List<Batch> getBatchList() {  
           String query = "select b from Batch b";  
           return getHibernateTemplate().find(query);  
      }  

      public void addBatch(Batch batch) {  
           getHibernateTemplate().save(batch);  
      }  
 }  

StudentDAO.java

 package springmvchibernatedemo.dao;  
 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;  
 import springmvchibernatedemo.entity.Student;  

 public class StudentDAO extends HibernateDaoSupport{  
      public void addStudent(Student student) {  
           getHibernateTemplate().save(student);  
      }  
 }  

Ta cũng không quên khai báo để khởi tạo một đối tượng thuộc lớp StudentDAO

spring-config-dao.xml

 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:context="http://www.springframework.org/schema/context"  
      xmlns:mvc="http://www.springframework.org/schema/mvc"  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation="  
     http://www.springframework.org/schema/mvc  
     http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd  
     http://www.springframework.org/schema/beans  
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
     http://www.springframework.org/schema/context   
     http://www.springframework.org/schema/context/spring-context-3.0.xsd">  

      <bean id="batchDAO" class="springmvchibernatedemo.dao.BatchDAO">  
           <property name="sessionFactory" ref="sessionFactory"/>  
      </bean>  
      <bean id="studentDAO" class="springmvchibernatedemo.dao.StudentDAO">  
           <property name="sessionFactory" ref="sessionFactory"/>  
      </bean>  

 </beans>  

Tạo một view là addBatch.jsp, view này hiển thị giao diện cho phép người dùng có thể nhập vào một lớp học và thông tin của một sinh viên thuộc về lớp học đó.

addBatch.jsp

 <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>  
 <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  

 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
 <html>  
 <head>  
 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">  
 <title>Add Batch</title>  
 </head>  

 <body>  
      <form:form id="mainForm" method="post" commandName="model">  
           <strong>Enter the batch name:</strong>  
           <form:input path="batch.name"/>  
           <br/><br/>  
           <strong>Enter the student who belongs to the batch: </strong><br/>  
           <strong>Student ID</strong>  
           <form:input path="student.id"/><br/><br/>  
           <strong>Student Name</strong>  
           <form:input path="student.name"/><br/><br/>  
           <form:button >Add Batch</form:button>            
      </form:form>  
 </body>  

 </html>  

Khi chạy chương trình, nhập vào thông tin của một lớp học và một sinh viên sau đó nhấn nút Add Batch.

spring_transaction_3

Thông tin của lớp học sẽ được lưu vào bảng BATCH như hình dưới đây:

spring_transaction_1

Thông tin của sinh viên sẽ được lưu vào bảng STUDENT như hình dưới đây:
spring_transaction_2

Bây giờ ta giả sử trong quá trình lưu thông tin của lớp học và sinh viên có một lỗi nào đó xảy ra. Để tạo ra một lỗi như vậy, ta sửa SchoolManager để đặt vào giữa phương thức addBatch() đoạn mã ném ra một RuntimeException như sau:

SchoolManager.java

 package springmvchibernatedemo.business;  
 import java.util.List;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import springmvchibernatedemo.dao.BatchDAO;  
 import springmvchibernatedemo.dao.StudentDAO;  
 import springmvchibernatedemo.entity.Batch;  
 import springmvchibernatedemo.entity.Student;  
 import springmvchibernatedemo.model.SchoolModel;  

 public class SchoolManager {  
      @Autowired  
      private BatchDAO batchDAO;  
      @Autowired  
      private StudentDAO studentDAO;  

      public void getBatchInfo(SchoolModel schoolModel) {  
           Batch selectedBatch = schoolModel.getSelectedBatch();  
           List<Batch> batches = batchDAO.getBatchList();  
           if(selectedBatch != null){  
                Integer batchId = selectedBatch.getId();  
                for(Batch batch : batches){  
                     if(batch.getId().equals(batchId)){  
                          selectedBatch = batch;  
                          break;  
                     }  
                }  
           }else{  
                selectedBatch = batches.get(0);  
           }  
           schoolModel.setBatches(batches);  
           schoolModel.setSelectedBatch(selectedBatch);  
      }  
      public BatchDAO getBatchDAO() {  
           return batchDAO;  
      }  
      public void setBatchDAO(BatchDAO batchDAO) {  
           this.batchDAO = batchDAO;  
      }  
      public StudentDAO getStudentDAO() {  
           return studentDAO;  
      }  
      public void setStudentDAO(StudentDAO studentDAO) {  
           this.studentDAO = studentDAO;  
      }  
      public void addBatch(Batch batch, Student student) {  
           batchDAO.addBatch(batch);  
           student.setBatch(batch);  
           if(batch != null){  
                throw new RuntimeException();  
           }  
           studentDAO.addStudent(student);  
      }  
 }  

Và tiến hành chạy lại chương trình, nhập thông tin về một lớp học và một sinh viên trên màn hình sau đó nhấn Add Batch

spring_transaction_4

Khi chạy đến đoạn mã ném ra RuntimException, quá trình lưu thông tin vào database sẽ bị lỗi và hiển thị trên browser sẽ như sau:

spring_transaction_5

Bây giờ kiểm tra database, ta thấy thông tin của lớp học đã được lưu vào trong bảng BATCH như hình dưới đây:

spring_transaction_6

Tuy nhiên bởi vì RuntimeException bị ném ra trước khi thực hiện lưu thông tin sinh viên vào database, cho nên không có thông tin gì mới được cập nhật vào bảng STUDENT như hình dưới đây:

spring_transaction_7

Rõ ràng như trên, ta đã có một sự không toàn vẹn về mặt dữ liệu khi mà thông tin về lớp học đã được cập nhật vào database nhưng thông tin về sinh viên trong lớp học đó chưa được lưu. Xảy ra sự không toàn vẹn này là do hai thao tác với database là lưu thông tin lớp học và lưu thông tin sinh viên vào database không nằm trong cùng một transaction. Bây giờ để đặt hai thao tác vào cùng một transaction, ta sẽ phải cấu hình một chút như sau:

spring-config-transaction.xml

 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xmlns:aop="http://www.springframework.org/schema/aop"  
      xmlns:tx="http://www.springframework.org/schema/tx"  
      xsi:schemaLocation="  
     http://www.springframework.org/schema/beans  
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
     http://www.springframework.org/schema/tx  
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
     http://www.springframework.org/schema/aop  
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

   <aop:aspectj-autoproxy/>  

   <tx:advice id="txAdvice" transaction-manager="masterTransactionManager">  
     <tx:attributes>  
       <tx:method name="get*" read-only="true"/>  
       <tx:method name="*" propagation="REQUIRED"/>  
     </tx:attributes>  
   </tx:advice>  

   <aop:config>  
     <aop:pointcut id="businessOperations" expression="execution(* *..business.*Manager*.*(..))"/>  
     <aop:advisor advice-ref="txAdvice" pointcut-ref="businessOperations" />  
   </aop:config>  

   <bean id="masterTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
           <property name="dataSource" ref="dataSource" />  
           <property name="sessionFactory" ref="sessionFactory" />            
       </bean>  

 </beans>  

Ta khởi tạo một bean có tên là masterTransactionManager, và truyền thông tin về dataSource và sessionFactory cho bean này. Transaction manager này sẽ làm nhiệm vụ quản lý các transaction có liên quan đến dataSource và sessionFactory được truyền vào. Ta sử dụng thẻ <aop:aspectj-autoproxy/> để khai báo việc cấu hình transaction sẽ được thực hiện bằng cơ chế AOP của Spring. AOP là viết tắt của Aspect Oriented Programming có thể tạm dịch ra tiếng Việt là cơ chế lập trình hướng khía cạnh. Lập trình hướng khía cạnh cho phép một luồng logic có thể được chèn xen kẽ vào một luồng logic khác trong quá trình run time. Như trong ví dụ này, ở các phương thức có liên quan đến việc xử lý logic, ta hoàn toàn không có bất cứ một đoạn code nào liên quan đến việc sử dụng transaction. Tuy nhiên với AOP, các luồng liên quan đến việc khởi tạo và commit transaction sẽ được chèn xen kẽ vào đầu vào cuối mỗi phương thức trong quá trình chạy chương trình. Để thực hiện chèn các luồng xen kẽ vào nhau, AOP cung cấp một cơ chế khai báo trong file .xml.

Quay trở lại ví dụ, thẻ <tx:advice> được sử dụng để chỉ ra cách hành xử cho transaction manager mà chúng ta nói ở trên. Đối với các phương thức bắt đầu bằng “get” thì ở đó sẽ không tồn tại các thao tác liên quan đến cập nhật dữ liệu vào database, chính vì thế mà ta đặt thuộc tính read-only="true". Còn đối với các phương thức khác, ta để ý thấy có thuộc tính propagation="REQUIRED", điều này có nghĩa là nếu trong một phương thức cha có gọi một phương thức con khác trong nó. Nếu một lỗi liên quan đến việc cập nhật database xảy ra trong phương thức con, thì bản thân các thao tác cập nhật dữ liệu ở cả phương thức con và phương thức cha đều bị hủy bỏ và dữ liệu được quay trở lại trạng thái ban đầu (rollback).

Tiếp theo ta sử dụng thẻ lt;aop:config> để chỉ ra vị trí trong ứng dụng mà transaction manager sẽ làm nhiệm vụ quản lý transaction của nó. Việc chỉ ra vị trí được thực hiện bằng cách khhai báo pointcut expression. Trong ví dụ này ta có pointcut expression là execution(* *..business.*Manager*.*(..)). Với việc khai báo point-cut expression như trên, transaction manager sẽ bắt đầu làm nhiệm vụ quản lý transaction mỗi khi có một phương thức trong các lớp có tên chứa chữ “Manager” và nằm trong package có tên kết thúc bằng “business” được thực hiện.

Bây giờ khi chạy chương trình, ta nhập vào thông tin của lớp học và sinh viên của lớp đó.

spring_transaction_8

Khi bấm Add Batch, ta vẫn nhận được màn hình lỗi giống như trên. Tuy nhiên sự khác biệt ở đây là khi ta kiểm tra dữ liệu trong database, dữ liệu không có sự thay đổi. Điều này là bởi vì các thao tác với database giờ đây đã được cùng đặt vào trong một transaction, khi một thao tác thất bại thì đồng nghĩa với việc các thao tác khác cũng không được thực hiện thành công và nó đảm bảo tính toàn vẹn cho dữ liệu.

Không có sự thay đổi về dữ liệu trong bảng BATCH

spring_transaction_9

Không có sự thay đổi về dữ liệu trong bảng STUDENT

spring_transaction_10

Good luck!

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s