在本系列的上一篇文章中,我們看到了一個典型的事務處理失敗的案例,其主要原因在於,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類如下:
- 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();
- }
- }
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類:
- 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();
- }
- }
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類如下:
- 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;
- }
- }
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對象的情況下完成和本文相同的事務處理功能。
轉載地址:http://www.davenkin.me/post/2013-02-22/40049367747