Coverage Summary for Class: Session (net.sf.persism)

Class Method, % Line, %
Session 100% (21/21) 94.4% (354/375)
Session$1 100% (1/1) 100% (1/1)
Total 100% (22/22) 94.4% (355/376)


1 package net.sf.persism; 2  3 import net.sf.persism.annotations.NotTable; 4 import net.sf.persism.annotations.View; 5  6 import java.lang.reflect.Method; 7 import java.math.BigDecimal; 8 import java.sql.*; 9 import java.util.ArrayList; 10 import java.util.Arrays; 11 import java.util.List; 12 import java.util.Map; 13  14 import static net.sf.persism.Util.isRecord; 15  16 /** 17  * Performs various read and write operations in the database. 18  * 19  * @author Dan Howard 20  * @since 1/8/2021 21  */ 22 public final class Session implements AutoCloseable { 23  24  private static final Log log = Log.getLogger(Session.class); 25  26  private final Connection connection; 27  28  private MetaData metaData; 29  30  private Reader reader; 31  private Converter converter; 32  33  /** 34  * Default constructor for a Session object 35  * 36  * @param connection db connection 37  * @throws PersismException if something goes wrong 38  */ 39  public Session(Connection connection) throws PersismException { 40  this.connection = connection; 41  init(connection, null); 42  } 43  44  /** 45  * Constructor for Session where you want to specify the Session Key. 46  * 47  * @param connection db connection 48  * @param sessionKey Unique string to represent the connection URL if it is not available on the Connection metadata. 49  * This string should start with the jdbc url string to indicate the connection type. 50  * <code> 51  * <br> jdbc:h2 = h2 52  * <br> jdbc:sqlserver = MS SQL 53  * <br> jdbc:oracle = Oracle 54  * <br> jdbc:sqlite = SQLite 55  * <br> jdbc:derby = Derby 56  * <br> jdbc:mysql = MySQL/MariaDB 57  * <br> jbc:postgresql = PostgreSQL 58  * <br> jdbc:firebirdsql = Firebird (Jaybird) 59  * <br> jdbc:hsqldb = HSQLDB 60  * <br> jdbc:ucanaccess = MS Access 61  * <br> jdbc:informix = Informix 62  * </code> 63  * @throws PersismException if something goes wrong 64  */ 65  public Session(Connection connection, String sessionKey) throws PersismException { 66  this.connection = connection; 67  init(connection, sessionKey); 68  } 69  70  /** 71  * Close the connection 72  */ 73  @Override 74  public void close() { 75  if (connection != null) { 76  try { 77  connection.close(); 78  } catch (SQLException e) { 79  log.warn(e.getMessage(), e); 80  } 81  } 82  } 83  84  private void init(Connection connection, String sessionKey) { 85  // place any DB specific properties here. 86  try { 87  metaData = MetaData.getInstance(connection, sessionKey); 88  } catch (SQLException e) { 89  throw new PersismException(e.getMessage(), e); 90  } 91  92  converter = new Converter(); 93  reader = new Reader(this); 94  } 95  96  /** 97  * Function block of database operations to group together in one transaction. 98  * This method will set autocommit to false then execute the function, commit and set autocommit back to true. 99  * <pre>{@code 100  * session.withTransaction(() -> { 101  * Contact contact = getContactFromSomewhere(); 102  * 103  * contact.setIdentity(randomUUID); 104  * session.insert(contact); 105  * 106  * contact.setContactName("Wilma Flintstone"); 107  * 108  * session.update(contact); 109  * session.fetch(contact); 110  * }); 111  * }</pre> 112  * 113  * @param transactionBlock Block of operations expected to run as a single transaction. 114  * @throws PersismException in case of SQLException where the transaction is rolled back. 115  */ 116  public void withTransaction(Runnable transactionBlock) { 117  try { 118  connection.setAutoCommit(false); 119  transactionBlock.run(); 120  connection.commit(); 121  } catch (Exception e) { 122  Util.rollback(connection); 123  throw new PersismException(e.getMessage(), e); 124  } finally { 125  try { 126  connection.setAutoCommit(true); 127  } catch (SQLException e) { 128  log.warn(e.getMessage()); 129  } 130  } 131  } 132  133  /** 134  * Updates the data object in the database. 135  * 136  * @param object data object to update. 137  * @return usually 1 to indicate rows changed via JDBC. 138  * @throws PersismException Indicating the upcoming robot uprising. 139  */ 140  public <T> Result<T> update(T object) throws PersismException { 141  142  checkIfOkForWriteOperation(object, "Update"); 143  144  List<String> primaryKeys = metaData.getPrimaryKeys(object.getClass(), connection); 145  if (primaryKeys.size() == 0) { 146  throw new PersismException(Messages.TableHasNoPrimaryKeys.message("UPDATE", metaData.getTableName(object.getClass()))); 147  } 148  149  PreparedStatement st = null; 150  try { 151  152  String updateStatement = null; 153  try { 154  updateStatement = metaData.getUpdateStatement(object, connection); 155  } catch (NoChangesDetectedForUpdateException e) { 156  log.info("No properties changed. No update required for Object: " + object + " class: " + object.getClass().getName()); 157  return new Result<>(0, object); 158  } 159  160  st = connection.prepareStatement(updateStatement); 161  162  // These keys should always be in sorted order. 163  Map<String, PropertyInfo> allProperties = metaData.getTableColumnsPropertyInfo(object.getClass(), connection); 164  Map<String, PropertyInfo> changedProperties; 165  if (object instanceof Persistable) { 166  changedProperties = metaData.getChangedProperties((Persistable<?>) object, connection); 167  } else { 168  changedProperties = allProperties; 169  } 170  171  List<Object> params = new ArrayList<>(primaryKeys.size()); 172  List<ColumnInfo> columnInfos = new ArrayList<>(changedProperties.size()); 173  174  Map<String, ColumnInfo> columns = metaData.getColumns(object.getClass(), connection); 175  176  for (String column : changedProperties.keySet()) { 177  ColumnInfo columnInfo = columns.get(column); 178  179  if (primaryKeys.contains(column)) { 180  log.info("Session update: skipping column " + column); 181  } else { 182  Object value = allProperties.get(column).getter.invoke(object); 183  params.add(value); 184  columnInfos.add(columnInfo); 185  } 186  } 187  188  for (String column : primaryKeys) { 189  params.add(allProperties.get(column).getter.invoke(object)); 190  columnInfos.add(metaData.getColumns(object.getClass(), connection).get(column)); 191  } 192  assert params.size() == columnInfos.size(); 193  for (int j = 0; j < params.size(); j++) { 194  if (params.get(j) != null) { 195  params.set(j, converter.convert(params.get(j), columnInfos.get(j).columnType.getJavaType(), columnInfos.get(j).columnName)); 196  } 197  } 198  setParameters(st, params.toArray()); 199  int rows = st.executeUpdate(); 200  201  if (object instanceof Persistable) { 202  // Save this object state to later detect changed properties 203  ((Persistable<?>) object).saveReadState(); 204  } 205  206  return new Result<>(rows, object); 207  208  } catch (Exception e) { 209  Util.rollback(connection); 210  throw new PersismException(e.getMessage(), e); 211  212  } finally { 213  Util.cleanup(st, null); 214  } 215  } 216  217  /** 218  * Inserts the data object in the database refreshing with autoinc and other defaults that may exist. 219  * 220  * @param <T> Type of the inserted object 221  * @param object the data object to insert. 222  * @return usually 1 to indicate rows changed via JDBC. 223  * @throws PersismException When planet of the apes starts happening. 224  */ 225  public <T> Result<T> insert(T object) throws PersismException { 226  227  checkIfOkForWriteOperation(object, "Insert"); 228  229  Object returnObject = object; 230  231  String insertStatement = metaData.getInsertStatement(object, connection); 232  233  PreparedStatement st = null; 234  ResultSet rs = null; 235  236  try { 237  // These keys should always be in sorted order. 238  Map<String, PropertyInfo> properties = metaData.getTableColumnsPropertyInfo(object.getClass(), connection); 239  Map<String, ColumnInfo> columns = metaData.getColumns(object.getClass(), connection); 240  241  List<String> generatedKeys = new ArrayList<>(1); 242  for (ColumnInfo column : columns.values()) { 243  if (column.autoIncrement) { 244  generatedKeys.add(column.columnName); 245  } else if (metaData.getConnectionType() == ConnectionTypes.PostgreSQL && column.primary && column.hasDefault) { 246  generatedKeys.add(column.columnName); 247  } 248  } 249  250  if (generatedKeys.size() > 0) { 251  String[] keyArray = generatedKeys.toArray(new String[0]); 252  st = connection.prepareStatement(insertStatement, keyArray); 253  } else { 254  st = connection.prepareStatement(insertStatement); 255  } 256  257  boolean refreshAfterInsert = false; 258  259  List<Object> params = new ArrayList<>(columns.size()); 260  List<ColumnInfo> columnInfos = new ArrayList<>(columns.size()); 261  262  for (ColumnInfo columnInfo : columns.values()) { 263  264  PropertyInfo propertyInfo = properties.get(columnInfo.columnName); 265  if (propertyInfo.getter == null) { 266  throw new PersismException(Messages.ClassHasNoGetterForProperty.message(object.getClass(), propertyInfo.propertyName)); 267  } 268  if (!columnInfo.autoIncrement) { 269  270  if (columnInfo.hasDefault) { 271  // Do not include if this column has a default and no value has been 272  // set on it's associated property. 273  if (propertyInfo.getter.getReturnType().isPrimitive()) { 274  log.warnNoDuplicates(Messages.PropertyShouldBeAnObjectType.message(propertyInfo.propertyName, columnInfo.columnName, object.getClass())); 275  } 276  277  if (propertyInfo.getter.invoke(object) == null) { 278  279  if (columnInfo.primary) { 280  // This is supported with PostgreSQL but otherwise throw this an exception 281  if (!(metaData.getConnectionType() == ConnectionTypes.PostgreSQL)) { 282  throw new PersismException(Messages.NonAutoIncGeneratedNotSupported.message()); 283  } 284  } 285  286  refreshAfterInsert = true; 287  continue; 288  } 289  } 290  291  Object value = propertyInfo.getter.invoke(object); 292  293  params.add(value); 294  columnInfos.add(columnInfo); 295  } 296  } 297  298  // https://forums.oracle.com/forums/thread.jspa?threadID=879222 299  // http://download.oracle.com/javase/1.4.2/docs/guide/jdbc/getstart/statement.html 300  //int ret = st.executeUpdate(insertStatement, Statement.RETURN_GENERATED_KEYS); 301  assert params.size() == columnInfos.size(); 302  for (int j = 0; j < params.size(); j++) { 303  if (params.get(j) != null) { 304  params.set(j, converter.convert(params.get(j), columnInfos.get(j).columnType.getJavaType(), columnInfos.get(j).columnName)); 305  } 306  } 307  308  setParameters(st, params.toArray()); 309  st.execute(); 310  int ret = st.getUpdateCount(); 311  312  log.debug("insert return count after insert: %s", ret); 313  314  List<Object> primaryKeyValues = new ArrayList<>(); 315  if (generatedKeys.size() > 0) { 316  rs = st.getGeneratedKeys(); 317  PropertyInfo propertyInfo; 318  for (String column : generatedKeys) { 319  if (rs.next()) { 320  321  propertyInfo = properties.get(column); 322  323  Method setter = propertyInfo.setter; 324  Object value; 325  if (setter != null) { 326  value = getTypedValueReturnedFromGeneratedKeys(setter.getParameterTypes()[0], rs); 327  setter.invoke(object, value); 328  } else { 329  // Set read-only property by field ONLY FOR NON-RECORDS. 330  value = getTypedValueReturnedFromGeneratedKeys(propertyInfo.field.getType(), rs); 331  if (!isRecord(object.getClass())) { 332  propertyInfo.field.setAccessible(true); 333  propertyInfo.field.set(object, value); 334  propertyInfo.field.setAccessible(false); 335  log.debug("insert %s generated %s", column, value); 336  } 337  } 338  339  primaryKeyValues.add(value); 340  } 341  } 342  } 343  344  // If it's a record we can't assign the autoinc so we need a refresh 345  if (generatedKeys.size() > 0 && isRecord(object.getClass())) { 346  refreshAfterInsert = true; 347  } 348  349  if (refreshAfterInsert) { 350  // Read the full object back to update any properties which had defaults 351  if (isRecord(object.getClass())) { 352  returnObject = fetch(object.getClass(), metaData.getSelectStatement(object.getClass(), connection), primaryKeyValues.toArray()); 353  } else { 354  fetch(object); 355  returnObject = object; 356  } 357  } 358  359  if (object instanceof Persistable) { 360  // Save this object new state to later detect changed properties 361  ((Persistable<?>) object).saveReadState(); 362  } 363  364  //noinspection unchecked 365  return new Result<>(ret, (T) returnObject); 366  } catch (Exception e) { 367  Util.rollback(connection); 368  throw new PersismException(e.getMessage(), e); 369  } finally { 370  Util.cleanup(st, rs); 371  } 372  } 373  374  375  /** 376  * Deletes the data object from the database. 377  * 378  * @param object data object to delete 379  * @return usually 1 to indicate rows changed via JDBC. 380  * @throws PersismException Perhaps when asteroid 1999 RQ36 hits us? 381  */ 382  public <T> Result<T> delete(T object) throws PersismException { 383  384  checkIfOkForWriteOperation(object, "Delete"); 385  386  List<String> primaryKeys = metaData.getPrimaryKeys(object.getClass(), connection); 387  if (primaryKeys.size() == 0) { 388  throw new PersismException(Messages.TableHasNoPrimaryKeys.message("DELETE", metaData.getTableName(object.getClass()))); 389  } 390  391  PreparedStatement st = null; 392  try { 393  String deleteStatement = metaData.getDeleteStatement(object, connection); 394  st = connection.prepareStatement(deleteStatement); 395  396  // These keys should always be in sorted order. 397  Map<String, PropertyInfo> columns = metaData.getTableColumnsPropertyInfo(object.getClass(), connection); 398  399  List<Object> params = new ArrayList<>(primaryKeys.size()); 400  List<ColumnInfo> columnInfos = new ArrayList<>(columns.size()); 401  for (String column : primaryKeys) { 402  params.add(columns.get(column).getter.invoke(object)); 403  columnInfos.add(metaData.getColumns(object.getClass(), connection).get(column)); 404  } 405  406  for (int j = 0; j < params.size(); j++) { 407  if (params.get(j) != null) { 408  params.set(j, converter.convert(params.get(j), columnInfos.get(j).columnType.getJavaType(), columnInfos.get(j).columnName)); 409  } 410  } 411  setParameters(st, params.toArray()); 412  int rows = st.executeUpdate(); 413  return new Result<>(rows, object); 414  415  } catch (Exception e) { 416  Util.rollback(connection); 417  throw new PersismException(e.getMessage(), e); 418  419  } finally { 420  Util.cleanup(st, null); 421  } 422  } 423  424  // For unit tests only for now. 425  void execute(String sql, Object... parameters) { 426  427  log.debug("execute: %s params: %s", sql, Arrays.asList(parameters)); 428  429  Statement st = null; 430  try { 431  432  if (parameters.length == 0) { 433  st = connection.createStatement(); 434  st.execute(sql); 435  } else { 436  st = connection.prepareStatement(sql); 437  PreparedStatement pst = (PreparedStatement) st; 438  setParameters(pst, parameters); 439  pst.execute(); 440  } 441  442  } catch (Exception e) { 443  Util.rollback(connection); 444  throw new PersismException(e.getMessage(), e); 445  } finally { 446  Util.cleanup(st, null); 447  } 448  } 449  450  /** 451  * Query for all rows in a table or view. 452  * 453  * @param objectClass class of objects to return. 454  * @param <T> Return type 455  * @return a list of objects of the specified class 456  * @throws PersismException If something goes wrong you get a big stack trace. 457  */ 458  public <T> List<T> query(Class<T> objectClass) throws Exception { 459  if (objectClass.getAnnotation(NotTable.class) != null) { 460  throw new PersismException(Messages.OperationNotSupportedForNotTableQuery.message(objectClass, "QUERY")); 461  } 462  String sql = metaData.getSelectStatement(objectClass, connection); 463  sql = sql.substring(0, sql.indexOf(" WHERE")); 464  return query(objectClass, sql); 465  } 466  467  /** 468  * Query for a list of objects of the specified class using the specified SQL query and parameters. 469  * The type of the list can be Data Objects or native Java Objects or primitives. 470  * 471  * @param objectClass class of objects to return. 472  * @param sql query string to execute. 473  * @param parameters parameters to the query. 474  * @param <T> Return type 475  * @return a list of objects of the specified class using the specified SQL query and parameters. 476  * @throws PersismException If something goes wrong you get a big stack trace. 477  */ 478  public <T> List<T> query(Class<T> objectClass, String sql, Object... parameters) throws PersismException { 479  List<T> list = new ArrayList<T>(32); 480  481  // If we know this type it means it's a primitive type. Not a DAO so we use a different rule to read those 482  boolean isPOJO = Types.getType(objectClass) == null; 483  boolean isRecord = isPOJO && isRecord(objectClass); 484  485  if (isPOJO && objectClass.getAnnotation(NotTable.class) == null) { 486  // Make sure columns are initialized if this is a table. 487  metaData.getTableColumnsPropertyInfo(objectClass, connection); 488  } 489  490  JDBCResult result = new JDBCResult(); 491  try { 492  // we don't check parameter types here? Nope - we don't know anything at this point. 493  executeQuery(result, sql, parameters); 494  495  while (result.rs.next()) { 496  if (isRecord) { 497  list.add(reader.readRecord(objectClass, result.rs)); 498  } else if (isPOJO) { 499  T t = objectClass.getDeclaredConstructor().newInstance(); 500  list.add(reader.readObject(t, result.rs)); 501  } else { 502  list.add(reader.readColumn(result.rs, 1, objectClass)); 503  } 504  } 505  506  } catch (Exception e) { 507  Util.rollback(connection); 508  throw new PersismException(e.getMessage(), e); 509  } finally { 510  Util.cleanup(result.st, result.rs); 511  } 512  513  return list; 514  515  } 516  517  /** 518  * Fetch an object from the database by it's primary key(s). 519  * You should instantiate the object and set the primary key properties before calling this method. 520  * 521  * @param object Data object to read from the database. 522  * @return true if the object was found by the primary key. 523  * @throws PersismException if something goes wrong. 524  */ 525  public boolean fetch(Object object) throws PersismException { 526  Class<?> objectClass = object.getClass(); 527  528  // If we know this type it means it's a primitive type. This method cannot be used for primitives 529  boolean readPrimitive = Types.getType(objectClass) != null; 530  if (readPrimitive) { 531  // For unit tests 532  throw new PersismException(Messages.CannotReadThisType.message("primitive")); 533  } 534  535  if (isRecord(objectClass)) { 536  throw new PersismException(Messages.CannotReadThisType.message("Record")); 537  } 538  539  List<String> primaryKeys = metaData.getPrimaryKeys(objectClass, connection); 540  if (primaryKeys.size() == 0) { 541  throw new PersismException(Messages.TableHasNoPrimaryKeys.message("FETCH", metaData.getTableName(object.getClass()))); 542  } 543  544  Map<String, PropertyInfo> properties = metaData.getTableColumnsPropertyInfo(object.getClass(), connection); 545  List<Object> params = new ArrayList<>(primaryKeys.size()); 546  List<ColumnInfo> columnInfos = new ArrayList<>(properties.size()); 547  Map<String, ColumnInfo> cols = metaData.getColumns(objectClass, connection); 548  JDBCResult JDBCResult = new JDBCResult(); 549  try { 550  for (String column : primaryKeys) { 551  PropertyInfo propertyInfo = properties.get(column); 552  params.add(propertyInfo.getter.invoke(object)); 553  columnInfos.add(cols.get(column)); 554  } 555  assert params.size() == columnInfos.size(); 556  557  String sql = metaData.getSelectStatement(objectClass, connection); 558  log.debug("FETCH %s PARAMS: %s", sql, params); 559  for (int j = 0; j < params.size(); j++) { 560  if (params.get(j) != null) { 561  params.set(j, converter.convert(params.get(j), columnInfos.get(j).columnType.getJavaType(), columnInfos.get(j).columnName)); 562  } 563  } 564  executeQuery(JDBCResult, sql, params.toArray()); 565  566  if (JDBCResult.rs.next()) { 567  reader.readObject(object, JDBCResult.rs); 568  return true; 569  } 570  return false; 571  572  } catch (Exception e) { 573  Util.rollback(connection); 574  throw new PersismException(e.getMessage(), e); 575  } finally { 576  Util.cleanup(JDBCResult.st, JDBCResult.rs); 577  } 578  } 579  580  /** 581  * Fetch an object of the specified type from the database. The type can be a Data Object or a native Java Object or primitive. 582  * 583  * @param objectClass Type of returned value 584  * @param sql query - this would usually be a select OR a select of a single column if the type is a primitive. 585  * If this is a primitive type then this method will only look at the 1st column in the result. 586  * @param parameters parameters to the query. 587  * @param <T> Return type 588  * @return value read from the database of type T or null if not found 589  * @throws PersismException Well, this is a runtime exception so actually it could be anything really. 590  */ 591  public <T> T fetch(Class<T> objectClass, String sql, Object... parameters) throws PersismException { 592  // If we know this type it means it's a primitive type. Not a DAO so we use a different rule to read those 593  boolean isPOJO = Types.getType(objectClass) == null; 594  boolean isRecord = isPOJO && isRecord(objectClass); 595  596  JDBCResult result = new JDBCResult(); 597  try { 598  599  executeQuery(result, sql, parameters); 600  601  if (result.rs.next()) { 602  if (isRecord) { 603  return reader.readRecord(objectClass, result.rs); 604  } else if (isPOJO) { 605  T t = objectClass.getDeclaredConstructor().newInstance(); 606  return reader.readObject(t, result.rs); 607  } else { 608  return reader.readColumn(result.rs, 1, objectClass); 609  } 610  } 611  612  return null; 613  614  } catch (Exception e) { 615  Util.rollback(connection); 616  throw new PersismException(e.getMessage(), e); 617  } finally { 618  Util.cleanup(result.st, result.rs); 619  } 620  } 621  622  MetaData getMetaData() { 623  return metaData; 624  } 625  626  Converter getConverter() { 627  return converter; 628  } 629  630  Connection getConnection() { 631  return connection; 632  } 633  /* 634  Private methods 635  */ 636  637  private void executeQuery(JDBCResult result, String sql, Object... parameters) throws SQLException { 638  if (sql.trim().toLowerCase().startsWith("select ")) { 639  if (metaData.getConnectionType() == ConnectionTypes.Firebird) { 640  // https://stackoverflow.com/questions/935511/how-can-i-avoid-resultset-is-closed-exception-in-java 641  result.st = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); 642  } else { 643  result.st = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 644  } 645  646  PreparedStatement pst = (PreparedStatement) result.st; 647  setParameters(pst, parameters); 648  result.rs = pst.executeQuery(); 649  } else { 650  if (!sql.trim().toLowerCase().startsWith("{call")) { 651  sql = "{call " + sql + "} "; 652  } 653  // Don't need If Firebird here. Firebird would call a selectable stored proc with SELECT anyway 654  result.st = connection.prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); 655  656  CallableStatement cst = (CallableStatement) result.st; 657  setParameters(cst, parameters); 658  result.rs = cst.executeQuery(); 659  } 660  } 661  662  private void checkIfOkForWriteOperation(Object object, String operation) { 663  Class<?> objectClass = object.getClass(); 664  665  if (objectClass.getAnnotation(View.class) != null) { 666  throw new PersismException(Messages.OperationNotSupportedForView.message(objectClass, operation)); 667  } 668  if (objectClass.getAnnotation(NotTable.class) != null) { 669  throw new PersismException(Messages.OperationNotSupportedForNotTableQuery.message(objectClass, operation)); 670  } 671  if (Types.getType(objectClass) != null) { 672  throw new PersismException(Messages.OperationNotSupportedForJavaType.message(objectClass, operation)); 673  } 674  } 675  676  private <T> T getTypedValueReturnedFromGeneratedKeys(Class<T> objectClass, ResultSet rs) throws SQLException { 677  678  Object value = null; 679  Types type = Types.getType(objectClass); 680  681  if (type == null) { 682  log.warn(Messages.UnknownTypeForPrimaryGeneratedKey.message(objectClass)); 683  return (T) rs.getObject(1); 684  } 685  686  switch (type) { 687  688  case integerType: 689  case IntegerType: 690  value = rs.getInt(1); 691  break; 692  693  case longType: 694  case LongType: 695  value = rs.getLong(1); 696  break; 697  698  default: 699  value = rs.getObject(1); 700  } 701  return (T) value; 702  } 703  704  void setParameters(PreparedStatement st, Object[] parameters) throws SQLException { 705  if (log.isDebugEnabled()) { 706  log.debug("setParameters PARAMS: %s", Arrays.asList(parameters)); 707  } 708  709  int n = 1; 710  for (Object param : parameters) { 711  712  if (param != null) { 713  714  Types type = Types.getType(param.getClass()); 715  if (type == null) { 716  log.warn(Messages.UnknownTypeInSetParameters.message(param.getClass())); 717  type = Types.ObjectType; 718  } 719  720  Object value; // used for conversions 721  722  switch (type) { 723  724  case booleanType: 725  case BooleanType: 726  st.setBoolean(n, (Boolean) param); 727  break; 728  729  case byteType: 730  case ByteType: 731  st.setByte(n, (Byte) param); 732  break; 733  734  case shortType: 735  case ShortType: 736  st.setShort(n, (Short) param); 737  break; 738  739  case integerType: 740  case IntegerType: 741  st.setInt(n, (Integer) param); 742  break; 743  744  case longType: 745  case LongType: 746  st.setLong(n, (Long) param); 747  break; 748  749  case floatType: 750  case FloatType: 751  st.setFloat(n, (Float) param); 752  break; 753  754  case doubleType: 755  case DoubleType: 756  st.setDouble(n, (Double) param); 757  break; 758  759  case BigDecimalType: 760  st.setBigDecimal(n, (BigDecimal) param); 761  break; 762  763  case BigIntegerType: 764  st.setString(n, "" + param); 765  break; 766  767  case StringType: 768  st.setString(n, (String) param); 769  break; 770  771  case characterType: 772  case CharacterType: 773  st.setObject(n, "" + param); 774  break; 775  776  case SQLDateType: 777  st.setDate(n, (java.sql.Date) param); 778  break; 779  780  case TimeType: 781  st.setTime(n, (Time) param); 782  break; 783  784  case TimestampType: 785  st.setTimestamp(n, (Timestamp) param); 786  break; 787  788  case LocalTimeType: 789  value = converter.convert(param, Time.class, "Parameter " + n); 790  st.setObject(n, value); 791  break; 792  793  case UtilDateType: 794  case LocalDateType: 795  case LocalDateTimeType: 796  value = converter.convert(param, Timestamp.class, "Parameter " + n); 797  st.setObject(n, value); 798  break; 799  800  case OffsetDateTimeType: 801  case ZonedDateTimeType: 802  case InstantType: 803  log.warn(Messages.UnSupportedTypeInSetParameters.message(type)); 804  st.setObject(n, param); 805  // todo ZonedDateTime, OffsetDateTimeType and MAYBE Instant 806  break; 807  808  case byteArrayType: 809  case ByteArrayType: 810  // Blob maps to byte array 811  st.setBytes(n, (byte[]) param); 812  break; 813  814  case ClobType: 815  case BlobType: 816  // Clob is converted to String Blob is converted to byte array 817  // so this should not occur unless they were passed in by the user. 818  // We are most probably about to fail here. 819  log.warn(Messages.ParametersDoNotUseClobOrBlob.message(), new Throwable()); 820  st.setObject(n, param); 821  break; 822  823  case EnumType: 824  if (metaData.getConnectionType() == ConnectionTypes.PostgreSQL) { 825  st.setObject(n, param.toString(), java.sql.Types.OTHER); 826  } else { 827  st.setString(n, param.toString()); 828  } 829  break; 830  831  case UUIDType: 832  if (metaData.getConnectionType() == ConnectionTypes.PostgreSQL) { 833  // PostgreSQL does work with setObject but not setString unless you set the connection property stringtype=unspecified 834  st.setObject(n, param); 835  } else { 836  st.setString(n, param.toString()); 837  } 838  break; 839  840  default: 841  // Usually SQLite with util.date - setObject works 842  // Also if it's a custom non-standard type. 843  log.info("setParameters using setObject on parameter: " + n + " for " + param.getClass()); 844  st.setObject(n, param); 845  } 846  847  } else { 848  // param is null 849  st.setObject(n, param); 850  } 851  852  n++; 853  } 854  } 855  856 }