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 }