KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > datasource > LazyConnectionDataSourceProxy


1 /*
2  * Copyright 2002-2006 the original author or authors.
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.springframework.jdbc.datasource;
18
19 import java.lang.reflect.InvocationHandler JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.sql.Connection JavaDoc;
24 import java.sql.SQLException JavaDoc;
25
26 import javax.sql.DataSource JavaDoc;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 /**
32  * Proxy for a target DataSource, fetching actual JDBC Connections lazily,
33  * i.e. not until first creation of a Statement. Connection initialization
34  * properties like auto-commit mode, transaction isolation and read-only mode
35  * will be kept and applied to the actual JDBC Connection as soon as an
36  * actual Connection is fetched (if ever). Consequently, commit and rollback
37  * calls will be ignored if no Statements have been created.
38  *
39  * <p>This DataSource proxy allows to avoid fetching JDBC Connections from
40  * a pool unless actually necessary. JDBC transaction control can happen
41  * without fetching a Connection from the pool or communicating with the
42  * database; this will be done lazily on first creation of a JDBC Statement.
43  *
44  * <p><b>If you configure both a LazyConnectionDataSourceProxy and a
45  * TransactionAwareDataSourceProxy, make sure that the latter is the outermost
46  * DataSource.</b> In such a scenario, data access code will talk to the
47  * transaction-aware DataSource, which will in turn work with the
48  * LazyConnectionDataSourceProxy.
49  *
50  * <p>Lazy fetching of physical JDBC Connections is particularly beneficial
51  * in a generic transaction demarcation environment. It allows you to demarcate
52  * transactions on all methods that could potentially perform data access,
53  * without paying a performance penalty if no actual data access happens.
54  *
55  * <p>This DataSource proxy gives you behavior analogous to JTA and a
56  * transactional JNDI DataSource (as provided by the J2EE server), even
57  * with a local transaction strategy like DataSourceTransactionManager or
58  * HibernateTransactionManager. It does not add value with Spring's
59  * JtaTransactionManager as transaction strategy.
60  *
61  * <p>Lazy fetching of JDBC Connections is also recommended for read-only
62  * operations with Hibernate, in particular if the chances of resolving the
63  * result in the second-level cache are high. This avoids the need to
64  * communicate with the database at all for such read-only operations.
65  * You will get the same effect with non-transactional reads, but lazy fetching
66  * of JDBC Connections allows you to still perform reads in transactions.
67  *
68  * <p><b>NOTE:</b> This DataSource proxy needs to return wrapped Connections to
69  * handle lazy fetching of an actual JDBC Connection. Therefore, the returned
70  * Connections cannot be cast to a native JDBC Connection type like OracleConnection,
71  * or to a connection pool implementation type. Use a corresponding
72  * NativeJdbcExtractor to retrieve the native JDBC Connection.
73  *
74  * @author Juergen Hoeller
75  * @since 1.1.4
76  * @see ConnectionProxy
77  * @see DataSourceTransactionManager
78  * @see org.springframework.orm.hibernate.HibernateTransactionManager
79  * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
80  */

81 public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
82
83     private static final Log logger = LogFactory.getLog(LazyConnectionDataSourceProxy.class);
84
85     private Boolean JavaDoc defaultAutoCommit;
86
87     private Integer JavaDoc defaultTransactionIsolation;
88
89
90     /**
91      * Create a new LazyConnectionDataSourceProxy.
92      * @see #setTargetDataSource
93      */

94     public LazyConnectionDataSourceProxy() {
95     }
96
97     /**
98      * Create a new LazyConnectionDataSourceProxy.
99      * @param targetDataSource the target DataSource
100      */

101     public LazyConnectionDataSourceProxy(DataSource JavaDoc targetDataSource) {
102         setTargetDataSource(targetDataSource);
103         afterPropertiesSet();
104     }
105
106     /**
107      * Set the default auto-commit mode to expose when no target Connection
108      * has been fetched yet (-> actual JDBC Connection default not known yet).
109      * <p>If not specified, the default gets determined by checking a target
110      * Connection on startup. If that check fails, the default will be determined
111      * lazily on first access of a Connection.
112      * @see java.sql.Connection#getAutoCommit
113      */

114     public void setDefaultAutoCommit(boolean defaultAutoCommit) {
115         this.defaultAutoCommit = new Boolean JavaDoc(defaultAutoCommit);
116     }
117
118     /**
119      * Set the default transaction isolation level to expose when no target Connection
120      * has been fetched yet (-> actual JDBC Connection default not known yet).
121      * <p>If not specified, the default gets determined by checking a target
122      * Connection on startup. If that check fails, the default will be determined
123      * lazily on first access of a Connection.
124      * @see java.sql.Connection#getTransactionIsolation
125      */

126     public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
127         this.defaultTransactionIsolation = new Integer JavaDoc(defaultTransactionIsolation);
128     }
129
130     public void afterPropertiesSet() {
131         super.afterPropertiesSet();
132
133         // Determine default auto-commit and transaction isolation
134
// via a Connection from the target DataSource, if possible.
135
if (this.defaultAutoCommit == null || this.defaultTransactionIsolation == null) {
136             try {
137                 Connection JavaDoc con = getTargetDataSource().getConnection();
138                 try {
139                     checkDefaultConnectionProperties(con);
140                 }
141                 finally {
142                     con.close();
143                 }
144             }
145             catch (SQLException JavaDoc ex) {
146                 logger.warn("Could not retrieve default auto-commit and transaction isolation settings", ex);
147             }
148         }
149     }
150
151     /**
152      * Check the default connection properties (auto-commit, transaction isolation),
153      * keeping them to be able to expose them correctly without fetching an actual
154      * JDBC Connection from the target DataSource.
155      * <p>This will be invoked once on startup, but also for each retrieval of a
156      * target Connection. If the check failed on startup (because the database was
157      * down), we'll lazily retrieve those settings.
158      * @param con the Connection to use for checking
159      * @throws SQLException if thrown by Connection methods
160      */

161     protected synchronized void checkDefaultConnectionProperties(Connection JavaDoc con) throws SQLException JavaDoc {
162         if (this.defaultAutoCommit == null) {
163             this.defaultAutoCommit = new Boolean JavaDoc(con.getAutoCommit());
164         }
165         if (this.defaultTransactionIsolation == null) {
166             this.defaultTransactionIsolation = new Integer JavaDoc(con.getTransactionIsolation());
167         }
168     }
169
170     /**
171      * Expose the default auto-commit value.
172      */

173     protected Boolean JavaDoc defaultAutoCommit() {
174         return defaultAutoCommit;
175     }
176
177     /**
178      * Expose the default transaction isolation value.
179      */

180     protected Integer JavaDoc defaultTransactionIsolation() {
181         return defaultTransactionIsolation;
182     }
183
184
185     /**
186      * Return a Connection handle that lazily fetches an actual JDBC Connection
187      * when asked for a Statement (or PreparedStatement or CallableStatement).
188      * <p>The returned Connection handle implements the ConnectionProxy interface,
189      * allowing to retrieve the underlying target Connection.
190      * @return a lazy Connection handle
191      * @see ConnectionProxy#getTargetConnection
192      */

193     public Connection JavaDoc getConnection() throws SQLException JavaDoc {
194         return (Connection JavaDoc) Proxy.newProxyInstance(
195                 ConnectionProxy.class.getClassLoader(),
196                 new Class JavaDoc[] {ConnectionProxy.class},
197                 new LazyConnectionInvocationHandler());
198     }
199
200     /**
201      * Return a Connection handle that lazily fetches an actual JDBC Connection
202      * when asked for a Statement (or PreparedStatement or CallableStatement).
203      * <p>The returned Connection handle implements the ConnectionProxy interface,
204      * allowing to retrieve the underlying target Connection.
205      * @param username the per-Connection username
206      * @param password the per-Connection password
207      * @return a lazy Connection handle
208      * @see ConnectionProxy#getTargetConnection
209      */

210     public Connection JavaDoc getConnection(String JavaDoc username, String JavaDoc password) throws SQLException JavaDoc {
211         return (Connection JavaDoc) Proxy.newProxyInstance(
212                 ConnectionProxy.class.getClassLoader(),
213                 new Class JavaDoc[] {ConnectionProxy.class},
214                 new LazyConnectionInvocationHandler(username, password));
215     }
216
217
218     /**
219      * Invocation handler that defers fetching an actual JDBC Connection
220      * until first creation of a Statement.
221      */

222     private class LazyConnectionInvocationHandler implements InvocationHandler JavaDoc {
223
224         private String JavaDoc username;
225
226         private String JavaDoc password;
227
228         private Boolean JavaDoc readOnly = Boolean.FALSE;
229
230         private Integer JavaDoc transactionIsolation;
231
232         private Boolean JavaDoc autoCommit;
233
234         private boolean closed = false;
235
236         private Connection JavaDoc target;
237
238         public LazyConnectionInvocationHandler() {
239             this.autoCommit = defaultAutoCommit();
240             this.transactionIsolation = defaultTransactionIsolation();
241         }
242
243         public LazyConnectionInvocationHandler(String JavaDoc username, String JavaDoc password) {
244             this();
245             this.username = username;
246             this.password = password;
247         }
248
249         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
250             // Invocation on ConnectionProxy interface coming in...
251

252             if (method.getName().equals("getTargetConnection")) {
253                 // Handle getTargetConnection method: return underlying connection.
254
return getTargetConnection(method);
255             }
256             else if (method.getName().equals("equals")) {
257                 // We must avoid fetching a target Connection for "equals".
258
// Only consider equal when proxies are identical.
259
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
260             }
261             else if (method.getName().equals("hashCode")) {
262                 // We must avoid fetching a target Connection for "hashCode",
263
// and we must return the same hash code even when the target
264
// Connection has been fetched: use hashCode of Connection proxy.
265
return new Integer JavaDoc(hashCode());
266             }
267
268             if (!hasTargetConnection()) {
269                 // No physical target Connection kept yet ->
270
// resolve transaction demarcation methods without fetching
271
// a physical JDBC Connection until absolutely necessary.
272

273                 if (method.getName().equals("toString")) {
274                     return "Lazy Connection proxy for target DataSource [" + getTargetDataSource() + "]";
275                 }
276                 else if (method.getName().equals("isReadOnly")) {
277                     return this.readOnly;
278                 }
279                 else if (method.getName().equals("setReadOnly")) {
280                     this.readOnly = (Boolean JavaDoc) args[0];
281                     return null;
282                 }
283                 else if (method.getName().equals("getTransactionIsolation")) {
284                     if (this.transactionIsolation != null) {
285                         return this.transactionIsolation;
286                     }
287                     // Else fetch actual Connection and check there,
288
// because we didn't have a default specified.
289
}
290                 else if (method.getName().equals("setTransactionIsolation")) {
291                     this.transactionIsolation = (Integer JavaDoc) args[0];
292                     return null;
293                 }
294                 else if (method.getName().equals("getAutoCommit")) {
295                     if (this.autoCommit != null) {
296                         return this.autoCommit;
297                     }
298                     // Else fetch actual Connection and check there,
299
// because we didn't have a default specified.
300
}
301                 else if (method.getName().equals("setAutoCommit")) {
302                     this.autoCommit = (Boolean JavaDoc) args[0];
303                     return null;
304                 }
305                 else if (method.getName().equals("commit")) {
306                     // Ignore: no statements created yet.
307
return null;
308                 }
309                 else if (method.getName().equals("rollback")) {
310                     // Ignore: no statements created yet.
311
return null;
312                 }
313                 else if (method.getName().equals("getWarnings")) {
314                     return null;
315                 }
316                 else if (method.getName().equals("clearWarnings")) {
317                     return null;
318                 }
319                 else if (method.getName().equals("isClosed")) {
320                     return (this.closed ? Boolean.TRUE : Boolean.FALSE);
321                 }
322                 else if (method.getName().equals("close")) {
323                     // Ignore: no target connection yet.
324
this.closed = true;
325                     return null;
326                 }
327                 else if (this.closed) {
328                     // Connection proxy closed, without ever having fetched a
329
// physical JDBC Connection: throw corresponding SQLException.
330
throw new SQLException JavaDoc("Illegal operation: connection is closed");
331                 }
332             }
333
334             // Target Connection already fetched,
335
// or target Connection necessary for current operation ->
336
// invoke method on target connection.
337
try {
338                 return method.invoke(getTargetConnection(method), args);
339             }
340             catch (InvocationTargetException JavaDoc ex) {
341                 throw ex.getTargetException();
342             }
343         }
344
345         /**
346          * Return whether the proxy currently holds a target Connection.
347          */

348         private boolean hasTargetConnection() {
349             return (this.target != null);
350         }
351
352         /**
353          * Return the target Connection, fetching it and initializing it if necessary.
354          */

355         private Connection JavaDoc getTargetConnection(Method JavaDoc operation) throws SQLException JavaDoc {
356             if (this.target == null) {
357                 // No target Connection held -> fetch one.
358
if (logger.isDebugEnabled()) {
359                     logger.debug("Connecting to database for operation '" + operation.getName() + "'");
360                 }
361
362                 // Fetch physical Connection from DataSource.
363
this.target = (this.username != null) ?
364                         getTargetDataSource().getConnection(this.username, this.password) :
365                         getTargetDataSource().getConnection();
366
367                 // If we still lack default connection properties, check them now.
368
checkDefaultConnectionProperties(this.target);
369
370                 // Apply kept transaction settings, if any.
371
if (this.readOnly.booleanValue()) {
372                     this.target.setReadOnly(this.readOnly.booleanValue());
373                 }
374                 if (this.transactionIsolation != null &&
375                         !this.transactionIsolation.equals(defaultTransactionIsolation())) {
376                     this.target.setTransactionIsolation(this.transactionIsolation.intValue());
377                 }
378                 if (this.autoCommit != null && this.autoCommit.booleanValue() != this.target.getAutoCommit()) {
379                     this.target.setAutoCommit(this.autoCommit.booleanValue());
380                 }
381             }
382
383             else {
384                 // Target Connection already held -> return it.
385
if (logger.isDebugEnabled()) {
386                     logger.debug("Using existing database connection for operation '" + operation.getName() + "'");
387                 }
388             }
389
390             return this.target;
391         }
392     }
393
394 }
395
Popular Tags