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.configuration2; 019 020import javax.sql.DataSource; 021import java.sql.Clob; 022import java.sql.Connection; 023import java.sql.PreparedStatement; 024import java.sql.ResultSet; 025import java.sql.SQLException; 026import java.sql.Statement; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.List; 031 032import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler; 033import org.apache.commons.configuration2.convert.ListDelimiterHandler; 034import org.apache.commons.configuration2.event.ConfigurationErrorEvent; 035import org.apache.commons.configuration2.event.ConfigurationEvent; 036import org.apache.commons.configuration2.event.EventType; 037import org.apache.commons.configuration2.io.ConfigurationLogger; 038import org.apache.commons.lang3.StringUtils; 039 040/** 041 * Configuration stored in a database. The properties are retrieved from a 042 * table containing at least one column for the keys, and one column for the 043 * values. It's possible to store several configurations in the same table by 044 * adding a column containing the name of the configuration. The name of the 045 * table and the columns have to be specified using the corresponding 046 * properties. 047 * <p> 048 * The recommended way to create an instance of {@code DatabaseConfiguration} 049 * is to use a <em>configuration builder</em>. The builder is configured with 050 * a special parameters object defining the database structures used by the 051 * configuration. Such an object can be created using the {@code database()} 052 * method of the {@code Parameters} class. See the examples below for more 053 * details. 054 * </p> 055 * 056 * <p> 057 * <strong>Example 1 - One configuration per table</strong> 058 * </p> 059 * 060 * <pre> 061 * CREATE TABLE myconfig ( 062 * `key` VARCHAR NOT NULL PRIMARY KEY, 063 * `value` VARCHAR 064 * ); 065 * 066 * INSERT INTO myconfig (key, value) VALUES ('foo', 'bar'); 067 * 068 * BasicConfigurationBuilder<DatabaseConfiguration> builder = 069 * new BasicConfigurationBuilder<DatabaseConfiguration>(DatabaseConfiguration.class); 070 * builder.configure( 071 * Parameters.database() 072 * .setDataSource(dataSource) 073 * .setTable("myconfig") 074 * .setKeyColumn("key") 075 * .setValueColumn("value") 076 * ); 077 * Configuration config = builder.getConfiguration(); 078 * String value = config.getString("foo"); 079 * </pre> 080 * 081 * <p> 082 * <strong>Example 2 - Multiple configurations per table</strong> 083 * </p> 084 * 085 * <pre> 086 * CREATE TABLE myconfigs ( 087 * `name` VARCHAR NOT NULL, 088 * `key` VARCHAR NOT NULL, 089 * `value` VARCHAR, 090 * CONSTRAINT sys_pk_myconfigs PRIMARY KEY (`name`, `key`) 091 * ); 092 * 093 * INSERT INTO myconfigs (name, key, value) VALUES ('config1', 'key1', 'value1'); 094 * INSERT INTO myconfigs (name, key, value) VALUES ('config2', 'key2', 'value2'); 095 * 096 * BasicConfigurationBuilder<DatabaseConfiguration> builder = 097 * new BasicConfigurationBuilder<DatabaseConfiguration>(DatabaseConfiguration.class); 098 * builder.configure( 099 * Parameters.database() 100 * .setDataSource(dataSource) 101 * .setTable("myconfigs") 102 * .setKeyColumn("key") 103 * .setValueColumn("value") 104 * .setConfigurationNameColumn("name") 105 * .setConfigurationName("config1") 106 * ); 107 * Configuration config1 = new DatabaseConfiguration(dataSource, "myconfigs", "name", "key", "value", "config1"); 108 * String value1 = conf.getString("key1"); 109 * </pre> 110 * The configuration can be instructed to perform commits after database updates. 111 * This is achieved by setting the {@code commits} parameter of the 112 * constructors to <b>true</b>. If commits should not be performed (which is the 113 * default behavior), it should be ensured that the connections returned by the 114 * {@code DataSource} are in auto-commit mode. 115 * 116 * <h1>Note: Like JDBC itself, protection against SQL injection is left to the user.</h1> 117 * @since 1.0 118 * 119 * @author <a href="mailto:ebourg@apache.org">Emmanuel Bourg</a> 120 * @version $Id: DatabaseConfiguration.java 1843324 2018-10-09 18:44:18Z ggregory $ 121 */ 122public class DatabaseConfiguration extends AbstractConfiguration 123{ 124 /** Constant for the statement used by getProperty.*/ 125 private static final String SQL_GET_PROPERTY = "SELECT * FROM %s WHERE %s =?"; 126 127 /** Constant for the statement used by isEmpty.*/ 128 private static final String SQL_IS_EMPTY = "SELECT count(*) FROM %s WHERE 1 = 1"; 129 130 /** Constant for the statement used by clearProperty.*/ 131 private static final String SQL_CLEAR_PROPERTY = "DELETE FROM %s WHERE %s =?"; 132 133 /** Constant for the statement used by clear.*/ 134 private static final String SQL_CLEAR = "DELETE FROM %s WHERE 1 = 1"; 135 136 /** Constant for the statement used by getKeys.*/ 137 private static final String SQL_GET_KEYS = "SELECT DISTINCT %s FROM %s WHERE 1 = 1"; 138 139 /** The data source to connect to the database. */ 140 private DataSource dataSource; 141 142 /** The configurationName of the table containing the configurations. */ 143 private String table; 144 145 /** The column containing the configurationName of the configuration. */ 146 private String configurationNameColumn; 147 148 /** The column containing the keys. */ 149 private String keyColumn; 150 151 /** The column containing the values. */ 152 private String valueColumn; 153 154 /** The configurationName of the configuration. */ 155 private String configurationName; 156 157 /** A flag whether commits should be performed by this configuration. */ 158 private boolean autoCommit; 159 160 /** 161 * Creates a new instance of {@code DatabaseConfiguration}. 162 */ 163 public DatabaseConfiguration() 164 { 165 initLogger(new ConfigurationLogger(DatabaseConfiguration.class)); 166 addErrorLogListener(); 167 } 168 169 /** 170 * Returns the {@code DataSource} for obtaining database connections. 171 * 172 * @return the {@code DataSource} 173 */ 174 public DataSource getDataSource() 175 { 176 return dataSource; 177 } 178 179 /** 180 * Sets the {@code DataSource} for obtaining database connections. 181 * 182 * @param dataSource the {@code DataSource} 183 */ 184 public void setDataSource(final DataSource dataSource) 185 { 186 this.dataSource = dataSource; 187 } 188 189 /** 190 * Returns the name of the table containing configuration data. 191 * 192 * @return the name of the table to be queried 193 */ 194 public String getTable() 195 { 196 return table; 197 } 198 199 /** 200 * Sets the name of the table containing configuration data. 201 * 202 * @param table the table name 203 */ 204 public void setTable(final String table) 205 { 206 this.table = table; 207 } 208 209 /** 210 * Returns the name of the table column with the configuration name. 211 * 212 * @return the name of the configuration name column 213 */ 214 public String getConfigurationNameColumn() 215 { 216 return configurationNameColumn; 217 } 218 219 /** 220 * Sets the name of the table column with the configuration name. 221 * 222 * @param configurationNameColumn the name of the column with the 223 * configuration name 224 */ 225 public void setConfigurationNameColumn(final String configurationNameColumn) 226 { 227 this.configurationNameColumn = configurationNameColumn; 228 } 229 230 /** 231 * Returns the name of the column containing the configuration keys. 232 * 233 * @return the name of the key column 234 */ 235 public String getKeyColumn() 236 { 237 return keyColumn; 238 } 239 240 /** 241 * Sets the name of the column containing the configuration keys. 242 * 243 * @param keyColumn the name of the key column 244 */ 245 public void setKeyColumn(final String keyColumn) 246 { 247 this.keyColumn = keyColumn; 248 } 249 250 /** 251 * Returns the name of the column containing the configuration values. 252 * 253 * @return the name of the value column 254 */ 255 public String getValueColumn() 256 { 257 return valueColumn; 258 } 259 260 /** 261 * Sets the name of the column containing the configuration values. 262 * 263 * @param valueColumn the name of the value column 264 */ 265 public void setValueColumn(final String valueColumn) 266 { 267 this.valueColumn = valueColumn; 268 } 269 270 /** 271 * Returns the name of this configuration instance. 272 * 273 * @return the name of this configuration 274 */ 275 public String getConfigurationName() 276 { 277 return configurationName; 278 } 279 280 /** 281 * Sets the name of this configuration instance. 282 * 283 * @param configurationName the name of this configuration 284 */ 285 public void setConfigurationName(final String configurationName) 286 { 287 this.configurationName = configurationName; 288 } 289 290 /** 291 * Returns a flag whether this configuration performs commits after database 292 * updates. 293 * 294 * @return a flag whether commits are performed 295 */ 296 public boolean isAutoCommit() 297 { 298 return autoCommit; 299 } 300 301 /** 302 * Sets the auto commit flag. If set to <b>true</b>, this configuration 303 * performs a commit after each database update. 304 * 305 * @param autoCommit the auto commit flag 306 */ 307 public void setAutoCommit(final boolean autoCommit) 308 { 309 this.autoCommit = autoCommit; 310 } 311 312 /** 313 * Returns the value of the specified property. If this causes a database 314 * error, an error event will be generated of type 315 * {@code READ} with the causing exception. The 316 * event's {@code propertyName} is set to the passed in property key, 317 * the {@code propertyValue} is undefined. 318 * 319 * @param key the key of the desired property 320 * @return the value of this property 321 */ 322 @Override 323 protected Object getPropertyInternal(final String key) 324 { 325 final JdbcOperation<Object> op = 326 new JdbcOperation<Object>(ConfigurationErrorEvent.READ, 327 ConfigurationErrorEvent.READ, key, null) 328 { 329 @Override 330 protected Object performOperation() throws SQLException 331 { 332 final List<Object> results = new ArrayList<>(); 333 try (final ResultSet rs = 334 openResultSet(String.format(SQL_GET_PROPERTY, 335 table, keyColumn), true, key)) 336 { 337 while (rs.next()) 338 { 339 final Object value = extractPropertyValue(rs); 340 // Split value if it contains the list delimiter 341 for (final Object o : getListDelimiterHandler().parse(value)) 342 { 343 results.add(o); 344 } 345 } 346 } 347 if (!results.isEmpty()) 348 { 349 return (results.size() > 1) ? results : results 350 .get(0); 351 } 352 return null; 353 } 354 }; 355 356 return op.execute(); 357 } 358 359 /** 360 * Adds a property to this configuration. If this causes a database error, 361 * an error event will be generated of type {@code ADD_PROPERTY} 362 * with the causing exception. The event's {@code propertyName} is 363 * set to the passed in property key, the {@code propertyValue} 364 * points to the passed in value. 365 * 366 * @param key the property key 367 * @param obj the value of the property to add 368 */ 369 @Override 370 protected void addPropertyDirect(final String key, final Object obj) 371 { 372 new JdbcOperation<Void>(ConfigurationErrorEvent.WRITE, 373 ConfigurationEvent.ADD_PROPERTY, key, obj) 374 { 375 @Override 376 protected Void performOperation() throws SQLException 377 { 378 final StringBuilder query = new StringBuilder("INSERT INTO "); 379 query.append(table).append(" ("); 380 query.append(keyColumn).append(", "); 381 query.append(valueColumn); 382 if (configurationNameColumn != null) 383 { 384 query.append(", ").append(configurationNameColumn); 385 } 386 query.append(") VALUES (?, ?"); 387 if (configurationNameColumn != null) 388 { 389 query.append(", ?"); 390 } 391 query.append(")"); 392 393 try (final PreparedStatement pstmt = initStatement(query.toString(), 394 false, key, String.valueOf(obj))) 395 { 396 if (configurationNameColumn != null) 397 { 398 pstmt.setString(3, configurationName); 399 } 400 401 pstmt.executeUpdate(); 402 return null; 403 } 404 } 405 } 406 .execute(); 407 } 408 409 /** 410 * Adds a property to this configuration. This implementation 411 * temporarily disables list delimiter parsing, so that even if the value 412 * contains the list delimiter, only a single record is written into 413 * the managed table. The implementation of {@code getProperty()} 414 * takes care about delimiters. So list delimiters are fully supported 415 * by {@code DatabaseConfiguration}, but internally treated a bit 416 * differently. 417 * 418 * @param key the key of the new property 419 * @param value the value to be added 420 */ 421 @Override 422 protected void addPropertyInternal(final String key, final Object value) 423 { 424 final ListDelimiterHandler oldHandler = getListDelimiterHandler(); 425 try 426 { 427 // temporarily disable delimiter parsing 428 setListDelimiterHandler(DisabledListDelimiterHandler.INSTANCE); 429 super.addPropertyInternal(key, value); 430 } 431 finally 432 { 433 setListDelimiterHandler(oldHandler); 434 } 435 } 436 437 /** 438 * Checks if this configuration is empty. If this causes a database error, 439 * an error event will be generated of type {@code READ} 440 * with the causing exception. Both the event's {@code propertyName} 441 * and {@code propertyValue} will be undefined. 442 * 443 * @return a flag whether this configuration is empty. 444 */ 445 @Override 446 protected boolean isEmptyInternal() 447 { 448 final JdbcOperation<Integer> op = 449 new JdbcOperation<Integer>(ConfigurationErrorEvent.READ, 450 ConfigurationErrorEvent.READ, null, null) 451 { 452 @Override 453 protected Integer performOperation() throws SQLException 454 { 455 try (final ResultSet rs = openResultSet(String.format( 456 SQL_IS_EMPTY, table), true)) 457 { 458 return rs.next() ? Integer.valueOf(rs.getInt(1)) : null; 459 } 460 } 461 }; 462 463 final Integer count = op.execute(); 464 return count == null || count.intValue() == 0; 465 } 466 467 /** 468 * Checks whether this configuration contains the specified key. If this 469 * causes a database error, an error event will be generated of type 470 * {@code READ} with the causing exception. The 471 * event's {@code propertyName} will be set to the passed in key, the 472 * {@code propertyValue} will be undefined. 473 * 474 * @param key the key to be checked 475 * @return a flag whether this key is defined 476 */ 477 @Override 478 protected boolean containsKeyInternal(final String key) 479 { 480 final JdbcOperation<Boolean> op = 481 new JdbcOperation<Boolean>(ConfigurationErrorEvent.READ, 482 ConfigurationErrorEvent.READ, key, null) 483 { 484 @Override 485 protected Boolean performOperation() throws SQLException 486 { 487 try (final ResultSet rs = openResultSet( 488 String.format(SQL_GET_PROPERTY, table, keyColumn), true, key)) 489 { 490 return rs.next(); 491 } 492 } 493 }; 494 495 final Boolean result = op.execute(); 496 return result != null && result.booleanValue(); 497 } 498 499 /** 500 * Removes the specified value from this configuration. If this causes a 501 * database error, an error event will be generated of type 502 * {@code CLEAR_PROPERTY} with the causing exception. The 503 * event's {@code propertyName} will be set to the passed in key, the 504 * {@code propertyValue} will be undefined. 505 * 506 * @param key the key of the property to be removed 507 */ 508 @Override 509 protected void clearPropertyDirect(final String key) 510 { 511 new JdbcOperation<Void>(ConfigurationErrorEvent.WRITE, 512 ConfigurationEvent.CLEAR_PROPERTY, key, null) 513 { 514 @Override 515 protected Void performOperation() throws SQLException 516 { 517 try (final PreparedStatement ps = initStatement(String.format( 518 SQL_CLEAR_PROPERTY, table, keyColumn), true, key)) 519 { 520 ps.executeUpdate(); 521 return null; 522 } 523 } 524 } 525 .execute(); 526 } 527 528 /** 529 * Removes all entries from this configuration. If this causes a database 530 * error, an error event will be generated of type 531 * {@code CLEAR} with the causing exception. Both the 532 * event's {@code propertyName} and the {@code propertyValue} 533 * will be undefined. 534 */ 535 @Override 536 protected void clearInternal() 537 { 538 new JdbcOperation<Void>(ConfigurationErrorEvent.WRITE, 539 ConfigurationEvent.CLEAR, null, null) 540 { 541 @Override 542 protected Void performOperation() throws SQLException 543 { 544 initStatement(String.format(SQL_CLEAR, 545 table), true).executeUpdate(); 546 return null; 547 } 548 } 549 .execute(); 550 } 551 552 /** 553 * Returns an iterator with the names of all properties contained in this 554 * configuration. If this causes a database 555 * error, an error event will be generated of type 556 * {@code READ} with the causing exception. Both the 557 * event's {@code propertyName} and the {@code propertyValue} 558 * will be undefined. 559 * @return an iterator with the contained keys (an empty iterator in case 560 * of an error) 561 */ 562 @Override 563 protected Iterator<String> getKeysInternal() 564 { 565 final Collection<String> keys = new ArrayList<>(); 566 new JdbcOperation<Collection<String>>(ConfigurationErrorEvent.READ, 567 ConfigurationErrorEvent.READ, null, null) 568 { 569 @Override 570 protected Collection<String> performOperation() throws SQLException 571 { 572 try (final ResultSet rs = openResultSet(String.format( 573 SQL_GET_KEYS, keyColumn, table), true)) 574 { 575 while (rs.next()) 576 { 577 keys.add(rs.getString(1)); 578 } 579 return keys; 580 } 581 } 582 } 583 .execute(); 584 585 return keys.iterator(); 586 } 587 588 /** 589 * Returns the used {@code DataSource} object. 590 * 591 * @return the data source 592 * @since 1.4 593 */ 594 public DataSource getDatasource() 595 { 596 return dataSource; 597 } 598 599 /** 600 * Close the specified database objects. 601 * Avoid closing if null and hide any SQLExceptions that occur. 602 * 603 * @param conn The database connection to close 604 * @param stmt The statement to close 605 * @param rs the result set to close 606 */ 607 protected void close(final Connection conn, final Statement stmt, final ResultSet rs) 608 { 609 try 610 { 611 if (rs != null) 612 { 613 rs.close(); 614 } 615 } 616 catch (final SQLException e) 617 { 618 getLogger().error("An error occurred on closing the result set", e); 619 } 620 621 try 622 { 623 if (stmt != null) 624 { 625 stmt.close(); 626 } 627 } 628 catch (final SQLException e) 629 { 630 getLogger().error("An error occured on closing the statement", e); 631 } 632 633 try 634 { 635 if (conn != null) 636 { 637 conn.close(); 638 } 639 } 640 catch (final SQLException e) 641 { 642 getLogger().error("An error occured on closing the connection", e); 643 } 644 } 645 646 /** 647 * Extracts the value of a property from the given result set. The passed in 648 * {@code ResultSet} was created by a SELECT statement on the underlying 649 * database table. This implementation reads the value of the column 650 * determined by the {@code valueColumn} property. Normally the contained 651 * value is directly returned. However, if it is of type {@code CLOB}, text 652 * is extracted as string. 653 * 654 * @param rs the current {@code ResultSet} 655 * @return the value of the property column 656 * @throws SQLException if an error occurs 657 */ 658 protected Object extractPropertyValue(final ResultSet rs) throws SQLException 659 { 660 Object value = rs.getObject(valueColumn); 661 if (value instanceof Clob) 662 { 663 value = convertClob((Clob) value); 664 } 665 return value; 666 } 667 668 /** 669 * Converts a CLOB to a string. 670 * 671 * @param clob the CLOB to be converted 672 * @return the extracted string value 673 * @throws SQLException if an error occurs 674 */ 675 private static Object convertClob(final Clob clob) throws SQLException 676 { 677 final int len = (int) clob.length(); 678 return (len > 0) ? clob.getSubString(1, len) : StringUtils.EMPTY; 679 } 680 681 /** 682 * An internally used helper class for simplifying database access through 683 * plain JDBC. This class provides a simple framework for creating and 684 * executing a JDBC statement. It especially takes care of proper handling 685 * of JDBC resources even in case of an error. 686 * @param <T> the type of the results produced by a JDBC operation 687 */ 688 private abstract class JdbcOperation<T> 689 { 690 /** Stores the connection. */ 691 private Connection conn; 692 693 /** Stores the statement. */ 694 private PreparedStatement pstmt; 695 696 /** Stores the result set. */ 697 private ResultSet resultSet; 698 699 /** The type of the event to send in case of an error. */ 700 private final EventType<? extends ConfigurationErrorEvent> errorEventType; 701 702 /** The type of the operation which caused an error. */ 703 private final EventType<?> operationEventType; 704 705 /** The property configurationName for an error event. */ 706 private final String errorPropertyName; 707 708 /** The property value for an error event. */ 709 private final Object errorPropertyValue; 710 711 /** 712 * Creates a new instance of {@code JdbcOperation} and initializes the 713 * properties related to the error event. 714 * 715 * @param errEvType the type of the error event 716 * @param opType the operation event type 717 * @param errPropName the property configurationName for the error event 718 * @param errPropVal the property value for the error event 719 */ 720 protected JdbcOperation( 721 final EventType<? extends ConfigurationErrorEvent> errEvType, 722 final EventType<?> opType, final String errPropName, final Object errPropVal) 723 { 724 errorEventType = errEvType; 725 operationEventType = opType; 726 errorPropertyName = errPropName; 727 errorPropertyValue = errPropVal; 728 } 729 730 /** 731 * Executes this operation. This method obtains a database connection 732 * and then delegates to {@code performOperation()}. Afterwards it 733 * performs the necessary clean up. Exceptions that are thrown during 734 * the JDBC operation are caught and transformed into configuration 735 * error events. 736 * 737 * @return the result of the operation 738 */ 739 public T execute() 740 { 741 T result = null; 742 743 try 744 { 745 conn = getDatasource().getConnection(); 746 result = performOperation(); 747 748 if (isAutoCommit()) 749 { 750 conn.commit(); 751 } 752 } 753 catch (final SQLException e) 754 { 755 fireError(errorEventType, operationEventType, errorPropertyName, 756 errorPropertyValue, e); 757 } 758 finally 759 { 760 close(conn, pstmt, resultSet); 761 } 762 763 return result; 764 } 765 766 /** 767 * Returns the current connection. This method can be called while 768 * {@code execute()} is running. It returns <b>null</b> otherwise. 769 * 770 * @return the current connection 771 */ 772 protected Connection getConnection() 773 { 774 return conn; 775 } 776 777 /** 778 * Creates a {@code PreparedStatement} object for executing the 779 * specified SQL statement. 780 * 781 * @param sql the statement to be executed 782 * @param nameCol a flag whether the configurationName column should be taken into 783 * account 784 * @return the prepared statement object 785 * @throws SQLException if an SQL error occurs 786 */ 787 protected PreparedStatement createStatement(final String sql, final boolean nameCol) 788 throws SQLException 789 { 790 String statement; 791 if (nameCol && configurationNameColumn != null) 792 { 793 final StringBuilder buf = new StringBuilder(sql); 794 buf.append(" AND ").append(configurationNameColumn).append("=?"); 795 statement = buf.toString(); 796 } 797 else 798 { 799 statement = sql; 800 } 801 802 pstmt = getConnection().prepareStatement(statement); 803 return pstmt; 804 } 805 806 /** 807 * Creates an initializes a {@code PreparedStatement} object for 808 * executing an SQL statement. This method first calls 809 * {@code createStatement()} for creating the statement and then 810 * initializes the statement's parameters. 811 * 812 * @param sql the statement to be executed 813 * @param nameCol a flag whether the configurationName column should be taken into 814 * account 815 * @param params the parameters for the statement 816 * @return the initialized statement object 817 * @throws SQLException if an SQL error occurs 818 */ 819 protected PreparedStatement initStatement(final String sql, final boolean nameCol, 820 final Object... params) throws SQLException 821 { 822 final PreparedStatement ps = createStatement(sql, nameCol); 823 824 int idx = 1; 825 for (final Object param : params) 826 { 827 ps.setObject(idx++, param); 828 } 829 if (nameCol && configurationNameColumn != null) 830 { 831 ps.setString(idx, configurationName); 832 } 833 834 return ps; 835 } 836 837 /** 838 * Creates a {@code PreparedStatement} for a query, initializes it and 839 * executes it. The resulting {@code ResultSet} is returned. 840 * 841 * @param sql the statement to be executed 842 * @param nameCol a flag whether the configurationName column should be taken into 843 * account 844 * @param params the parameters for the statement 845 * @return the {@code ResultSet} produced by the query 846 * @throws SQLException if an SQL error occurs 847 */ 848 protected ResultSet openResultSet(final String sql, final boolean nameCol, 849 final Object... params) throws SQLException 850 { 851 return resultSet = initStatement(sql, nameCol, params).executeQuery(); 852 } 853 854 /** 855 * Performs the JDBC operation. This method is called by 856 * {@code execute()} after this object has been fully initialized. 857 * Here the actual JDBC logic has to be placed. 858 * 859 * @return the result of the operation 860 * @throws SQLException if an SQL error occurs 861 */ 862 protected abstract T performOperation() throws SQLException; 863 } 864}