JDBC transaction management trong Spring

Trong bài viết trước, mình đã giới thiệu với các bạn về khái niệm transaction trong database, giúp chúng ta có thể đảm bảo dữ liệu luôn luôn được toàn vẹn và nhất quán. Spring cũng hỗ trợ cho chúng ta quản lý các transaction, giúp chúng ta chỉ tập trung vào phát triển các business logic mà không cần phải lo lắng nhiều về tính toàn vẹn của dữ liệu. Spring hỗ trợ nhiều loại transaction management nhưng trong bài viết này, mình chỉ giới thiệu với các bạn về JDBC transaction management trong Spring các bạn nhé!

Đầu tiên, mình sẽ tạo mới một Maven project để làm ví dụ:

JDBC transaction management trong Spring

Trong project này, mình khai báo Spring framework dependencies như sau: ngoài spring-context cho Spring Core, còn có spring-jdbc cho việc sử dụng JDBC API và spring-tx cho việc quản lý transaction.

với properties “spring-framework.version” là:

Mình sẽ sử dụng JDBC với MySQL database, do đó mình cũng sẽ thêm MySQL driver vào project dependency của chúng ta.

Database thì đã được định nghĩa với cấu trúc như sau:

Ngoài ra chúng ta cần phải nói đến tập tin cấu hình của Spring, spring.xml, nằm trong thư mục /src/main/resources với nội dung ban đầu như sau:

Trong ví dụ này, chúng ta sẽ tạo ra một ứng dụng ngân hàng nhỏ cho phép chúng ta có thể chuyển tiền từ tài khoản A sang tài khoản B,. Quá trình chuyển khoản này sẽ gồm 2 bước: trừ tiền trong tài khoản A và cộng tiền vào tài khoản B. Bất kỳ exception nào xảy ra trong quá trình chuyển tiền thì tất cả các hoạt động sẽ được rollback lại.

OK, bây giờ, mình sẽ đi hiện thực chi tiết từng class trong ví dụ của chúng ta nhé các bạn!

Đầu tiên, chúng ta phải nói đến đó là interface AccountDAO và hiện thực của nó AccountDAOImpl.

Những class này sẽ giúp chúng ta thao tác trực tiếp với database.

Nội dung của những class này như sau:

AccountDAO

Trong interface này, mình đã khai báo 2 methods, dùng để thao tác trực tiếp với database để lấy số tiền hiện tại trong một tài khoản và cập nhập số tiền cho một tài khoản.

AccountDAOImpl:

Class này thì mình đã khai báo nó trong Spring container sử dụng annotation @Repository và sử dụng đối tượng JdbcTemplate để thao tác với database.

Cái thứ hai đó chính là interface AccountService và hiện thực của nó AccountServiceImpl.

AccountService

Interface này thì chỉ có duy nhất một phương thức giúp chúng ta có thể chuyển tiền từ tài khoản này sang tài khoản khác.

AccountServiceImpl

Đối với class này thì mình đã sử dụng @Service annotation để khai báo nó trong Spring container và sử dụng interface AccountDao để thao tác với database.

Cái nữa các bạn để ý là trên phương thức transfer(), mình có khai báo một annotation @Transactional với thuộc tính rollbackFor có giá trị là Exception.class. Annotation này, mục đích là để chỉ rõ method này sẽ được thực thi trong một transaction. Bất kỳ một thao tác nào bị lỗi trong method này, tất cả những thay đổi trước đó trong method này đều sẽ bị rollback.

Tiếp theo, chúng ta sẽ xem xét đến tập tin cấu hình của Spring.

Có một số khai báo mà chúng ta phải làm là:

– Enable auto component scan cho các class mà chúng ta đã khai báo sử dụng annotation @Repository và @Service.

Bạn nào chưa rành về auto component scan thì có thể xem thêm ở bài viết này.

– Cấu hình Datasource để thao tác với database.

– Enable transaction management cho ứng dụng của chúng ta.

Trong khai báo trên, thẻ tx:annotation-driven dùng để khai báo với Spring rằng, chúng ta sẽ sử dụng annotation để quản lý transaction. Thuộc tính transaction-manager dùng để khai báo bean id của class sẽ quản lý transaction. Như các bạn thấy, ở đây chúng ta sử dụng đối tượng DataSourceTransactionManager để quản lý các transaction.

OK, vậy là chúng ta đã khai báo và hiện thực các class cần thiết. Bước cuối cùng, chúng ta sẽ hiện thực ứng dụng này.

Ví dụ bây giờ trong database của mình có 2 tài khoản A và B với các thông tin như sau:

JDBC transaction management trong Spring

Giờ mình sẽ viết code để thực hiện việc chuyển 10$ từ tài khoản A sang tài khoản B. Nội dung của class Application sẽ như sau:

Kết quả:

JDBC transaction management trong Spring

Đây là kết quả trong trường hợp mọi thứ đều OK.

Nếu mọi thứ không OK hết thì sao?

Giả sử bây giờ, sau khi đã trừ tiền trong tài khoản A, mình kiểm tra lại số tiền còn lại trong tài khoản A. Nếu số tiền dưới 20$ thì mình không cho chuyển nữa bằng cách throw một Exception như sau:

thì khi chạy với database như sau:

JDBC transaction management trong Spring

Một exception sẽ xảy ra:

và không có gì thay đổi trong database cả.

3.7/5 - (6 bình chọn)

19 thoughts on “JDBC transaction management trong Spring

  1. a ơi e code bị lỗi như này:

    Exception in thread “main” org.springframework.dao.TransientDataAccessResourceException: StatementCallback; SQL [SELECT amount FROM account WHERE id=1Before start of result set; nested exception is java.sql.SQLException: Before start of result set
    at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:110)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1402)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:388)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:446)
    at com.springjdbctransactionmanagement.dao.impl.AccountDaoImpl.getCurrentAmount(AccountDaoImpl.java:23)
    at com.springjdbctransactionmanagement.service.impl.AccountServiceImpl.transfer(AccountServiceImpl.java:18)
    at com.springjdbctransactionmanagement.service.impl.AccountServiceImpl$$FastClassBySpringCGLIB$$b479d23b.invoke()
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
    at com.springjdbctransactionmanagement.service.impl.AccountServiceImpl$$EnhancerBySpringCGLIB$$113f5fb.transfer()
    at com.springjdbctransactionmanagement.Application.main(Application.java:18)
    Caused by: java.sql.SQLException: Before start of result set

    A giúp e với ạ

  2. Giả sử em chuyển tiền từ A sang B, bên A bị trừ tiền, kiểm tra dưới 20$ thì roll back, tức là nó không lưu vào db. Nhưng giờ em muốn lưu lịch sử bên A đã từng chuyển tiền mà bị lỗi thì làm như nào ạ ?

  3. Trong class AccountServiceImpl có đối tượng AccountDAO dùng annotation @Autowired Nhưng trong spring.xml lại không có tag mà nó vẫn chạy được.
    Em thấy nó chỉ là 1 interface vậy autowired sẽ nối nó với bean AccountDAOImpl có implements AccountDAO. Nhưng lỡ có nhiều hơn 1 class implements AccountDAO thì sao ạ.

  4. Anh cho em hỏi của em bị lỗi này là sao ạ:
    Exception in thread “main” org.springframework.jdbc.UncategorizedSQLException: StatementCallback; uncategorized SQLException for SQL [SELECT amount FROM account WHERE id=1]; SQL state [24000]; error code [0]; No current row in the ResultSet.; nested exception is java.sql.SQLException: No current row in the ResultSet.

  5. Em chào anh. Anh cho em hỏi em nhận được lỗi như thế này khi chạy.
    “Unable to load authentication plugin ‘caching_sha2_password'”
    – Nếu em thêm ?useSSL=true sau value=”jdbc:mysql://localhost:3306/example?useSSL=true” hoặc
    ” ”
    thì nó xuất hiện lỗi này ạ.
    “PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target”
    – Còn nếu em để như bên dưới thì lại sinh lỗi.

    ==> The reference to entity “verifyServerCertificate” must end with the ‘;’ delimiter.
    Em nên cấu hình như thế nào ạ.
    Em cảm ơn anh.

  6. vậy phân biệt cho em giữa transaction của spring và hibernate khác nhau điểm gì không ạ.tại sao em thấy có ví dụ sử dụng cả 2 ở trong impDAO vậy anh

Add Comment