001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.dbcp.cpdsadapter;
019
020 import java.util.Hashtable;
021 import java.util.Properties;
022 import java.io.PrintWriter;
023 import java.io.Serializable;
024 import java.sql.DriverManager;
025 import java.sql.SQLException;
026 import javax.sql.PooledConnection;
027 import javax.sql.ConnectionPoolDataSource;
028 import javax.naming.Name;
029 import javax.naming.Context;
030 import javax.naming.Referenceable;
031 import javax.naming.spi.ObjectFactory;
032 import javax.naming.Reference;
033 import javax.naming.RefAddr;
034 import javax.naming.StringRefAddr;
035 import javax.naming.NamingException;
036
037 import org.apache.commons.pool.KeyedObjectPool;
038 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
039
040 /**
041 * <p>
042 * An adapter for jdbc drivers that do not include an implementation
043 * of {@link javax.sql.ConnectionPoolDataSource}, but still include a
044 * {@link java.sql.DriverManager} implementation.
045 * <code>ConnectionPoolDataSource</code>s are not used within general
046 * applications. They are used by <code>DataSource</code> implementations
047 * that pool <code>Connection</code>s, such as
048 * {@link org.apache.commons.dbcp.datasources.SharedPoolDataSource}. A J2EE
049 * container will normally provide some method of initializing the
050 * <code>ConnectionPoolDataSource</code> whose attributes are presented
051 * as bean getters/setters and then deploying it via JNDI. It is then
052 * available as a source of physical connections to the database, when
053 * the pooling <code>DataSource</code> needs to create a new
054 * physical connection.
055 * </p>
056 *
057 * <p>
058 * Although normally used within a JNDI environment, the DriverAdapterCPDS
059 * can be instantiated and initialized as any bean and then attached
060 * directly to a pooling <code>DataSource</code>.
061 * <code>Jdbc2PoolDataSource</code> can use the
062 * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
063 * </p>
064 *
065 * <p>
066 * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
067 * which is not generally available in jbdc2
068 * <code>ConnectionPoolDataSource</code> implementation, but is
069 * addressed within the jdbc3 specification. The <code>PreparedStatement</code>
070 * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
071 * it has not undergone extensive testing in the configuration used here.
072 * It should be considered experimental and can be toggled with the
073 * poolPreparedStatements attribute.
074 * </p>
075 *
076 * <p>
077 * The <a href="package-summary.html">package documentation</a> contains an
078 * example using catalina and JNDI. The <a
079 * href="../datasources/package-summary.html">datasources package documentation</a>
080 * shows how to use <code>DriverAdapterCPDS</code> as a source for
081 * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
082 * </p>
083 *
084 * @author John D. McNally
085 * @version $Revision: 896266 $ $Date: 2010-01-05 18:20:12 -0500 (Tue, 05 Jan 2010) $
086 */
087 public class DriverAdapterCPDS
088 implements ConnectionPoolDataSource, Referenceable, Serializable,
089 ObjectFactory {
090
091 private static final long serialVersionUID = -4820523787212147844L;
092
093
094 private static final String GET_CONNECTION_CALLED
095 = "A PooledConnection was already requested from this source, "
096 + "further initialization is not allowed.";
097
098 /** Description */
099 private String description;
100 /** Password */
101 private String password;
102 /** Url name */
103 private String url;
104 /** User name */
105 private String user;
106 /** Driver class name */
107 private String driver;
108
109 /** Login TimeOut in seconds */
110 private int loginTimeout;
111 /** Log stream. NOT USED */
112 private transient PrintWriter logWriter = null;
113
114 // PreparedStatement pool properties
115 private boolean poolPreparedStatements;
116 private int maxActive = 10;
117 private int maxIdle = 10;
118 private int _timeBetweenEvictionRunsMillis = -1;
119 private int _numTestsPerEvictionRun = -1;
120 private int _minEvictableIdleTimeMillis = -1;
121 private int _maxPreparedStatements = -1;
122
123 /** Whether or not getConnection has been called */
124 private volatile boolean getConnectionCalled = false;
125
126 /** Connection properties passed to JDBC Driver */
127 private Properties connectionProperties = null;
128
129 static {
130 // Attempt to prevent deadlocks - see DBCP - 272
131 DriverManager.getDrivers();
132 }
133
134 /**
135 * Controls access to the underlying connection
136 */
137 private boolean accessToUnderlyingConnectionAllowed = false;
138
139 /**
140 * Default no-arg constructor for Serialization
141 */
142 public DriverAdapterCPDS() {
143 }
144
145 /**
146 * Attempt to establish a database connection using the default
147 * user and password.
148 */
149 public PooledConnection getPooledConnection() throws SQLException {
150 return getPooledConnection(getUser(), getPassword());
151 }
152
153 /**
154 * Attempt to establish a database connection.
155 * @param username name to be used for the connection
156 * @param pass password to be used fur the connection
157 */
158 public PooledConnection getPooledConnection(String username,
159 String pass)
160 throws SQLException {
161 getConnectionCalled = true;
162 /*
163 public GenericKeyedObjectPool(KeyedPoolableObjectFactory factory,
164 int maxActive, byte whenExhaustedAction, long maxWait,
165 int maxIdle, boolean testOnBorrow, boolean testOnReturn,
166 long timeBetweenEvictionRunsMillis,
167 int numTestsPerEvictionRun, long minEvictableIdleTimeMillis,
168 boolean testWhileIdle) {
169 */
170 KeyedObjectPool stmtPool = null;
171 if (isPoolPreparedStatements()) {
172 if (getMaxPreparedStatements() <= 0)
173 {
174 // since there is no limit, create a prepared statement pool with an eviction thread
175 // evictor settings are the same as the connection pool settings.
176 stmtPool = new GenericKeyedObjectPool(null,
177 getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
178 getMaxIdle(), false, false,
179 getTimeBetweenEvictionRunsMillis(),getNumTestsPerEvictionRun(),getMinEvictableIdleTimeMillis(),
180 false);
181 }
182 else
183 {
184 // since there is limit, create a prepared statement pool without an eviction thread
185 // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
186 // see org.apache.commons.pool.impl.GenericKeyedObjectPool.clearOldest method
187 stmtPool = new GenericKeyedObjectPool(null,
188 getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
189 getMaxIdle(), getMaxPreparedStatements(), false, false,
190 -1,0,0, // -1 tells the pool that there should be no eviction thread.
191 false);
192 }
193 }
194 // Workaround for buggy WebLogic 5.1 classloader - ignore the
195 // exception upon first invocation.
196 try {
197 PooledConnectionImpl pci = null;
198 if (connectionProperties != null) {
199 connectionProperties.put("user", username);
200 connectionProperties.put("password", pass);
201 pci = new PooledConnectionImpl(
202 DriverManager.getConnection(getUrl(), connectionProperties),
203 stmtPool);
204 } else {
205 pci = new PooledConnectionImpl(
206 DriverManager.getConnection(getUrl(), username, pass),
207 stmtPool);
208 }
209 pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
210 return pci;
211 }
212 catch (ClassCircularityError e)
213 {
214 PooledConnectionImpl pci = null;
215 if (connectionProperties != null) {
216 pci = new PooledConnectionImpl(
217 DriverManager.getConnection(getUrl(), connectionProperties),
218 stmtPool);
219 } else {
220 pci = new PooledConnectionImpl(
221 DriverManager.getConnection(getUrl(), username, pass),
222 stmtPool);
223 }
224 pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
225 return pci;
226 }
227 }
228
229 // ----------------------------------------------------------------------
230 // Referenceable implementation
231
232 /**
233 * <CODE>Referenceable</CODE> implementation.
234 */
235 public Reference getReference() throws NamingException {
236 // this class implements its own factory
237 String factory = getClass().getName();
238
239 Reference ref = new Reference(getClass().getName(), factory, null);
240
241 ref.add(new StringRefAddr("description", getDescription()));
242 ref.add(new StringRefAddr("driver", getDriver()));
243 ref.add(new StringRefAddr("loginTimeout",
244 String.valueOf(getLoginTimeout())));
245 ref.add(new StringRefAddr("password", getPassword()));
246 ref.add(new StringRefAddr("user", getUser()));
247 ref.add(new StringRefAddr("url", getUrl()));
248
249 ref.add(new StringRefAddr("poolPreparedStatements",
250 String.valueOf(isPoolPreparedStatements())));
251 ref.add(new StringRefAddr("maxActive",
252 String.valueOf(getMaxActive())));
253 ref.add(new StringRefAddr("maxIdle",
254 String.valueOf(getMaxIdle())));
255 ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis",
256 String.valueOf(getTimeBetweenEvictionRunsMillis())));
257 ref.add(new StringRefAddr("numTestsPerEvictionRun",
258 String.valueOf(getNumTestsPerEvictionRun())));
259 ref.add(new StringRefAddr("minEvictableIdleTimeMillis",
260 String.valueOf(getMinEvictableIdleTimeMillis())));
261 ref.add(new StringRefAddr("maxPreparedStatements",
262 String.valueOf(getMaxPreparedStatements())));
263
264 return ref;
265 }
266
267
268 // ----------------------------------------------------------------------
269 // ObjectFactory implementation
270
271 /**
272 * implements ObjectFactory to create an instance of this class
273 */
274 public Object getObjectInstance(Object refObj, Name name,
275 Context context, Hashtable env)
276 throws Exception {
277 // The spec says to return null if we can't create an instance
278 // of the reference
279 DriverAdapterCPDS cpds = null;
280 if (refObj instanceof Reference) {
281 Reference ref = (Reference)refObj;
282 if (ref.getClassName().equals(getClass().getName())) {
283 RefAddr ra = ref.get("description");
284 if (ra != null && ra.getContent() != null) {
285 setDescription(ra.getContent().toString());
286 }
287
288 ra = ref.get("driver");
289 if (ra != null && ra.getContent() != null) {
290 setDriver(ra.getContent().toString());
291 }
292 ra = ref.get("url");
293 if (ra != null && ra.getContent() != null) {
294 setUrl(ra.getContent().toString());
295 }
296 ra = ref.get("user");
297 if (ra != null && ra.getContent() != null) {
298 setUser(ra.getContent().toString());
299 }
300 ra = ref.get("password");
301 if (ra != null && ra.getContent() != null) {
302 setPassword(ra.getContent().toString());
303 }
304
305 ra = ref.get("poolPreparedStatements");
306 if (ra != null && ra.getContent() != null) {
307 setPoolPreparedStatements(Boolean.valueOf(
308 ra.getContent().toString()).booleanValue());
309 }
310 ra = ref.get("maxActive");
311 if (ra != null && ra.getContent() != null) {
312 setMaxActive(Integer.parseInt(ra.getContent().toString()));
313 }
314
315 ra = ref.get("maxIdle");
316 if (ra != null && ra.getContent() != null) {
317 setMaxIdle(Integer.parseInt(ra.getContent().toString()));
318 }
319
320 ra = ref.get("timeBetweenEvictionRunsMillis");
321 if (ra != null && ra.getContent() != null) {
322 setTimeBetweenEvictionRunsMillis(
323 Integer.parseInt(ra.getContent().toString()));
324 }
325
326 ra = ref.get("numTestsPerEvictionRun");
327 if (ra != null && ra.getContent() != null) {
328 setNumTestsPerEvictionRun(
329 Integer.parseInt(ra.getContent().toString()));
330 }
331
332 ra = ref.get("minEvictableIdleTimeMillis");
333 if (ra != null && ra.getContent() != null) {
334 setMinEvictableIdleTimeMillis(
335 Integer.parseInt(ra.getContent().toString()));
336 }
337 ra = ref.get("maxPreparedStatements");
338 if (ra != null && ra.getContent() != null) {
339 setMaxPreparedStatements(
340 Integer.parseInt(ra.getContent().toString()));
341 }
342
343 cpds = this;
344 }
345 }
346 return cpds;
347 }
348
349 /**
350 * Throws an IllegalStateException, if a PooledConnection has already
351 * been requested.
352 */
353 private void assertInitializationAllowed() throws IllegalStateException {
354 if (getConnectionCalled) {
355 throw new IllegalStateException(GET_CONNECTION_CALLED);
356 }
357 }
358
359 // ----------------------------------------------------------------------
360 // Properties
361
362 /**
363 * Get the connection properties passed to the JDBC driver.
364 *
365 * @return the JDBC connection properties used when creating connections.
366 * @since 1.3
367 */
368 public Properties getConnectionProperties() {
369 return connectionProperties;
370 }
371
372 /**
373 * <p>Set the connection properties passed to the JDBC driver.</p>
374 *
375 * <p>If <code>props</code> contains "user" and/or "password"
376 * properties, the corresponding instance properties are set. If these
377 * properties are not present, they are filled in using
378 * {@link #getUser()}, {@link #getPassword()} when {@link #getPooledConnection()}
379 * is called, or using the actual parameters to the method call when
380 * {@link #getPooledConnection(String, String)} is called. Calls to
381 * {@link #setUser(String)} or {@link #setPassword(String)} overwrite the values
382 * of these properties if <code>connectionProperties</code> is not null.</p>
383 *
384 * @param props Connection properties to use when creating new connections.
385 * @since 1.3
386 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
387 */
388 public void setConnectionProperties(Properties props) {
389 assertInitializationAllowed();
390 connectionProperties = props;
391 if (connectionProperties.containsKey("user")) {
392 setUser(connectionProperties.getProperty("user"));
393 }
394 if (connectionProperties.containsKey("password")) {
395 setPassword(connectionProperties.getProperty("password"));
396 }
397 }
398
399 /**
400 * Get the value of description. This property is here for use by
401 * the code which will deploy this datasource. It is not used
402 * internally.
403 *
404 * @return value of description, may be null.
405 * @see #setDescription(String)
406 */
407 public String getDescription() {
408 return description;
409 }
410
411 /**
412 * Set the value of description. This property is here for use by
413 * the code which will deploy this datasource. It is not used
414 * internally.
415 *
416 * @param v Value to assign to description.
417 */
418 public void setDescription(String v) {
419 this.description = v;
420 }
421
422 /**
423 * Get the value of password for the default user.
424 * @return value of password.
425 */
426 public String getPassword() {
427 return password;
428 }
429
430 /**
431 * Set the value of password for the default user.
432 * @param v Value to assign to password.
433 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
434 */
435 public void setPassword(String v) {
436 assertInitializationAllowed();
437 this.password = v;
438 if (connectionProperties != null) {
439 connectionProperties.setProperty("password", v);
440 }
441 }
442
443 /**
444 * Get the value of url used to locate the database for this datasource.
445 * @return value of url.
446 */
447 public String getUrl() {
448 return url;
449 }
450
451 /**
452 * Set the value of url used to locate the database for this datasource.
453 * @param v Value to assign to url.
454 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
455 */
456 public void setUrl(String v) {
457 assertInitializationAllowed();
458 this.url = v;
459 }
460
461 /**
462 * Get the value of default user (login or username).
463 * @return value of user.
464 */
465 public String getUser() {
466 return user;
467 }
468
469 /**
470 * Set the value of default user (login or username).
471 * @param v Value to assign to user.
472 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
473 */
474 public void setUser(String v) {
475 assertInitializationAllowed();
476 this.user = v;
477 if (connectionProperties != null) {
478 connectionProperties.setProperty("user", v);
479 }
480 }
481
482 /**
483 * Get the driver classname.
484 * @return value of driver.
485 */
486 public String getDriver() {
487 return driver;
488 }
489
490 /**
491 * Set the driver classname. Setting the driver classname cause the
492 * driver to be registered with the DriverManager.
493 * @param v Value to assign to driver.
494 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
495 */
496 public void setDriver(String v) throws ClassNotFoundException {
497 assertInitializationAllowed();
498 this.driver = v;
499 // make sure driver is registered
500 Class.forName(v);
501 }
502
503 /**
504 * Gets the maximum time in seconds that this data source can wait
505 * while attempting to connect to a database. NOT USED.
506 */
507 public int getLoginTimeout() {
508 return loginTimeout;
509 }
510
511 /**
512 * Get the log writer for this data source. NOT USED.
513 */
514 public PrintWriter getLogWriter() {
515 return logWriter;
516 }
517
518 /**
519 * Sets the maximum time in seconds that this data source will wait
520 * while attempting to connect to a database. NOT USED.
521 */
522 public void setLoginTimeout(int seconds) {
523 loginTimeout = seconds;
524 }
525
526 /**
527 * Set the log writer for this data source. NOT USED.
528 */
529 public void setLogWriter(java.io.PrintWriter out) {
530 logWriter = out;
531 }
532
533
534 // ------------------------------------------------------------------
535 // PreparedStatement pool properties
536
537
538 /**
539 * Flag to toggle the pooling of <code>PreparedStatement</code>s
540 * @return value of poolPreparedStatements.
541 */
542 public boolean isPoolPreparedStatements() {
543 return poolPreparedStatements;
544 }
545
546 /**
547 * Flag to toggle the pooling of <code>PreparedStatement</code>s
548 * @param v true to pool statements.
549 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
550 */
551 public void setPoolPreparedStatements(boolean v) {
552 assertInitializationAllowed();
553 this.poolPreparedStatements = v;
554 }
555
556 /**
557 * The maximum number of active statements that can be allocated from
558 * this pool at the same time, or non-positive for no limit.
559 */
560 public int getMaxActive() {
561 return (this.maxActive);
562 }
563
564 /**
565 * The maximum number of active statements that can be allocated from
566 * this pool at the same time, or non-positive for no limit.
567 * @param maxActive the maximum number of concurrent active statements allowed
568 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
569 */
570 public void setMaxActive(int maxActive) {
571 assertInitializationAllowed();
572 this.maxActive = maxActive;
573 }
574
575 /**
576 * The maximum number of statements that can remain idle in the
577 * pool, without extra ones being released, or negative for no limit.
578 * @return the value of maxIdle
579 */
580 public int getMaxIdle() {
581 return (this.maxIdle);
582 }
583
584 /**
585 * The maximum number of statements that can remain idle in the
586 * pool, without extra ones being released, or negative for no limit.
587 *
588 * @param maxIdle The maximum number of statements that can remain idle
589 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
590 */
591 public void setMaxIdle(int maxIdle) {
592 assertInitializationAllowed();
593 this.maxIdle = maxIdle;
594 }
595
596 /**
597 * Returns the number of milliseconds to sleep between runs of the
598 * idle object evictor thread.
599 * When non-positive, no idle object evictor thread will be
600 * run.
601 * @return the value of the evictor thread timer
602 * @see #setTimeBetweenEvictionRunsMillis(int)
603 */
604 public int getTimeBetweenEvictionRunsMillis() {
605 return _timeBetweenEvictionRunsMillis;
606 }
607
608 /**
609 * Sets the number of milliseconds to sleep between runs of the
610 * idle object evictor thread.
611 * When non-positive, no idle object evictor thread will be
612 * run.
613 * @param timeBetweenEvictionRunsMillis
614 * @see #getTimeBetweenEvictionRunsMillis()
615 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
616 */
617 public void setTimeBetweenEvictionRunsMillis(
618 int timeBetweenEvictionRunsMillis) {
619 assertInitializationAllowed();
620 _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
621 }
622
623 /**
624 * Returns the number of statements to examine during each run of the
625 * idle object evictor thread (if any).
626 *
627 * *see #setNumTestsPerEvictionRun
628 * *see #setTimeBetweenEvictionRunsMillis
629 */
630 public int getNumTestsPerEvictionRun() {
631 return _numTestsPerEvictionRun;
632 }
633
634 /**
635 * Sets the number of statements to examine during each run of the
636 * idle object evictor thread (if any).
637 * <p>
638 * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
639 * tests will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
640 * idle objects will be tested per run.
641 *
642 * @param numTestsPerEvictionRun number of statements to examine per run
643 * @see #getNumTestsPerEvictionRun()
644 * @see #setTimeBetweenEvictionRunsMillis(int)
645 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
646 */
647 public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
648 assertInitializationAllowed();
649 _numTestsPerEvictionRun = numTestsPerEvictionRun;
650 }
651
652 /**
653 * Returns the minimum amount of time a statement may sit idle in the pool
654 * before it is eligible for eviction by the idle object evictor
655 * (if any).
656 *
657 * *see #setMinEvictableIdleTimeMillis
658 * *see #setTimeBetweenEvictionRunsMillis
659 */
660 public int getMinEvictableIdleTimeMillis() {
661 return _minEvictableIdleTimeMillis;
662 }
663
664 /**
665 * Sets the minimum amount of time a statement may sit idle in the pool
666 * before it is eligable for eviction by the idle object evictor
667 * (if any).
668 * When non-positive, no objects will be evicted from the pool
669 * due to idle time alone.
670 * @param minEvictableIdleTimeMillis minimum time to set (in ms)
671 * @see #getMinEvictableIdleTimeMillis()
672 * @see #setTimeBetweenEvictionRunsMillis(int)
673 * @throws IllegalStateException if {@link #getPooledConnection()} has been called
674 */
675 public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
676 assertInitializationAllowed();
677 _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
678 }
679
680 /**
681 * Returns the value of the accessToUnderlyingConnectionAllowed property.
682 *
683 * @return true if access to the underlying is allowed, false otherwise.
684 */
685 public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
686 return this.accessToUnderlyingConnectionAllowed;
687 }
688
689 /**
690 * Sets the value of the accessToUnderlyingConnectionAllowed property.
691 * It controls if the PoolGuard allows access to the underlying connection.
692 * (Default: false)
693 *
694 * @param allow Access to the underlying connection is granted when true.
695 */
696 public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
697 this.accessToUnderlyingConnectionAllowed = allow;
698 }
699
700 /**
701 * Returns the maximun number of prepared statements.
702 *
703 * @return maxPrepartedStatements value
704 * @since 1.2.2
705 */
706 public int getMaxPreparedStatements()
707 {
708 return _maxPreparedStatements;
709 }
710
711 /**
712 * Sets the maximum number of prepared statements.
713 * @param maxPreparedStatements the new maximum number of prepared
714 * statements
715 *
716 * @since 1.2.2
717 */
718 public void setMaxPreparedStatements(int maxPreparedStatements)
719 {
720 _maxPreparedStatements = maxPreparedStatements;
721 }
722 }