View Javadoc

1   /*
2    * Copyright 2004-2007 Brian McCallister
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.skife.jdbi.v2.tweak.transactions;
18  
19  import org.skife.jdbi.v2.Handle;
20  import org.skife.jdbi.v2.exceptions.TransactionException;
21  import org.skife.jdbi.v2.tweak.TransactionHandler;
22  
23  import java.sql.Connection;
24  import java.sql.SQLException;
25  import java.sql.Savepoint;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  /**
31   * This <code>TransactionHandler</code> uses local JDBC transactions
32   * demarcated explicitely on the handle and passed through to be handled
33   * directly on the JDBC Connection instance.
34   */
35  public class LocalTransactionHandler implements TransactionHandler
36  {
37      private ConcurrentHashMap<Handle, LocalStuff> localStuff = new ConcurrentHashMap<Handle, LocalStuff>();
38  
39      /**
40       * Called when a transaction is started
41       */
42      public void begin(Handle handle)
43      {
44          try {
45              boolean initial = handle.getConnection().getAutoCommit();
46              localStuff.put(handle, new LocalStuff(initial));
47              handle.getConnection().setAutoCommit(false);
48          }
49          catch (SQLException e) {
50              throw new TransactionException("Failed to start transaction", e);
51          }
52      }
53  
54      /**
55       * Called when a transaction is committed
56       */
57      public void commit(Handle handle)
58      {
59          try {
60              handle.getConnection().commit();
61              final LocalStuff stuff = localStuff.remove(handle);
62              if (stuff != null) {
63                  handle.getConnection().setAutoCommit(stuff.getInitialAutocommit());
64                  stuff.getCheckpoints().clear();
65              }
66          }
67          catch (SQLException e) {
68              throw new TransactionException("Failed to commit transaction", e);
69          }
70          finally {
71              // prevent memory leak if commit throws an exception
72              if (localStuff.containsKey(handle)) {
73                  localStuff.remove(handle);
74              }
75          }
76      }
77  
78      /**
79       * Called when a transaction is rolled back
80       */
81      public void rollback(Handle handle)
82      {
83          try {
84              handle.getConnection().rollback();
85              final LocalStuff stuff = localStuff.remove(handle);
86              if (stuff != null) {
87                  handle.getConnection().setAutoCommit(stuff.getInitialAutocommit());
88                  stuff.getCheckpoints().clear();
89              }
90          }
91          catch (SQLException e) {
92              throw new TransactionException("Failed to rollback transaction", e);
93          }
94          finally {
95              // prevent memory leak if rollback throws an exception
96              if (localStuff.containsKey(handle)) {
97                  localStuff.remove(handle);
98              }
99          }
100     }
101 
102     /**
103      * Create a new checkpoint (savepoint in JDBC terminology)
104      *
105      * @param handle the handle on which the transaction is being checkpointed
106      * @param name   The name of the chckpoint, used to rollback to or release late
107      */
108     public void checkpoint(Handle handle, String name)
109     {
110         final Connection conn = handle.getConnection();
111         try {
112             final Savepoint savepoint = conn.setSavepoint(name);
113             localStuff.get(handle).getCheckpoints().put(name, savepoint);
114         }
115         catch (SQLException e) {
116             throw new TransactionException(String.format("Unable to create checkpoint %s", name), e);
117         }
118     }
119 
120     public void release(Handle handle, String name)
121     {
122         final Connection conn = handle.getConnection();
123         try {
124             final Savepoint savepoint = localStuff.get(handle).getCheckpoints().remove(name);
125             if (savepoint == null) {
126                 throw new TransactionException(String.format("Attempt to rollback to non-existant savepoint, '%s'",
127                                                              name));
128             }
129             conn.releaseSavepoint(savepoint);
130         }
131         catch (SQLException e) {
132             throw new TransactionException(String.format("Unable to create checkpoint %s", name), e);
133         }
134     }
135 
136     /**
137      * Roll back to a named checkpoint
138      *
139      * @param handle the handle the rollback is being performed on
140      * @param name   the name of the checkpoint to rollback to
141      */
142     public void rollback(Handle handle, String name)
143     {
144         final Connection conn = handle.getConnection();
145         try {
146             final Savepoint savepoint = localStuff.get(handle).getCheckpoints().remove(name);
147             if (savepoint == null) {
148                 throw new TransactionException(String.format("Attempt to rollback to non-existant savepoint, '%s'",
149                                                              name));
150             }
151             conn.rollback(savepoint);
152         }
153         catch (SQLException e) {
154             throw new TransactionException(String.format("Unable to create checkpoint %s", name), e);
155         }
156     }
157 
158     /**
159      * Called to test if a handle is in a transaction
160      */
161     public boolean isInTransaction(Handle handle)
162     {
163         try {
164             return !handle.getConnection().getAutoCommit();
165         }
166         catch (SQLException e) {
167             throw new TransactionException("Failed to test for transaction status", e);
168         }
169     }
170 
171     private static class LocalStuff
172     {
173         private final Map<String, Savepoint> checkpoints = new HashMap<String, Savepoint>();
174         private final boolean initialAutocommit;
175 
176         public LocalStuff(boolean initial)
177         {
178             this.initialAutocommit = initial;
179         }
180 
181         public Map<String, Savepoint> getCheckpoints()
182         {
183             return checkpoints;
184         }
185 
186         public boolean getInitialAutocommit()
187         {
188             return initialAutocommit;
189         }
190     }
191 }