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;
18  
19  import java.sql.Connection;
20  import java.sql.DriverManager;
21  import java.sql.SQLException;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.concurrent.ConcurrentHashMap;
25  
26  import javax.sql.DataSource;
27  
28  import org.skife.jdbi.v2.exceptions.CallbackFailedException;
29  import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException;
30  import org.skife.jdbi.v2.logging.NoOpLog;
31  import org.skife.jdbi.v2.tweak.ConnectionFactory;
32  import org.skife.jdbi.v2.tweak.HandleCallback;
33  import org.skife.jdbi.v2.tweak.SQLLog;
34  import org.skife.jdbi.v2.tweak.StatementBuilder;
35  import org.skife.jdbi.v2.tweak.StatementBuilderFactory;
36  import org.skife.jdbi.v2.tweak.StatementLocator;
37  import org.skife.jdbi.v2.tweak.StatementRewriter;
38  import org.skife.jdbi.v2.tweak.TransactionHandler;
39  import org.skife.jdbi.v2.tweak.transactions.LocalTransactionHandler;
40  
41  /**
42   * This class  provides the access point for jDBI. Use it to obtain Handle instances
43   * and provide "global" configuration for all handles obtained from it.
44   */
45  public class DBI implements IDBI
46  {
47      private final ConnectionFactory connectionFactory;
48      private StatementRewriter statementRewriter = new ColonPrefixNamedParamStatementRewriter();
49      private StatementLocator statementLocator = new ClasspathStatementLocator();
50      private TransactionHandler transactionhandler = new LocalTransactionHandler();
51      private StatementBuilderFactory statementBuilderFactory = new DefaultStatementBuilderFactory();
52      private final Map<String, Object> globalStatementAttributes = new ConcurrentHashMap<String, Object>();
53      private SQLLog log = new NoOpLog();
54      private TimingCollector timingCollector = TimingCollector.NOP_TIMING_COLLECTOR;
55  
56      /**
57       * Constructor for use with a DataSource which will provide
58       *
59       * @param dataSource
60       */
61      public DBI(DataSource dataSource)
62      {
63          this(new DataSourceConnectionFactory(dataSource));
64          assert (dataSource != null);
65      }
66  
67      /**
68       * Constructor used to allow for obtaining a Connection in a customized manner.
69       * <p/>
70       * The {@link org.skife.jdbi.v2.tweak.ConnectionFactory#openConnection()} method will
71       * be invoked to obtain a connection instance whenever a Handle is opened.
72       *
73       * @param connectionFactory PrvidesJDBC connections to Handle instances
74       */
75      public DBI(ConnectionFactory connectionFactory)
76      {
77          assert (connectionFactory != null);
78          this.connectionFactory = connectionFactory;
79      }
80  
81      /**
82       * Create a DBI which directly uses the DriverManager
83       *
84       * @param url JDBC URL for connections
85       */
86      public DBI(final String url)
87      {
88          this(new ConnectionFactory()
89          {
90              public Connection openConnection() throws SQLException
91              {
92                  return DriverManager.getConnection(url);
93              }
94          });
95      }
96  
97      /**
98       * Create a DBI which directly uses the DriverManager
99       *
100      * @param url   JDBC URL for connections
101      * @param props Properties to pass to DriverManager.getConnection(url, props) for each new handle
102      */
103     public DBI(final String url, final Properties props)
104     {
105         this(new ConnectionFactory()
106         {
107             public Connection openConnection() throws SQLException
108             {
109                 return DriverManager.getConnection(url, props);
110             }
111         });
112     }
113 
114     /**
115      * Create a DBI which directly uses the DriverManager
116      *
117      * @param url      JDBC URL for connections
118      * @param username User name for connection authentication
119      * @param password Password for connection authentication
120      */
121     public DBI(final String url, final String username, final String password)
122     {
123         this(new ConnectionFactory()
124         {
125             public Connection openConnection() throws SQLException
126             {
127                 return DriverManager.getConnection(url, username, password);
128             }
129         });
130     }
131 
132     /**
133      * Use a non-standard StatementLocator to look up named statements for all
134      * handles created from this DBi instance.
135      *
136      * @param locator StatementLocator which will be used by all Handle instances
137      *                created from this DBI
138      */
139     public void setStatementLocator(StatementLocator locator)
140     {
141         assert (locator != null);
142         this.statementLocator = locator;
143     }
144 
145     /**
146      * Use a non-standard StatementRewriter to transform SQL for all Handle instances
147      * created by this DBI.
148      *
149      * @param rewriter StatementRewriter to use on all Handle instances
150      */
151     public void setStatementRewriter(StatementRewriter rewriter)
152     {
153         assert (rewriter != null);
154         this.statementRewriter = rewriter;
155     }
156 
157     /**
158      * Specify the TransactionHandler instance to use. This allows overriding
159      * transaction semantics, or mapping into different transaction
160      * management systems.
161      * <p/>
162      * The default version uses local transactions on the database Connection
163      * instances obtained.
164      *
165      * @param handler The TransactionHandler to use for all Handle instances obtained
166      *                from this DBI
167      */
168     public void setTransactionHandler(TransactionHandler handler)
169     {
170         assert (handler != null);
171         this.transactionhandler = handler;
172     }
173 
174     /**
175      * Obtain a Handle to the data source wrapped by this DBI instance
176      *
177      * @return an open Handle instance
178      */
179     public Handle open()
180     {
181         try {
182             final long start = System.nanoTime();
183             Connection conn = connectionFactory.openConnection();
184             final long stop = System.nanoTime();
185             StatementBuilder cache = statementBuilderFactory.createStatementBuilder(conn);
186             Handle h = new BasicHandle(transactionhandler,
187                                        statementLocator,
188                                        cache,
189                                        statementRewriter,
190                                        conn,
191                                        globalStatementAttributes,
192                                        log,
193                                        timingCollector);
194             log.logObtainHandle((stop - start) / 1000000L, h);
195             return h;
196         }
197         catch (SQLException e) {
198             throw new UnableToObtainConnectionException(e);
199         }
200     }
201 
202     /**
203      * Define an attribute on every {@link StatementContext} for every statement created
204      * from a handle obtained from this DBI instance.
205      *
206      * @param key   The key for the attribute
207      * @param value the value for the attribute
208      */
209     public void define(String key, Object value)
210     {
211         this.globalStatementAttributes.put(key, value);
212     }
213 
214     /**
215      * A convenience function which manages the lifecycle of a handle and yields it to a callback
216      * for use by clients.
217      *
218      * @param callback A callback which will receive an open Handle
219      *
220      * @return the value returned by callback
221      *
222      * @throws CallbackFailedException Will be thrown if callback raises an exception. This exception will
223      *                                 wrap the exception thrown by the callback.
224      */
225     public <ReturnType> ReturnType withHandle(HandleCallback<ReturnType> callback) throws CallbackFailedException
226     {
227         final Handle h = this.open();
228         try {
229             return callback.withHandle(h);
230         }
231         catch (Exception e) {
232             throw new CallbackFailedException(e);
233         }
234         finally {
235             h.close();
236         }
237     }
238 
239     /**
240      * A convenience function which manages the lifecycle of a handle and yields it to a callback
241      * for use by clients. The handle will be in a transaction when the callback is invoked, and
242      * that transaction will be committed if the callback finishes normally, or rolled back if the
243      * callback raises an exception.
244      *
245      * @param callback A callback which will receive an open Handle, in a transaction
246      *
247      * @return the value returned by callback
248      *
249      * @throws CallbackFailedException Will be thrown if callback raises an exception. This exception will
250      *                                 wrap the exception thrown by the callback.
251      */
252     public <ReturnType> ReturnType inTransaction(final TransactionCallback<ReturnType> callback) throws CallbackFailedException
253     {
254         return withHandle(new HandleCallback<ReturnType>() {
255             public ReturnType withHandle(Handle handle) throws Exception
256             {
257                 return handle.inTransaction(callback);
258             }
259         });
260     }
261 
262     /**
263      * Convenience methd used to obtain a handle from a specific data source
264      *
265      * @param dataSource
266      *
267      * @return Handle using a Connection obtained from the provided DataSource
268      */
269     public static Handle open(DataSource dataSource)
270     {
271         assert (dataSource != null);
272         return new DBI(dataSource).open();
273     }
274 
275     /**
276      * Create a Handle wrapping a particular JDBC Connection
277      *
278      * @param connection
279      *
280      * @return Handle bound to connection
281      */
282     public static Handle open(final Connection connection)
283     {
284         assert (connection != null);
285         return new DBI(new ConnectionFactory()
286         {
287             public Connection openConnection()
288             {
289                 return connection;
290             }
291         }).open();
292     }
293 
294     /**
295      * Obtain a handle with just a JDBC URL
296      *
297      * @param url JDBC Url
298      *
299      * @return newly opened Handle
300      */
301     public static Handle open(final String url)
302     {
303         assert (url != null);
304         return new DBI(url).open();
305     }
306 
307     /**
308      * Obtain a handle with just a JDBC URL
309      *
310      * @param url      JDBC Url
311      * @param username JDBC username for authentication
312      * @param password JDBC password for authentication
313      *
314      * @return newly opened Handle
315      */
316     public static Handle open(final String url, final String username, final String password)
317     {
318         assert (url != null);
319         return new DBI(url, username, password).open();
320     }
321 
322     /**
323      * Obtain a handle with just a JDBC URL
324      *
325      * @param url   JDBC Url
326      * @param props JDBC properties
327      *
328      * @return newly opened Handle
329      */
330     public static Handle open(final String url, final Properties props)
331     {
332         assert (url != null);
333         return new DBI(url, props).open();
334     }
335 
336     /**
337      * Allows customization of how prepared statements are created. When a Handle is created
338      * against this DBI instance the factory will be used to create a StatementBuilder for
339      * that specific handle. When the handle is closed, the StatementBuilder's close method
340      * will be invoked.
341      */
342     public void setStatementBuilderFactory(StatementBuilderFactory factory)
343     {
344         this.statementBuilderFactory = factory;
345     }
346 
347     /**
348      * Specify the class used to log sql statements. Will be passed to all handles created from
349      * this instance
350      */
351     public void setSQLLog(SQLLog log)
352     {
353         this.log = log;
354     }
355 
356     /**
357      * Add a callback to accumulate timing information about the queries running from this
358      * data source.
359      */
360     public void setTimingCollector(final TimingCollector timingCollector) {
361         if (timingCollector == null) {
362             this.timingCollector = TimingCollector.NOP_TIMING_COLLECTOR;
363         }
364         else {
365             this.timingCollector = timingCollector;
366         }
367     }
368 }