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