在本系列的上一篇文章中,我們看到了一個典型的事務處理失敗的案例,其主要原因在於,service層和各個DAO所使用的Connection是不一樣的,而JDBC中事務處理的作用對象正是Connection對象,所以不同DAO中的操作不在同一個事務里面,從而導致事務失敗。從中我們得出了教訓:要避免這種失敗,我們可以使所有操作共享一個Connection對象,這樣應該就沒有問題了。
請通過以下方式下載本系列文章的github源代碼:
git clone https://github.com/davenkin/java_transaction_workshop.git
在本篇文章中,我們將看到一個成功的,但是丑陋的事務處理方案,它的基本思路是:在service層創建Connection對象,再將該Connection傳給各個DAO類,這樣就完成了Connection共享的目的。
修改兩個DAO類,使他們都接受一個Connection對象,定義UglyBankDao類如下:
package davenkin.step2_ugly; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class UglyBankDao { public void withdraw(int bankId, int amount, Connection connection) throws SQLException { PreparedStatement selectStatement = connection.prepareStatement("SELECT BANK_AMOUNT FROM BANK_ACCOUNT WHERE BANK_ID = ?"); selectStatement.setInt(1, bankId); ResultSet resultSet = selectStatement.executeQuery(); resultSet.next(); int previousAmount = resultSet.getInt(1); resultSet.close(); selectStatement.close(); int newAmount = previousAmount - amount; PreparedStatement updateStatement = connection.prepareStatement("UPDATE BANK_ACCOUNT SET BANK_AMOUNT = ? WHERE BANK_ID = ?"); updateStatement.setInt(1, newAmount); updateStatement.setInt(2, bankId); updateStatement.execute(); updateStatement.close(); } }
使用同樣的方法,定義UglyInsuranceDao類:
package davenkin.step2_ugly; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class UglyInsuranceDao { public void deposit(int insuranceId, int amount, Connection connection) throws SQLException { PreparedStatement selectStatement = connection.prepareStatement("SELECT INSURANCE_AMOUNT FROM INSURANCE_ACCOUNT WHERE INSURANCE_ID = ?"); selectStatement.setInt(1, insuranceId); ResultSet resultSet = selectStatement.executeQuery(); resultSet.next(); int previousAmount = resultSet.getInt(1); resultSet.close(); selectStatement.close(); int newAmount = previousAmount + amount; PreparedStatement updateStatement = connection.prepareStatement("UPDATE INSURANCE_ACCOUNT SET INSURANCE_AMOUNT = ? WHERE INSURANCE_ID = ?"); updateStatement.setInt(1, newAmount); updateStatement.setInt(2, insuranceId); updateStatement.execute(); updateStatement.close(); } }
然后修改Service類,在UglyBankService類的transfer方法中,首先創建一個Connection對象,然后在將該對象依次傳給UglyBankDao的withdraw方法和UglyInsuranceDao類的deposit方法,這樣service層和DAO層使用相同的Connection對象。定義UglyBankService類如下:
package davenkin.step2_ugly; import davenkin.BankService; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class UglyBankService implements BankService { private DataSource dataSource; private UglyBankDao uglyBankDao; private UglyInsuranceDao uglyInsuranceDao; public UglyBankService(DataSource dataSource) { this.dataSource = dataSource; } public void transfer(int fromId, int toId, int amount) { Connection connection = null; try { connection = dataSource.getConnection(); connection.setAutoCommit(false); uglyBankDao.withdraw(fromId, amount, connection); uglyInsuranceDao.deposit(toId, amount, connection); connection.commit(); } catch (Exception e) { try { assert connection != null; connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { assert connection != null; connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public void setUglyBankDao(UglyBankDao uglyBankDao) { this.uglyBankDao = uglyBankDao; } public void setUglyInsuranceDao(UglyInsuranceDao uglyInsuranceDao) { this.uglyInsuranceDao = uglyInsuranceDao; } }
通過上面共享Connection對象的方法雖然可以完成事務處理的目的,但是這樣做法是丑陋的,原因在於:為了完成事務處理的目的,我們需要將一個底層的Connection類在service層和DAO層之間進行傳遞,而DAO層的方法也要接受這個Connection對象,這種做法顯然是不好的,這就是典型的API污染。
在下一篇博文中,我們將講到如何在不傳遞Connection對象的情況下完成和本文相同的事務處理功能。