Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的

来源:互联网 发布:python绝技 pdf 中文 编辑:程序博客网 时间:2024/05/19 05:05

在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的。在本篇文章中,我们将引入另外一种机制(ConnectionHolder)来完成事务管理。

 

这是一个关于Java事务处理的系列文章,请通过以下方式下载github源代码:

Git clone https://github.com/davenkin/java_transaction_workshop.git

 

ConnectionHolder的工作机制是:我们将Connection对象放在一个全局公用的地方,然后在不同的操作中都从这个地方取得Connection,从而完成Connection共享的目的,这也是一种ServiceLocator模式,有点像JNDI。定义一个ConnectionHolder类如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ConnectionHolder  
  2. {  
  3.     private Map<DataSource, Connection> connectionMap = new HashMap<DataSource, Connection>();  
  4.               
  5.     public Connection getConnection(DataSource dataSource) throws SQLException  
  6.     {  
  7.         Connection connection = connectionMap.get(dataSource);  
  8.         if (connection == null || connection.isClosed())  
  9.         {  
  10.             connection = dataSource.getConnection();  
  11.             connectionMap.put(dataSource, connection);  
  12.         }  
  13.               
  14.         return connection;  
  15.     }  
  16.               
  17.     public void removeConnection(DataSource dataSource)  
  18.     {  
  19.         connectionMap.remove(dataSource);  
  20.     }  
  21. }  

从ConnectionHolder类中可以看出,我们维护了一个键为DataSource、值为Connection的Map,这主要用于使ConnectionHolder可以服务多个DataSource。在调用getConnection方法时传入了一个DataSource对象,如果Map里面已经存在该DataSource对应的Connection,则直接返回该Connection,否则,调用DataSource的getConnection方法获得一个新的Connection,再将其加入到Map中,最后返回该Connection。这样在同一个事务过程中,我们先后从ConnectionHolder中取得的Connection是相同的,除非在中途我们调用了ConnectionHolder的removeConnection方法将当前Connection移除掉或者调用了Connection.close()将Connection关闭,然后在后续的操作中再次调用ConnectionHolder的getConnection方法,此时返回的则是一个新的Connection对象,从而导致事务处理失败,你应该不会做出这种中途移除或关闭Connection的事情。

 

然而,虽然我们不会自己手动地在中途移除或者关闭Conncetion对象(当然,在事务处理末尾我们应该关闭Conncetion),我们却无法阻止其他线程这么做。比如,ConnectionHolder类是可以在多个线程中同时使用的,并且这些线程使用了同一个DataSource,其中一个线程使用完Connection后便将其关闭,而此时另外一个线程正试图使用这个Connection,问题就出来了。因此,上面的ConnectionHolder不是线程安全的。

 

为了获得线程安全的ConnectionHolder类,我们可以引入Java提供的ThreadLocal类,该类保证一个类的实例变量在各个线程中都有一份单独的拷贝,从而不会影响其他线程中的实例变量。定义一个SingleThreadConnectionHolder类如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class SingleThreadConnectionHolder  
  2. {  
  3.     private static ThreadLocal<ConnectionHolder> localConnectionHolder = new ThreadLocal<ConnectionHolder>();  
  4.              
  5.     public static Connection getConnection(DataSource dataSource) throws SQLException  
  6.     {  
  7.         return getConnectionHolder().getConnection(dataSource);  
  8.     }  
  9.              
  10.     public static void removeConnection(DataSource dataSource)  
  11.     {  
  12.         getConnectionHolder().removeConnection(dataSource);  
  13.     }  
  14.              
  15.     private static ConnectionHolder getConnectionHolder()  
  16.     {  
  17.         ConnectionHolder connectionHolder = localConnectionHolder.get();  
  18.         if (connectionHolder == null)  
  19.         {  
  20.             connectionHolder = new ConnectionHolder();  
  21.             localConnectionHolder.set(connectionHolder);  
  22.         }  
  23.         return connectionHolder;  
  24.     }  
  25.              
  26. }  

有了一个线程安全的SingleThreadConnectionHolder类,我们便可以在service层和各个DAO中使用该类来获取Connection对象:

 

Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);

 

 

当然,此时我们需要传入一个DataSource,这个DataSource可以作为DAO类的实例变量存在,所以我们不用像上一篇文章那样将Connection对象直接传给DAO的方法。这里你可能要问,既然可以将DataSource作为实例变量,那么在上一篇文章中,为什么不可以将Connection也作为实例变量呢,这样不就不会造成丑陋的API了吗?原因在于:将Connection对象作为实例变量同样会带来线程安全问题,当多个线程同时使用同一个DAO类时,一个线程关闭了Connection而另一个正在使用,这样的问题和上面讲到的ConnectionHolder的线程安全问题一样。

 

关于Bank DAO和Insurance DAO类的源代码这里就不列出了,他们和上篇文章只是获得Connection对象的方法不一样而已,你可以参考github源代码。

 

接下来,我们再来看看TransactionManager类,在上几篇文章中,我们都是在service类中直接写和事务处理相关的代码,而更好的方式是声明一个TransactionManger类将事务处理相关工作集中管理:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class TransactionManager  
  2. {  
  3.     private DataSource dataSource;  
  4.           
  5.     public TransactionManager(DataSource dataSource)  
  6.     {  
  7.         this.dataSource = dataSource;  
  8.     }  
  9.           
  10.     public final void start() throws SQLException  
  11.     {  
  12.         Connection connection = getConnection();  
  13.         connection.setAutoCommit(false);  
  14.     }  
  15.           
  16.     public final void commit() throws SQLException  
  17.     {  
  18.         Connection connection = getConnection();  
  19.         connection.commit();  
  20.     }  
  21.           
  22.     public final void rollback()  
  23.     {  
  24.         Connection connection = null;  
  25.         try  
  26.         {  
  27.             connection = getConnection();  
  28.             connection.rollback();  
  29.           
  30.         } catch (SQLException e)  
  31.         {  
  32.             throw new RuntimeException("Couldn't rollback on connection[" + connection + "].", e);  
  33.         }  
  34.     }  
  35.           
  36.     public final void close()  
  37.     {  
  38.         Connection connection = null;  
  39.         try  
  40.         {  
  41.             connection = getConnection();  
  42.             connection.setAutoCommit(true);  
  43.             connection.setReadOnly(false);  
  44.             connection.close();  
  45.             SingleThreadConnectionHolder.removeConnection(dataSource);  
  46.         } catch (SQLException e)  
  47.         {  
  48.             throw new RuntimeException("Couldn't close connection[" + connection + "].", e);  
  49.         }  
  50.     }  
  51.           
  52.     private Connection getConnection() throws SQLException  
  53.     {  
  54.         return SingleThreadConnectionHolder.getConnection(dataSource);  
  55.     }  
  56. }  

可以看出,TransactionManager对象也维护了一个DataSource实例变量,并且也是通过SingleThreadConnectionHolder来获取Connection对象的。然后我们在service类中使用该TransactionManager

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ConnectionHolderBankService implements BankService  
  2. {  
  3.     private TransactionManager transactionManager;  
  4.     private ConnectionHolderBankDao connectionHolderBankDao;  
  5.     private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;  
  6.          
  7.     public ConnectionHolderBankService(DataSource dataSource)  
  8.     {  
  9.         transactionManager = new TransactionManager(dataSource);  
  10.         connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);  
  11.         connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);  
  12.          
  13.     }  
  14.          
  15.     public void transfer(int fromId, int toId, int amount)  
  16.     {  
  17.         try  
  18.         {  
  19.             transactionManager.start();  
  20.             connectionHolderBankDao.withdraw(fromId, amount);  
  21.             connectionHolderInsuranceDao.deposit(toId, amount);  
  22.             transactionManager.commit();  
  23.         } catch (Exception e)  
  24.         {  
  25.             transactionManager.rollback();  
  26.         } finally  
  27.         {  
  28.             transactionManager.close();  
  29.         }  
  30.     }  
  31. }  

在ConnectionHolderBankService中,我们使用TransactionManager来管理事务,由于TransactionManger和两个DAO类都是使用SingleThreadConnectionHolder来获取Connection,故他们在整个事务处理过程中使用了相同的Connection对象,事务处理成功。我们也可以看到,在两个DAO的withdraw和deposit方法没有接受和业务无关的对象,消除了API污染;另外,使用TransactionManager来管理事务,使Service层代码也变简洁了。

 

在下一篇文章中,我们将讲到使用Template模式来完成事务处理。

0 0
原创粉丝点击