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 }