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 }