From c74910e21cba25e959b059581bd2c89e8fc89377 Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 3 Aug 2011 00:38:43 +0200 Subject: [PATCH 12/12] Add interface DynamicQuery that can be used as a mix-in to add support for dynamically created queries and statements to any query interface. Also adds a demo program for using the DynamicQuery interface. Add interfaces DynamicBaseQuery and DynamicTransactionQuery. --- .../eodsql/demo/DemoUsingDynamicQueries.java | 168 ++++++++++++++ eodsql/docs/documentation/tutorial.html | 3 + eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java | 8 + eodsql/src/net/lemnik/eodsql/DynamicQuery.java | 230 ++++++++++++++++++++ .../net/lemnik/eodsql/DynamicTransactionQuery.java | 10 + .../src/net/lemnik/eodsql/impl/BaseQueryImpl.java | 156 ++++++++++++-- .../impl/BatchUpdateMethodImplementation.java | 88 +++++---- .../eodsql/impl/DynamicParameterFactory.java | 27 +++ .../eodsql/impl/MethodImplementationKey.java | 69 ++++++ .../eodsql/impl/SelectDynamicParameterFactory.java | 158 ++++++++++++++ .../eodsql/impl/SelectMethodImplementation.java | 8 +- .../eodsql/impl/UpdateDynamicParameterFactory.java | 145 ++++++++++++ .../eodsql/impl/UpdateMethodImplementation.java | 21 ++- eodsql/test/net/lemnik/eodsql/MapTestObject.java | 14 +- 14 files changed, 1034 insertions(+), 71 deletions(-) create mode 100644 eodsql/docs/documentation/examples/net/lemnik/eodsql/demo/DemoUsingDynamicQueries.java create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java diff --git a/eodsql/docs/documentation/examples/net/lemnik/eodsql/demo/DemoUsingDynamicQueries.java b/eodsql/docs/documentation/examples/net/lemnik/eodsql/demo/DemoUsingDynamicQueries.java new file mode 100644 index 0000000..18b645c --- /dev/null +++ b/eodsql/docs/documentation/examples/net/lemnik/eodsql/demo/DemoUsingDynamicQueries.java @@ -0,0 +1,168 @@ +package net.lemnik.eodsql.demo; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.lemnik.eodsql.BaseQuery; +import net.lemnik.eodsql.DataSet; +import net.lemnik.eodsql.DynamicQuery; +import net.lemnik.eodsql.QueryTool; + +import org.apache.commons.dbcp.BasicDataSource; + +/** + * A simple demo program on how to add, remove and modify rows. + * It needs to have on the classpath: + * + * + * @author Bernd Rinn + */ +public class DemoUsingDynamicQueries { + + // @formatter:off + + /** + * A BaseQuery offering dynamic queries only. + */ + interface Query extends BaseQuery, DynamicQuery {} + + private static void setupDatabase(Query query) { + query.update( + "CREATE TABLE addresses (" + + "addr_id identity PRIMARY KEY, " + + "city varchar(64), " + + "zip integer, " + "street varchar(64) )"); + query.update( + "CREATE TABLE persons (" + + "pers_id identity PRIMARY KEY, " + + "last_name varchar(64), " + + "first_name varchar(64), " + + "date_of_birth date DEFAULT NULL, " + + "addr_id bigint DEFAULT NULL REFERENCES addresses(addr_id) " + + " )"); + // This is the easiest way to insert a row and get the auto-generated key. + long addrIdJohn = query.insert( + "INSERT INTO addresses (city, zip, street) " + + "VALUES (?{2}, ?{3}, ?{1})", + "Am Bach 1", "Hinter den Sieben Bergen", 9999); + long addrIdSara = query.insert( + "INSERT INTO addresses (city, zip, street) " + + "VALUES (?{2}, ?{3}, ?{1})", + "Hinter Pfui Teufel 1", "Im Tal", 8888); + // If you don't use the return value, it doesn't matter whether you use update() or insert(). + query.update( + "INSERT INTO persons (last_name, first_name, date_of_birth, addr_id) " + + "VALUES (?{2}, ?{1}, ?{3}::date, ?{4})", + "Poor Little", "Goose", "2008-01-02", addrIdJohn); + query.update( + "INSERT INTO persons (last_name, first_name, date_of_birth, addr_id) " + + "VALUES (?{2}, ?{1}, ?{3}::date, ?{4})", + "John", "Fox", "1981-05-23", addrIdJohn); + query.update( + "INSERT INTO persons (last_name, first_name, date_of_birth, addr_id) " + + "VALUES (?{2}, ?{1}, ?{3}::date, ?{4})", + "Sara", "Bunny", "1983-02-17", addrIdSara); + } + + private static void printPersons(Query query) { + // + // This is the easiest way to iterate over a result set. + // + for (Map row : query.select("SELECT * FROM persons p " + + "LEFT OUTER JOIN addresses a ON p.addr_id = a.addr_id")) { + System.out.println( + row.get("pers_id") + + ": " + row.get("first_name") + + " " + row.get("last_name") + + " *" + row.get("date_of_birth") + + " " + row.get("street") + + ", " + row.get("zip") + + " " + row.get("city")); + } + } + + public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException { + + // + // Setup the data source and the database. + // + BasicDataSource source = new BasicDataSource(); + // This line is only needed with JRE 5 + source.setDriverClassName("org.h2.Driver"); + source.setUrl("jdbc:h2:mem:testdb"); + source.setUsername("sa"); + Query query = QueryTool.getQuery(source, Query.class); + + setupDatabase(query); + + System.out.println("Initially"); + printPersons(query); + + // + // 1. method to change the database: update statement + // (Note how we refer to positional parameters.) + // + query.update( + "UPDATE persons SET addr_id = " + + "(SELECT addr_id FROM persons " + + " WHERE last_name = ?{4} AND first_name = ?{3}) " + + "WHERE last_name = ?{2} AND first_name = ?{1}", + "Sara", "Bunny", "John", "Fox"); + + System.out.println("\nAfter Sara Bunny moved..."); + printPersons(query); + + // + // 2. method to change the database: connected DataSet + // + DataSet> rows = query.select("SELECT * FROM persons"); + Iterator> it = rows.iterator(); + while (it.hasNext()) { + Map r = it.next(); + if (r.get("last_name").equals("Goose")) { + // John eats Poor Little Goose + it.remove(); + break; + } + } + System.out.println("\nAfter John ate Poor Litte Goose..."); + printPersons(query); + + for (int i = 0; i < rows.size(); ++i) { + Map r = rows.get(i); + if (r.get("last_name").equals("Bunny")) { + // It might be tempting to write: + // rows.get(i).put("last_name", "Fox-Bunny") + // However, this does NOT work! You need to call rows.set() to change the database. + r.put("last_name", "Fox-Bunny"); + rows.set(i, r); + break; + } + } + System.out.println("\nAfter John and Sara married..."); + printPersons(query); + + Map newRow = new HashMap(); + newRow.put("last_name", "Bunny-Fox"); + newRow.put("first_name", "Young"); + newRow.put("date_of_birth", new Date()); + newRow.put("addr_id", + query.select(long.class, + "SELECT addr_id FROM persons WHERE last_name = 'Fox' and first_name = 'John'" + ).get(0)); + // Young Bunny-Fox is born + rows.add(newRow); + + System.out.println("\nFinally..."); + printPersons(query); + } +} diff --git a/eodsql/docs/documentation/tutorial.html b/eodsql/docs/documentation/tutorial.html index 0d09900..bf9b522 100644 --- a/eodsql/docs/documentation/tutorial.html +++ b/eodsql/docs/documentation/tutorial.html @@ -444,6 +444,9 @@
  • Example using predefined BaseQuery methods.
  • +
  • + Example using DynamicQuery methods. +
  • Where To From Here?

    diff --git a/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java new file mode 100644 index 0000000..ee8b4ed --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java @@ -0,0 +1,8 @@ +package net.lemnik.eodsql; + +/** + * A version of {@link BaseQuery} that includes the query capabilities of {@link DynamicQuery}. + * + * @author Bernd Rinn + */ +public interface DynamicBaseQuery extends BaseQuery, DynamicQuery { } diff --git a/eodsql/src/net/lemnik/eodsql/DynamicQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicQuery.java new file mode 100644 index 0000000..858225e --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicQuery.java @@ -0,0 +1,230 @@ +package net.lemnik.eodsql; + +import java.util.Map; + +/** + * This interface can be used as a mix-in for SQL queries and statements created + * dynamically at runtime. Usage is like this: + * + *
    + * 
    + * public interface MyQuery extends BaseQuery, DynamicQuery {
    + *     @Select(sql = "SELECT * FROM persons")
    + *     public DataSet<Address> list();
    + * 
    + *     @Select(sql = "SELECT * FROM persons")
    + *     public DataSet<Map<String,Object>> listMap();
    + * }
    + * ...
    + * MyQuery query = QueryTool.getQuery(conn, MyQuery.class);
    + * DataSet<Map<String,Object>> data = query.select("SELECT * FROM persons WHERE last_name = 'Smith'");
    + * 
    + * 
    + *

    + * DynamicQuery can be mixed-in to {@link BaseQuery} and {@link TransactionQuery}. + * The interfaces {@link DynamicBaseQuery} and {@link DynamicTransactionQuery} can be used for that. + * + * @author Bernd Rinn + */ +public interface DynamicQuery { + + /** + * Performs a SQL query. The returned DataSet is connected and + * updateable. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet> select(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. + * + * @param readOnly + * If true, the returned DataSet will + * not be updateable. + * @param disconnected + * If true, the returned DataSet will + * be disconnected. + * @param fetchSize + * The fetchSize given to the JDBC driver. + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet> select(final boolean readOnly, + final boolean disconnected, final int fetchSize, + final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Performs a SQL query. The returned DataSet is connected and + * updateable. + * + * @param type + * The Java type to return one rows in the returned + * DataSet. + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet select(final Class type, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. + * + * @param type + * The Java type to return one rows in the returned + * DataSet. + * @param readOnly + * If true, the returned DataSet will + * not be updateable. + * @param disconnected + * If true, the returned DataSet will + * be disconnected. + * @param fetchSize + * The fetchSize given to the JDBC driver. + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet select(final Class type, final boolean readOnly, + final boolean disconnected, final int fetchSize, + final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The number of rows updated by the SQL statement, or -1 if not + * applicable. Note: Not all JDBC drivers support this + * cleanly. + */ + int update(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. At least + * one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same + * size. + * + * @return The number of rows updated by the SQL statement, or -1 if not + * applicable. Note: Not all JDBC drivers support this + * cleanly. + */ + int batchUpdate(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement. Supposed to be used for INSERT statements with + * an automatically generated integer key. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The automatically generated key. Note: Not all JDBC + * drivers support this cleanly. + */ + long insert(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement. Supposed to be used for INSERT statements with + * one or more automatically generated keys. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. + * + * @return The automatically generated keys. Note: Not all JDBC + * drivers support this cleanly and it is very driver-dependent what + * keys are present in the returned map. + */ + Map insertMultiKeys(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * Supposed to be used for INSERT statements with an automatically generated + * integer key. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. At least + * one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same + * size. + * + * @return The automatically generated key for each element of the batch. + * Note: Not all JDBC drivers support this cleanly. + */ + long[] batchInsert(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * Supposed to be used for INSERT statements with one or more automatically + * generated keys. + * + * @param query + * The SQL query template. + * @param parameters + * The parameters to fill into the SQL query template. At least + * one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same + * size. + * + * @return The automatically generated keys for each element of the batch. + * Note: Not all JDBC drivers support this cleanly and it is + * very driver-dependent what keys are present in the returned map. + */ + Map[] batchInsertMultiKeys(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; +} diff --git a/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java new file mode 100644 index 0000000..73ec8d8 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java @@ -0,0 +1,10 @@ +package net.lemnik.eodsql; + +/** + * A version of {@link TransactionQuery} that includes the query capabilities of + * {@link DynamicQuery}. + * + * @author Bernd Rinn + */ +public interface DynamicTransactionQuery extends TransactionQuery, DynamicQuery { +} diff --git a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java index a0a0a6e..b66e949 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java @@ -1,35 +1,35 @@ package net.lemnik.eodsql.impl; import java.lang.annotation.Annotation; - -import java.lang.reflect.Method; import java.lang.reflect.InvocationHandler; - +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.sql.Connection; import java.sql.SQLException; - +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.HashSet; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Collections; - import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import net.lemnik.eodsql.BaseQuery; +import net.lemnik.eodsql.DataSet; +import net.lemnik.eodsql.DynamicQuery; import net.lemnik.eodsql.EoDException; -import net.lemnik.eodsql.QueryTool; +import net.lemnik.eodsql.GeneratedKeys; import net.lemnik.eodsql.InvalidQueryException; - +import net.lemnik.eodsql.QueryTool; +import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.MethodImplementationFactory; - -import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.Resource; +import net.lemnik.eodsql.spi.util.MapDataObjectBinding; /** * Created on 2008/06/26 @@ -37,9 +37,12 @@ import net.lemnik.eodsql.spi.Resource; */ class BaseQueryImpl implements InvocationHandler { - protected Map methods = new HashMap(); + protected final Map methods = new HashMap(); + + protected final Map> + dynamicMethodImplCache = new HashMap>(); - protected ConnectionSource connectionSource; + protected final ConnectionSource connectionSource; BaseQueryImpl( final ConnectionSource connectionSource, @@ -121,6 +124,10 @@ class BaseQueryImpl implements InvocationHandler { final Class baseInterface) { final Set parents = getParentInterfaces(baseInterface); + if (DynamicQuery.class.isAssignableFrom(clazz)) { + parents.add(DynamicQuery.class); + addDynamicQueryMethods(); + } for(final Method method : clazz.getMethods()) { if(parents.contains(method.getDeclaringClass())) { @@ -138,7 +145,47 @@ class BaseQueryImpl implements InvocationHandler { } catch(SecurityException securityException) { } } - + + private void addDynamicQueryMethods() { + try { + methods.put(DynamicQuery.class.getMethod("select", String.class, Object[].class), + new DynamicMethodImpl(new SelectDynamicParameterFactory( + MapDataObjectBinding.getStringObjectMapObjectType(), -1, 0, 0, false, false))); + methods.put(DynamicQuery.class.getMethod("select", Boolean.TYPE, Boolean.TYPE, + Integer.TYPE, String.class, Object[].class), + new DynamicMethodImpl(new SelectDynamicParameterFactory( + MapDataObjectBinding.getStringObjectMapObjectType(), -1, 3, 2, 0, 1))); + methods.put(DynamicQuery.class.getMethod("select", Class.class, String.class, Object[].class), + new DynamicMethodImpl(new SelectDynamicParameterFactory(null, 0, 1, 0, false, false))); + methods.put(DynamicQuery.class.getMethod("select", Class.class, Boolean.TYPE, Boolean.TYPE, + Integer.TYPE, String.class, Object[].class), + new DynamicMethodImpl(new SelectDynamicParameterFactory(null, 0, 4, 3, 1, 2))); + + methods.put(DynamicQuery.class.getMethod("update", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + Integer.TYPE, -1, true, 0, false, GeneratedKeys.NO_KEYS_RETURNED))); + methods.put(DynamicQuery.class.getMethod("batchUpdate", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + Integer.TYPE, -1, true, 0, true, GeneratedKeys.NO_KEYS_RETURNED))); + + methods.put(DynamicQuery.class.getMethod("insert", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + Long.TYPE, -1, true, 0, false, GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN))); + methods.put(DynamicQuery.class.getMethod("insertMultiKeys", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + MapDataObjectBinding.getStringObjectMapObjectType(), -1, true, 0, false, + GeneratedKeys.RETURNED_KEYS_DRIVER_DEFINED))); + methods.put(DynamicQuery.class.getMethod("batchInsert", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + long[].class, -1, true, 0, true, GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN))); + methods.put(DynamicQuery.class.getMethod("batchInsertMultiKeys", String.class, Object[].class), + new DynamicMethodImpl(new UpdateDynamicParameterFactory( + Map[].class, -1, true, 0, true, GeneratedKeys.RETURNED_KEYS_DRIVER_DEFINED))); + } catch(NoSuchMethodException noSuchMethodException) { + } catch(SecurityException securityException) { + } + } + public Object invoke( final Object proxy, final Method method, @@ -236,6 +283,83 @@ class BaseQueryImpl implements InvocationHandler { } } + class DynamicMethodImpl implements SQLCallable { + + private final DynamicParameterFactory factory; + + public DynamicMethodImpl(final DynamicParameterFactory factory) { + this.factory = factory; + } + + private Type createReturnType(final Class baseReturnType, final boolean singleRow) { + if (singleRow) { + return baseReturnType; + } else { + return new ParameterizedType() { + private final Class[] typeArgs = new Class[] { baseReturnType }; + + public Type[] getActualTypeArguments() { + return typeArgs; + } + + public Type getRawType() { + return DataSet.class; + } + + public Type getOwnerType() { + return null; + } + }; + } + } + + @SuppressWarnings("unchecked") + public Object invoke( + final Method method, + final Object[] args) + throws Throwable { + + final Annotation annotation = factory.createAnnotation(args); + final Object[] queryParams = (Object[]) args[args.length - 1]; + final Context context = createContext(annotation, queryParams); + + final Resource connection = + new ConnectionSourceConnectionResource(connectionSource); + + context.setResource(connection); + + try { + final MethodImplementationKey key = new MethodImplementationKey(method, annotation, + getQueryParamTypes(queryParams), factory.createBaseReturnType(args)); + MethodImplementation impl = dynamicMethodImplCache.get(key); + if (impl == null) { + impl = factory.createMethodImplementation(key.method, key.annotation, + key.queryParamTypes, createReturnType(key.baseReturnType, factory.isSingleRow()), args); + dynamicMethodImplCache.put(key, impl); + } + impl.invoke(context); + return context.getReturnValue(); + } finally { + if(context.isAutoclose()) { + context.close(); + } + } + } + + private Class[] getQueryParamTypes(final Object[] queryParams) { + final Class[] queryParamTypes = new Class[queryParams.length]; + for (int i = 0; i < queryParamTypes.length; i++) { + queryParamTypes[i] = queryParams[i].getClass(); + } + return queryParamTypes; + } + + public String getSQL(Method method, Object[] args) { + return factory.getSql(args); + } + + } + static interface ConnectionSource { public Connection getConnection() throws SQLException; diff --git a/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java index 05c8e32..85ebca4 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java @@ -25,7 +25,7 @@ import net.lemnik.eodsql.spi.Context; /** * Implementation of {@link Update} for batch updates. - * + * * @author Bernd Rinn */ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { @@ -36,7 +36,20 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { BatchUpdateMethodImplementation(final Method method) throws ParseException { super(method); - viewFactories = createParameterViewFactories(method); + viewFactories = createParameterViewFactories(method.getParameterTypes()); + firstIndexOfFiniteCollection = findFirstIndexOfFiniteCollection(viewFactories); + if (firstIndexOfFiniteCollection == -1) { + throw new RuntimeException("Method '" + method.getDeclaringClass().getSimpleName() + + "." + method.getName() + + "' supposed to do batch update, but has no batch parameter."); + } + } + + BatchUpdateMethodImplementation(final Update update, + final Class[] parameterTypes, final Class[] elementTypes, + final Type returnType, final Method method) throws ParseException { + super(update, elementTypes, returnType); + viewFactories = createParameterViewFactories(parameterTypes); firstIndexOfFiniteCollection = findFirstIndexOfFiniteCollection(viewFactories); if (firstIndexOfFiniteCollection == -1) { throw new RuntimeException("Method '" + method.getDeclaringClass().getSimpleName() @@ -51,14 +64,14 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { int size = -1; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { final Collection v = viewFactories[i].createView(parameters[i]); final int parameterSize = v.size(); - if(parameterSize != -1) { - if(size == -1) { + if (parameterSize != -1) { + if (size == -1) { size = parameterSize; - } else if(size != parameterSize) { + } else if (size != parameterSize) { throw new EoDException("Batch parameter is mismatched " + "in size: " + i); } @@ -73,8 +86,8 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private Iterator> iterate(final Context ctx) { final Iterator[] views = createParameterViews(ctx.getParameters()); - if(views.length == 0) { - return Collections.>emptyList().iterator(); + if (views.length == 0) { + return Collections.> emptyList().iterator(); } else { return new Iterator>() { @@ -95,7 +108,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } public Context next() { - for(int i = 0; i < paramCount; i++) { + for (int i = 0; i < paramCount; i++) { parameters[i] = views[i].next(); } @@ -113,9 +126,9 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private static ParameterViewFactory createParameterViewFactory( final Class type) { - if(type.isArray()) { + if (type.isArray()) { return new ArrayViewFactory(); - } else if(Collection.class.isAssignableFrom(type)) { + } else if (Collection.class.isAssignableFrom(type)) { return new CollectionViewFactory(); } else { return new SimpleParameterIteratorFactory(); @@ -123,14 +136,13 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } static ParameterViewFactory[] createParameterViewFactories( - final Method method) { + final Class[] parameterTypes) { - final Class[] parameterTypes = method.getParameterTypes(); final int parameterCount = parameterTypes.length; final ParameterViewFactory[] factories = new ParameterViewFactory[parameterCount]; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { factories[i] = createParameterViewFactory(parameterTypes[i]); } @@ -154,32 +166,32 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { final int parameterCount = genericTypes.length; final Class[] classes = new Class[parameterCount]; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { final Type type = genericTypes[i]; Class clazz = null; - if(type instanceof Class) { - clazz = (Class)type; + if (type instanceof Class) { + clazz = (Class) type; - if(clazz.isArray()) { + if (clazz.isArray()) { clazz = clazz.getComponentType(); } - } else if(type instanceof ParameterizedType) { - final ParameterizedType ptype = (ParameterizedType)type; + } else if (type instanceof ParameterizedType) { + final ParameterizedType ptype = (ParameterizedType) type; - if(ptype.getRawType() instanceof Class) { - final Class raw = (Class)ptype.getRawType(); + if (ptype.getRawType() instanceof Class) { + final Class raw = (Class) ptype.getRawType(); - if(Iterable.class.isAssignableFrom(raw)) { + if (Iterable.class.isAssignableFrom(raw)) { final Type[] args = ptype.getActualTypeArguments(); - if(args.length != 1) { + if (args.length != 1) { throw new InvalidQueryException( "A generic Iterable may only have " + "one type argument: " + ptype, method); - } else if(args[0] instanceof Class) { - clazz = (Class)args[0]; + } else if (args[0] instanceof Class) { + clazz = (Class) args[0]; } else { throw new InvalidQueryException( "A generic Iterable must have a " + @@ -188,8 +200,8 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } } else { clazz = raw; - - if(clazz.isArray()) { + + if (clazz.isArray()) { clazz = clazz.getComponentType(); } } @@ -199,12 +211,12 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { "class type: " + type, method); } - } else if(type instanceof GenericArrayType) { - final GenericArrayType gtype = (GenericArrayType)type; + } else if (type instanceof GenericArrayType) { + final GenericArrayType gtype = (GenericArrayType) type; final Type component = gtype.getGenericComponentType(); - if(component instanceof Class) { - clazz = (Class)component; + if (component instanceof Class) { + clazz = (Class) component; } else { throw new InvalidQueryException( "Arrays must have a concrete " + @@ -231,7 +243,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { final Iterator> iterator = iterate(context); - while(iterator.hasNext()) { + while (iterator.hasNext()) { final Context ctx = iterator.next(); fillPreparedStatementParameters(ctx, statement); @@ -245,7 +257,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { * Factory for creating an iterator for a batch update parameter. This iterator * will deliver all the values that will be used for that parameter in the batch * update. - * + * * @author Bernd Rinn */ interface ParameterViewFactory { @@ -254,9 +266,9 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { /** * Creates an iterator for the given batch update parameter. - * + * * @param parameter The parameter of the batch update method to get the iterator for. - * + * * @return a new iterator for the parameter. */ Collection createView(Object parameter); @@ -297,7 +309,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } public Object next() { - if(!hasNext()) { + if (!hasNext()) { throw new NoSuchElementException(); } @@ -322,7 +334,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private static class CollectionViewFactory implements ParameterViewFactory { public Collection createView(final Object parameter) { - return ((Collection)parameter); + return ((Collection) parameter); } public boolean isFiniteCollection() diff --git a/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java new file mode 100644 index 0000000..35b6e31 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java @@ -0,0 +1,27 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.text.ParseException; + +import net.lemnik.eodsql.spi.MethodImplementation; + +/** + * A factory for query method implementation parameters from dynamic invocation arguments. + * + * @author Bernd Rinn + */ +interface DynamicParameterFactory { + Annotation createAnnotation(final Object[] args); + + Class createBaseReturnType(final Object[] args); + + boolean isSingleRow(); + + MethodImplementation createMethodImplementation(Method method, + Annotation annotation, Class[] queryParamTypes, Type returnType, Object[] args) + throws ParseException; + + String getSql(Object[] args); +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java b/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java new file mode 100644 index 0000000..0d569ba --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java @@ -0,0 +1,69 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * A key class of method implementations used for caching. + * + * @author Bernd Rinn + */ +class MethodImplementationKey { + final Method method; + final Annotation annotation; + final Class[] queryParamTypes; + final Class baseReturnType; + + MethodImplementationKey(Method method, + Annotation annotation, Class[] queryParamTypes, Class baseReturnType) { + this.method = method; + this.annotation = annotation; + this.queryParamTypes = queryParamTypes; + this.baseReturnType = baseReturnType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((annotation == null) ? 0 : annotation.hashCode()); + result = prime * result + + ((method == null) ? 0 : method.hashCode()); + result = prime * result + Arrays.hashCode(queryParamTypes); + result = prime * result + + ((baseReturnType == null) ? 0 : baseReturnType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MethodImplementationKey other = (MethodImplementationKey) obj; + if (annotation == null) { + if (other.annotation != null) + return false; + } else if (!annotation.equals(other.annotation)) + return false; + if (method == null) { + if (other.method != null) + return false; + } else if (!method.equals(other.method)) + return false; + if (!Arrays.equals(queryParamTypes, other.queryParamTypes)) + return false; + if (baseReturnType == null) { + if (other.baseReturnType != null) + return false; + } else if (!baseReturnType.equals(other.baseReturnType)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java new file mode 100644 index 0000000..11bbd75 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java @@ -0,0 +1,158 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.text.ParseException; + +import net.lemnik.eodsql.ArrayDataSetCache; +import net.lemnik.eodsql.DataSetCache; +import net.lemnik.eodsql.Select; +import net.lemnik.eodsql.TypeMapper; +import net.lemnik.eodsql.spi.MethodImplementation; +import net.lemnik.eodsql.spi.util.DataObjectBinding; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; + +/** + * A {@link DynamicParameterFactory} for SELECT query method invocations. + * + * @author Bernd Rinn + */ +class SelectDynamicParameterFactory implements DynamicParameterFactory { + + private final Class baseReturnType; + + private final int fetchSize; + + private final boolean disconnected; + + private final boolean readOnly; + + private final int sqlParamPos; + + private final int fetchSizeParamPos; + + private final int readOnlyParamPos; + + private final int disconnectedParamPos; + + private final int baseReturnTypeParamPos; + + SelectDynamicParameterFactory(final Class baseReturnType, final int baseReturnTypeParamPos, + final int sqlParamPos, final int fetchSizeParamPos, + final int readOnlyParamPos, final int disconnectedParamPos) { + this.baseReturnType = baseReturnType; + this.fetchSize = 0; + this.disconnected = false; + this.readOnly = false; + this.baseReturnTypeParamPos = baseReturnTypeParamPos; + this.sqlParamPos = sqlParamPos; + this.fetchSizeParamPos = fetchSizeParamPos; + this.readOnlyParamPos = readOnlyParamPos; + this.disconnectedParamPos = disconnectedParamPos; + } + + SelectDynamicParameterFactory(final Class baseReturnType, final int baseReturnTypeParamPos, + final int sqlParamPos, final int fetchSize, final boolean disconnected, final boolean readOnly) { + this.baseReturnType = baseReturnType; + this.fetchSize = fetchSize; + this.disconnected = disconnected; + this.readOnly = readOnly; + this.baseReturnTypeParamPos = baseReturnTypeParamPos; + this.sqlParamPos = sqlParamPos; + this.fetchSizeParamPos = -1; + this.readOnlyParamPos = -1; + this.disconnectedParamPos = -1; + } + + public Annotation createAnnotation(final Object[] args) { + return new Select() { + + public String value() { + return ""; + } + + public String sql() { + return getSql(args); + } + + public boolean disconnected() { + return (disconnectedParamPos < 0) ? disconnected : ((Boolean) args[disconnectedParamPos]); + } + + public boolean rubberstamp() { + return false; + } + + public boolean readOnly() { + return (readOnlyParamPos < 0) ? readOnly : ((Boolean) args[readOnlyParamPos]); + } + + public Class cache() { + return ArrayDataSetCache.class; + } + + public Class annotationType() { + return Select.class; + } + + public int fetchSize() { + return (fetchSizeParamPos < 0) ? fetchSize : ((Integer) args[fetchSizeParamPos]); + } + + public int into() { + return 0; + } + + public Class resultSetBinding() + { + return NoDataObjectBinding.class; + } + + @SuppressWarnings("unchecked") + public Class[] parameterBindings() + { + return new Class[0]; + } + + @Override + public int hashCode() { + int hash = "Select".hashCode(); + hash = 37 * hash + sql().hashCode(); + hash = 37 * hash + (disconnected() ? 1 : 0); + hash = 37 * hash + (readOnly() ? 1 : 0); + hash = 37 * hash + fetchSize(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Select == false) { + return false; + } + Select other = (Select) obj; + return sql().equals(other.sql()) && disconnected() == other.disconnected() + && readOnly() == other.readOnly() && fetchSize() == other.fetchSize(); + } + }; + + } + + public String getSql(Object[] args) { + return (String) args[sqlParamPos]; + } + + public MethodImplementation createMethodImplementation(Method method, + Annotation annotation, Class[] queryParamTypes, Type returnType, Object[] args) throws ParseException { + return new SelectMethodImplementation((Select) annotation, queryParamTypes, returnType); + } + + public Class createBaseReturnType(final Object[] args) { + return (baseReturnTypeParamPos < 0) ? baseReturnType : (Class) args[baseReturnTypeParamPos]; + } + + public boolean isSingleRow() { + return false; + } + +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java index 86f7917..c6589bb 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java @@ -37,22 +37,24 @@ class SelectMethodImplementation extends AbstractMethodImplementation