From cc1bba56816175ff9ab8e7c666f70f03f45701f8 Mon Sep 17 00:00:00 2001
From: bernd
+ * The query timeout (in seconds). The call is cancelled if it hasn't returned a result after
+ * this time.
+ *
+ * 0 means: no timeout.
+ *
+ * @since 2.3
+ */
+ int timeout() default 0;
+
+ /**
+ *
+ * The index of the method parameter (starting with 1) which will be used as a query timeout
+ * (in seconds). The parameter type has to be an
+ * 0 means: no timeout.
+ *
+ * @since 2.3
+ */
+ int timeoutParameterIndex() default 0;
}
diff --git a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java
index 763a56d..4376f27 100644
--- a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java
+++ b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java
@@ -92,6 +92,14 @@ class QuickQueryUtil {
return new Class[0];
}
+ public int timeout() {
+ return 0;
+ }
+
+ public int timeoutParameterIndex() {
+ return 0;
+ }
+
};
private static final Update DEFAULT_UPDATE_PARAMETERS = new Update() {
@@ -120,6 +128,14 @@ class QuickQueryUtil {
public Class extends Annotation> annotationType() {
return Update.class;
}
+
+ public int timeout() {
+ return 0;
+ }
+
+ public int timeoutParameterIndex() {
+ return 0;
+ }
};
private QuickQueryUtil() {
diff --git a/eodsql/src/net/lemnik/eodsql/Select.java b/eodsql/src/net/lemnik/eodsql/Select.java
index d84fb1b..976726a 100644
--- a/eodsql/src/net/lemnik/eodsql/Select.java
+++ b/eodsql/src/net/lemnik/eodsql/Select.java
@@ -18,7 +18,8 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding;
* {@link BaseQuery Query} class that will return some form of data from
* database (typically a {@literal "SELECT"} query. The {@code Select}
* annotation contains the SQL query that will be executed on the database
- *
+ *
* Here is an example of how a int
or Integer. If
+ * both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes
+ * precedence.
+ * @Select
is used:
*
*
@@ -37,41 +38,38 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding;
* following datatypes:
*
*
- *
- * null
if no results where returned.
- * null
if no results where returned.
- * Columns that cannot be mapped to fields are ignored, as are fields
- * that cannot be mapped to columns. @Select
SQL uses the
- * exact same query parser as the {@link Update} annotation, and so can
- * also accept complex types as it's parameters, and insert their
- * fields into the SQL query.
*
+ * Columns that cannot be mapped to fields are ignored, as are fields that
+ * cannot be mapped to columns. @Select
SQL uses the exact same
+ * query parser as the {@link Update} annotation, and so can also accept complex
+ * types as it's parameters, and insert their fields into the SQL query.
+ *
- * The SQL query to execute on the database. The result set of the - * query will be mapped to the datatype specified in the method - * declaration. This attribute works in the exact same way as - * {@link #value}, but is used when you wish to specify values - * other than the SQL query. - *
- * The SQL is partly parsed by the implementation, allowing for - * parameters of the annotated method to be used in the SQL - * statement. Selecting a specific {@code User} object from the - * database may look as follows: + * The SQL query to execute on the database. The result set of the query + * will be mapped to the datatype specified in the method declaration. This + * attribute works in the exact same way as {@link #value}, but is used when + * you wish to specify values other than the SQL query. + *
+ *+ * The SQL is partly parsed by the implementation, allowing for parameters + * of the annotated method to be used in the SQL statement. Selecting a + * specific {@code User} object from the database may look as follows: *
*
* @Select("SELECT * FROM user WHERE id = ?1")
* User selectUserById(int id) throws SQLException;
*
* - * More complex introspection of the method parameters can be done - * using the bracketed syntax as follows: + * More complex introspection of the method parameters can be done using the + * bracketed syntax as follows: *
*
* @Select("SELECT * FROM user WHERE email = ?{1.email}")
* DataIterator<User> selectUsersByUserEmail(User newUser) throws SQLException;
*
- *
+ *
* @return the SQL query to execute against the database
*/
String sql() default "";
/**
- * Specify whether or not the {@link DataSet} resulting from
- * a call to the annotated method will be connected to the
- * database or not. This will generally only have an effect on
- * {@code DataSet}s.
+ * Specify whether or not the {@link DataSet} resulting from a call to the
+ * annotated method will be connected to the database or not. This will
+ * generally only have an effect on {@code DataSet}s.
*
* @see DataSet
* @see DataSet#disconnect
@@ -134,15 +130,14 @@ public @interface Select {
boolean disconnected() default false;
/**
- * If rubberstamp
is flagged as true
,
- * the annotated method must return a {@link DataIterator},
- * otherwise it will be rejected by the {@link QueryTool}.
- * Rubber-stamping will cause the returned {@code DataIterator}
- * to use a single instance of the data-object for all of the
- * rows returned, instead of initializing a new one for each row.
- * This can be used to save CPU time and memory, and is
- * especially useful in display code.
- *
+ * If rubberstamp
is flagged as true
, the
+ * annotated method must return a {@link DataIterator}, otherwise it will be
+ * rejected by the {@link QueryTool}. Rubber-stamping will cause the
+ * returned {@code DataIterator} to use a single instance of the data-object
+ * for all of the rows returned, instead of initializing a new one for each
+ * row. This can be used to save CPU time and memory, and is especially
+ * useful in display code.
+ *
* @see DataIterator
* @return {@literal false} by default
*/
@@ -155,55 +150,56 @@ public @interface Select {
* {@code DataSet} will implement the following additional methods:
*
* * It is considered a validation error for a method to have readOnly set * {@literal false} if it doesn't return a {@code DataSet}. *
- * + * * @since 1.1 * @return {@literal true} by default */ boolean readOnly() default true; /** - *- * The cache class to be used by {@code DataSet}s returned by the - * annotated method. This only has an effect of methods that return a - * {@link DataSet} type. - *
+ *
+ * The cache class to be used by {@code DataSet}s returned by the annotated + * method. This only has an effect of methods that return a {@link DataSet} + * type. + *
+ ** The default {@code DataSetCache} implementation is - * {@link ArrayDataSetCache}, which uses an array to cache - * the row values. For larger {@code ResultSet}s, you - * may want to look into a {@link NullDataSetCache} or using - * a {@link DataIterator} instead of the {@code DataSet} class. - *
- * + * {@link ArrayDataSetCache}, which uses an array to cache the row values. + * For larger {@code ResultSet}s, you may want to look into a + * {@link NullDataSetCache} or using a {@link DataIterator} instead of the + * {@code DataSet} class. + * + * * @see DataSet * @see ArrayDataSetCache * @return the {@code DataSetCache} type that the returned {@code DataSet} - * should proxy all of the returned objects through + * should proxy all of the returned objects through */ Class extends DataSetCache> cache() default ArrayDataSetCache.class; /** *- * This is a hint to the database driver to suggest the number of rows - * to fetch in a single page of the {@link ResultSet}. This value - * directly corresponds to the {@link Statement#setFetchSize(int)} - * method. - *
+ * This is a hint to the database driver to suggest the number of rows to + * fetch in a single page of the {@link ResultSet}. This value directly + * corresponds to the {@link Statement#setFetchSize(int)} method. + *
+ ** If this value is {@literal 0} (which it is by default) then the - * fetch-size of the underlying {@code Statement} will be left alone. - * This value (like the {@code fetch-size} of a {@code Statement}) is - * to be considered a hint to EoD SQL. The underlying - * {@link MethodImplementation} may choose to ignore this value. + * fetch-size of the underlying {@code Statement} will be left alone. This + * value (like the {@code fetch-size} of a {@code Statement}) is to be + * considered a hint to EoD SQL. The underlying {@link MethodImplementation} + * may choose to ignore this value. *
- * + * * @since 2.1 * @return the suggested number of rows to be fetched at a time */ @@ -211,47 +207,75 @@ public @interface Select { /** *- * This attribute optionally allows you to populate one of the parameters - * of the annotated method instead of returning a new object. The value - * of this attribute must point to the index of the parameter to be - * populated. The first parameter is {@literal 1}, the second is - * {@literal 2} and so on. The parameter must be a valid data-object, - * and thus may not be a {@code Collection}; array; primitive; etc. - *
+ * This attribute optionally allows you to populate one of the parameters of + * the annotated method instead of returning a new object. The value of this + * attribute must point to the index of the parameter to be populated. The + * first parameter is {@literal 1}, the second is {@literal 2} and so on. + * The parameter must be a valid data-object, and thus may not be a + * {@code Collection}; array; primitive; etc. + *
+ ** This attribute can be used to either refresh data that has previously * been fetched from the database and/or to fetch new data into an object. - * This allows for a lazy-loading or fetch-group style of data loading - * from the database. - *
- * Finally: a method selecting into an existing object may only - * return {@code void} or the same type as the specified - * parameter (in which case the parameter itself will be returned). + * This allows for a lazy-loading or fetch-group style of data loading from + * the database. *
- * - * @return {@literal 0} by default to indicate that the method returns - * new objects instead of creating new ones + *+ * Finally: a method selecting into an existing object may only return + * {@code void} or the same type as the specified parameter (in which + * case the parameter itself will be returned). + *
+ * + * @return {@literal 0} by default to indicate that the method returns new + * objects instead of creating new ones * @since 2.1 */ int into() default 0; /** *
- * The custom type bindings to be used on the parameters of this query. The binding is matched
- * by position. The default type binding will be used for each parameter that has no
- * parameterBindings
entry or an entry that is of type {@link TypeMapper} itself.
+ * The custom type bindings to be used on the parameters of this query. The
+ * binding is matched by position. The default type binding will be used for
+ * each parameter that has no parameterBindings
entry or an
+ * entry that is of type {@link TypeMapper} itself.
*
- * A custom data object binding to be used on the result set of this query. The default value - * {@link NoDataObjectBinding} indicates that no custom binding is to be used. + * A custom data object binding to be used on the result set of this query. + * The default value {@link NoDataObjectBinding} indicates that no custom + * binding is to be used. *
* * @since 2.2 */ Class extends DataObjectBinding> resultSetBinding() default NoDataObjectBinding.class; + + /** + *+ * The query timeout (in seconds). The query is cancelled if it hasn't returned a result after + * this time. + *
+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeout() default 0; + + /** + *
+ * The index of the method parameter (starting with 1) which will be used as a query timeout
+ * (in seconds). The parameter type has to be an int
or Integer. If
+ * both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes
+ * precedence.
+ *
+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeoutParameterIndex() default 0; } diff --git a/eodsql/src/net/lemnik/eodsql/Update.java b/eodsql/src/net/lemnik/eodsql/Update.java index b20090b..0c07f8c 100644 --- a/eodsql/src/net/lemnik/eodsql/Update.java +++ b/eodsql/src/net/lemnik/eodsql/Update.java @@ -121,4 +121,28 @@ public @interface Update { * @since 2.2 */ Class extends TypeMapper>[] parameterBindings() default {}; + + /** + *
+ * The query timeout (in seconds). The update is cancelled if it hasn't returned a result after + * this time. + *
+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeout() default 0; + + /** + *
+ * The index of the method parameter (starting with 1) which will be used as a query timeout
+ * (in seconds). The parameter type has to be an int
or Integer.
+ * If both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes
+ * precedence.
+ *
+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeoutParameterIndex() default 0; } diff --git a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java index 7b884bb..6db9b1a 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java @@ -11,12 +11,14 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import net.lemnik.eodsql.Call; import net.lemnik.eodsql.DataIterator; import net.lemnik.eodsql.DataSet; import net.lemnik.eodsql.InvalidQueryException; import net.lemnik.eodsql.QueryTool; import net.lemnik.eodsql.Select; import net.lemnik.eodsql.TypeMapper; +import net.lemnik.eodsql.Update; import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.StatementResource; @@ -45,7 +47,7 @@ abstract class AbstractMethodImplementation protected PreparedStatement createPreparedStatement( final Context context) throws SQLException { - + final Connection connection = context.getResource(Connection.class).get(); int preferredResultSetType = wrapper.getPreferredResultSetType(); int resultSetType = getResultSetType(connection.getMetaData(), preferredResultSetType); @@ -55,19 +57,27 @@ abstract class AbstractMethodImplementation } else { final String dbName = connection.getMetaData().getDatabaseProductName(); throw new SQLException("This database (" + dbName - + ") does not support scrollable cursors, thus connected DataSets are not available."); + + ") does not support scrollable cursors, thus connected DataSets are not available."); } } final PreparedStatement statement = connection.prepareStatement( query.toString(), resultSetType, wrapper.getPreferredResultSetConcurrency()); + final int queryTimeout = getQueryTimeout(context); + if (queryTimeout > 0) { + try { + statement.setQueryTimeout(queryTimeout); + } catch (Exception ex) { + // Some JDBC drivers don't support setting a query timeout - keep quiet about it. + } + } context.setResource(new StatementResource(statement)); return statement; } - + private int getResultSetType(DatabaseMetaData dbMetaData, int preferredResultSetType) throws SQLException { if (dbMetaData.supportsResultSetType(preferredResultSetType)) { return preferredResultSetType; @@ -83,32 +93,58 @@ abstract class AbstractMethodImplementation } private boolean supportsScrolling(int resultSetType) { - return resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE + return resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE; } - + private boolean isDisconnected(final Context context) { - if (context.getAnnotation() instanceof Select + if (context.getAnnotation() instanceof Select && ((Select) context.getAnnotation()).disconnected()) { return true; } - if (DataSet.class.isAssignableFrom(context.getReturnType()) + if (DataSet.class.isAssignableFrom(context.getReturnType()) || DataIterator.class.isAssignableFrom(context.getReturnType())) { return false; } return true; } - protected void fillPreparedStatementParameters( - final Context> context, - final PreparedStatement statement) - throws SQLException { + private int getQueryTimeout(final Context context) { + if (context.getAnnotation() instanceof Select) { + final Select select = (Select) context.getAnnotation(); + if (select.timeoutParameterIndex() > 0 + && context.getParameters()[select.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[select.timeoutParameterIndex() - 1]; + } + return select.timeout(); + } + if (context.getAnnotation() instanceof Update) { + final Update update = (Update) context.getAnnotation(); + if (update.timeoutParameterIndex() > 0 + && context.getParameters()[update.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[update.timeoutParameterIndex() - 1]; + } + return update.timeout(); + } + if (context.getAnnotation() instanceof Call) { + final Call call = (Call) context.getAnnotation(); + if (call.timeoutParameterIndex() > 0 + && context.getParameters()[call.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[call.timeoutParameterIndex() - 1]; + } + return call.timeout(); + } + return 0; + } + + protected void fillPreparedStatementParameters(final Context> context, + final PreparedStatement statement) throws SQLException { final TypeMapper>[] mappers = getParameterMappers(); - for(int i = 0; i < mappers.length; i++) { + for (int i = 0; i < mappers.length; i++) { final Object parameter = query.getParameter(context, i); - + @SuppressWarnings("unchecked") final TypeMapper