-/*\r
- * Copyright 2004-2011 H2 Group.\r
- * Copyright 2011 James Moger.\r
- * Copyright 2012 Frédéric Gaillard.\r
- * Copyright 2012 Alex Telepov.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.iciql;\r
-\r
-import java.sql.Connection;\r
-import java.sql.DatabaseMetaData;\r
-import java.sql.PreparedStatement;\r
-import java.sql.ResultSet;\r
-import java.sql.SQLException;\r
-import java.sql.SQLFeatureNotSupportedException;\r
-import java.sql.Savepoint;\r
-import java.sql.Statement;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import javax.sql.DataSource;\r
-\r
-import com.iciql.DbUpgrader.DefaultDbUpgrader;\r
-import com.iciql.Iciql.IQTable;\r
-import com.iciql.Iciql.IQVersion;\r
-import com.iciql.Iciql.IQView;\r
-import com.iciql.util.IciqlLogger;\r
-import com.iciql.util.JdbcUtils;\r
-import com.iciql.util.StringUtils;\r
-import com.iciql.util.Utils;\r
-import com.iciql.util.WeakIdentityHashMap;\r
-\r
-/**\r
- * This class represents a connection to a database.\r
- */\r
-\r
-public class Db implements AutoCloseable {\r
-\r
- /**\r
- * This map It holds unique tokens that are generated by functions such as\r
- * Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It\r
- * doesn't actually hold column tokens, as those are bound to the query\r
- * itself.\r
- */\r
- private static final Map<Object, Token> TOKENS;\r
-\r
- private static final Map<String, Class<? extends SQLDialect>> DIALECTS;\r
-\r
- private final Connection conn;\r
- private final Map<Class<?>, TableDefinition<?>> classMap = Collections\r
- .synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>());\r
- private final SQLDialect dialect;\r
- private DbUpgrader dbUpgrader = new DefaultDbUpgrader();\r
- private final Set<Class<?>> upgradeChecked = Collections.synchronizedSet(new HashSet<Class<?>>());\r
-\r
- private boolean skipCreate;\r
- private boolean autoSavePoint = true;\r
-\r
- static {\r
- TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap<Object, Token>());\r
- DIALECTS = Collections.synchronizedMap(new HashMap<String, Class<? extends SQLDialect>>());\r
- // can register by...\r
- // 1. Connection class name\r
- // 2. DatabaseMetaData.getDatabaseProductName()\r
- DIALECTS.put("Apache Derby", SQLDialectDerby.class);\r
- DIALECTS.put("H2", SQLDialectH2.class);\r
- DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class);\r
- DIALECTS.put("MySQL", SQLDialectMySQL.class);\r
- DIALECTS.put("PostgreSQL", SQLDialectPostgreSQL.class);\r
- DIALECTS.put("Microsoft SQL Server", SQLDialectMSSQL.class);\r
- DIALECTS.put("SQLite", SQLDialectSQLite.class);\r
- }\r
-\r
- private Db(Connection conn) {\r
- this.conn = conn;\r
- String databaseName = null;\r
- try {\r
- DatabaseMetaData data = conn.getMetaData();\r
- databaseName = data.getDatabaseProductName();\r
- } catch (SQLException s) {\r
- throw new IciqlException(s, "failed to retrieve database metadata!");\r
- }\r
- dialect = getDialect(databaseName, conn.getClass().getName());\r
- dialect.configureDialect(this);\r
- }\r
-\r
- /**\r
- * Register a new/custom dialect class. You can use this method to replace\r
- * any existing dialect or to add a new one.\r
- *\r
- * @param token\r
- * the fully qualified name of the connection class or the\r
- * expected result of DatabaseMetaData.getDatabaseProductName()\r
- * @param dialectClass\r
- * the dialect class to register\r
- */\r
- public static void registerDialect(String token, Class<? extends SQLDialect> dialectClass) {\r
- DIALECTS.put(token, dialectClass);\r
- }\r
-\r
- SQLDialect getDialect(String databaseName, String className) {\r
- Class<? extends SQLDialect> dialectClass = null;\r
- if (DIALECTS.containsKey(className)) {\r
- // dialect registered by connection class name\r
- dialectClass = DIALECTS.get(className);\r
- } else if (DIALECTS.containsKey(databaseName)) {\r
- // dialect registered by database name\r
- dialectClass = DIALECTS.get(databaseName);\r
- } else {\r
- // did not find a match, use default\r
- dialectClass = SQLDialectDefault.class;\r
- }\r
- return instance(dialectClass);\r
- }\r
-\r
- static <X> X registerToken(X x, Token token) {\r
- TOKENS.put(x, token);\r
- return x;\r
- }\r
-\r
- static Token getToken(Object x) {\r
- return TOKENS.get(x);\r
- }\r
-\r
- static <T> T instance(Class<T> clazz) {\r
- try {\r
- return clazz.newInstance();\r
- } catch (Exception e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- public static Db open(String url) {\r
- try {\r
- Connection conn = JdbcUtils.getConnection(null, url, null, null);\r
- return new Db(conn);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- public static Db open(String url, String user, String password) {\r
- try {\r
- Connection conn = JdbcUtils.getConnection(null, url, user, password);\r
- return new Db(conn);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- public static Db open(String url, String user, char[] password) {\r
- try {\r
- Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password));\r
- return new Db(conn);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- /**\r
- * Create a new database instance using a data source. This method is fast,\r
- * so that you can always call open() / close() on usage.\r
- *\r
- * @param ds\r
- * the data source\r
- * @return the database instance.\r
- */\r
- public static Db open(DataSource ds) {\r
- try {\r
- return new Db(ds.getConnection());\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- public static Db open(Connection conn) {\r
- return new Db(conn);\r
- }\r
-\r
-\r
-\r
- /**\r
- * Convenience function to avoid import statements in application code.\r
- */\r
- public void activateConsoleLogger() {\r
- IciqlLogger.activateConsoleLogger();\r
- }\r
-\r
- /**\r
- * Convenience function to avoid import statements in application code.\r
- */\r
- public void deactivateConsoleLogger() {\r
- IciqlLogger.deactivateConsoleLogger();\r
- }\r
-\r
- public <T> void insert(T t) {\r
- Class<?> clazz = t.getClass();\r
- long rc = define(clazz).createIfRequired(this).insert(this, t, false);\r
- if (rc == 0) {\r
- throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);\r
- }\r
- }\r
-\r
- public <T> long insertAndGetKey(T t) {\r
- Class<?> clazz = t.getClass();\r
- return define(clazz).createIfRequired(this).insert(this, t, true);\r
- }\r
-\r
- /**\r
- * Merge INSERTS if the record does not exist or UPDATES the record if it\r
- * does exist. Not all databases support MERGE and the syntax varies with\r
- * the database.\r
- *\r
- * If the database does not support a MERGE syntax the dialect can try to\r
- * simulate a merge by implementing:\r
- * <p>\r
- * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0)\r
- * <p>\r
- * iciql will check the affected row count returned by the internal merge\r
- * method and if the affected row count = 0, it will issue an update.\r
- * <p>\r
- * See the Derby dialect for an implementation of this technique.\r
- * <p>\r
- * If the dialect does not support merge an IciqlException will be thrown.\r
- *\r
- * @param t\r
- */\r
- public <T> void merge(T t) {\r
- Class<?> clazz = t.getClass();\r
- TableDefinition<?> def = define(clazz).createIfRequired(this);\r
- int rc = def.merge(this, t);\r
- if (rc == 0) {\r
- rc = def.update(this, t);\r
- }\r
- if (rc == 0) {\r
- throw new IciqlException("merge failed");\r
- }\r
- }\r
-\r
- public <T> int update(T t) {\r
- Class<?> clazz = t.getClass();\r
- return define(clazz).createIfRequired(this).update(this, t);\r
- }\r
-\r
- public <T> int delete(T t) {\r
- Class<?> clazz = t.getClass();\r
- return define(clazz).createIfRequired(this).delete(this, t);\r
- }\r
-\r
- public <T extends Object> Query<T> from(T alias) {\r
- Class<?> clazz = alias.getClass();\r
- define(clazz).createIfRequired(this);\r
- return Query.from(this, alias);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public <T> int dropTable(Class<? extends T> modelClass) {\r
- TableDefinition<T> def = (TableDefinition<T>) define(modelClass);\r
- SQLStatement stat = new SQLStatement(this);\r
- getDialect().prepareDropTable(stat, def);\r
- IciqlLogger.drop(stat.getSQL());\r
- int rc = 0;\r
- try {\r
- rc = stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {\r
- throw e;\r
- }\r
- }\r
- // remove this model class from the table definition cache\r
- classMap.remove(modelClass);\r
- // remove this model class from the upgrade checked cache\r
- upgradeChecked.remove(modelClass);\r
- return rc;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public <T> int dropView(Class<? extends T> modelClass) {\r
- TableDefinition<T> def = (TableDefinition<T>) define(modelClass);\r
- SQLStatement stat = new SQLStatement(this);\r
- getDialect().prepareDropView(stat, def);\r
- IciqlLogger.drop(stat.getSQL());\r
- int rc = 0;\r
- try {\r
- rc = stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {\r
- throw e;\r
- }\r
- }\r
- // remove this model class from the table definition cache\r
- classMap.remove(modelClass);\r
- // remove this model class from the upgrade checked cache\r
- upgradeChecked.remove(modelClass);\r
- return rc;\r
- }\r
-\r
- public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) {\r
- return buildObjects(modelClass, false, rs);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public <T> List<T> buildObjects(Class<? extends T> modelClass, boolean wildcardSelect, ResultSet rs) {\r
- List<T> result = new ArrayList<T>();\r
- TableDefinition<T> def = (TableDefinition<T>) define(modelClass);\r
- try {\r
- int[] columns = def.mapColumns(wildcardSelect, rs);\r
- while (rs.next()) {\r
- T item = Utils.newObject(modelClass);\r
- def.readRow(dialect, item, rs, columns);\r
- result.add(item);\r
- }\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- return result;\r
- }\r
-\r
- Db upgradeDb() {\r
- if (!upgradeChecked.contains(dbUpgrader.getClass())) {\r
- // flag as checked immediately because calls are nested.\r
- upgradeChecked.add(dbUpgrader.getClass());\r
-\r
- IQVersion model = dbUpgrader.getClass().getAnnotation(IQVersion.class);\r
- if (model.value() == 0) {\r
- // try superclass\r
- Class<?> superClass = dbUpgrader.getClass().getSuperclass();\r
- if (superClass.isAnnotationPresent(IQVersion.class)) {\r
- model = superClass.getAnnotation(IQVersion.class);\r
- }\r
- }\r
- if (model.value() > 0) {\r
- DbVersion v = new DbVersion();\r
- // (SCHEMA="" && TABLE="") == DATABASE\r
- DbVersion dbVersion = from(v).where(v.schemaName).is("").and(v.tableName).is("")\r
- .selectFirst();\r
- if (dbVersion == null) {\r
- // database has no version registration, but model specifies\r
- // version: insert DbVersion entry and return.\r
- DbVersion newDb = new DbVersion(model.value());\r
- // database is an older version than the model\r
- boolean success = dbUpgrader.upgradeDatabase(this, 0, newDb.version);\r
- if (success) {\r
- insert(newDb);\r
- }\r
- } else {\r
- // database has a version registration:\r
- // check to see if upgrade is required.\r
- if ((model.value() > dbVersion.version) && (dbUpgrader != null)) {\r
- // database is an older version than the model\r
- boolean success = dbUpgrader.upgradeDatabase(this, dbVersion.version, model.value());\r
- if (success) {\r
- dbVersion.version = model.value();\r
- update(dbVersion);\r
- }\r
- }\r
- }\r
- }\r
- }\r
- return this;\r
- }\r
-\r
- <T> void upgradeTable(TableDefinition<T> model) {\r
- if (!upgradeChecked.contains(model.getModelClass())) {\r
- // flag is checked immediately because calls are nested\r
- upgradeChecked.add(model.getModelClass());\r
-\r
- if (model.tableVersion > 0) {\r
- // table is using iciql version tracking.\r
- DbVersion v = new DbVersion();\r
- String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName;\r
- DbVersion dbVersion = from(v).where(v.schemaName).is(schema).and(v.tableName)\r
- .is(model.tableName).selectFirst();\r
- if (dbVersion == null) {\r
- // table has no version registration, but model specifies\r
- // version: insert DbVersion entry\r
- DbVersion newTable = new DbVersion(model.tableVersion);\r
- newTable.schemaName = schema;\r
- newTable.tableName = model.tableName;\r
- insert(newTable);\r
- } else {\r
- // table has a version registration:\r
- // check if upgrade is required\r
- if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) {\r
- // table is an older version than model\r
- boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName,\r
- dbVersion.version, model.tableVersion);\r
- if (success) {\r
- dbVersion.version = model.tableVersion;\r
- update(dbVersion);\r
- }\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- <T> TableDefinition<T> define(Class<T> clazz) {\r
- TableDefinition<T> def = getTableDefinition(clazz);\r
- if (def == null) {\r
- upgradeDb();\r
- def = new TableDefinition<T>(clazz);\r
- def.mapFields(this);\r
- classMap.put(clazz, def);\r
- if (Iciql.class.isAssignableFrom(clazz)) {\r
- T t = instance(clazz);\r
- Iciql table = (Iciql) t;\r
- Define.define(def, table);\r
- } else if (clazz.isAnnotationPresent(IQTable.class)) {\r
- // annotated classes skip the Define().define() static\r
- // initializer\r
- T t = instance(clazz);\r
- def.mapObject(t);\r
- } else if (clazz.isAnnotationPresent(IQView.class)) {\r
- // annotated classes skip the Define().define() static\r
- // initializer\r
- T t = instance(clazz);\r
- def.mapObject(t);\r
- }\r
- }\r
- return def;\r
- }\r
-\r
- <T> boolean hasCreated(Class<T> clazz) {\r
- return upgradeChecked.contains(clazz);\r
- }\r
-\r
- public synchronized void setDbUpgrader(DbUpgrader upgrader) {\r
- if (!upgrader.getClass().isAnnotationPresent(IQVersion.class)) {\r
- throw new IciqlException("DbUpgrader must be annotated with " + IQVersion.class.getSimpleName());\r
- }\r
- this.dbUpgrader = upgrader;\r
- upgradeChecked.clear();\r
- }\r
-\r
- public SQLDialect getDialect() {\r
- return dialect;\r
- }\r
-\r
- public Connection getConnection() {\r
- return conn;\r
- }\r
-\r
- @Override\r
- public void close() {\r
- try {\r
- conn.close();\r
- } catch (Exception e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- public <A> TestCondition<A> test(A x) {\r
- return new TestCondition<A>(x);\r
- }\r
-\r
- public <T> void insertAll(List<T> list) {\r
- if (list.size() == 0) {\r
- return;\r
- }\r
- Savepoint savepoint = null;\r
- try {\r
- Class<?> clazz = list.get(0).getClass();\r
- TableDefinition<?> def = define(clazz).createIfRequired(this);\r
- savepoint = prepareSavepoint();\r
- for (T t : list) {\r
- PreparedStatement ps = def.createInsertStatement(this, t, false);\r
- int rc = ps.executeUpdate();\r
- if (rc == 0) {\r
- throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);\r
- }\r
- }\r
- commit(savepoint);\r
- } catch (SQLException e) {\r
- rollback(savepoint);\r
- throw new IciqlException(e);\r
- } catch (IciqlException e) {\r
- rollback(savepoint);\r
- throw e;\r
- }\r
- }\r
-\r
- public <T> List<Long> insertAllAndGetKeys(List<T> list) {\r
- List<Long> identities = new ArrayList<Long>();\r
- if (list.size() == 0) {\r
- return identities;\r
- }\r
- Savepoint savepoint = null;\r
- try {\r
- Class<?> clazz = list.get(0).getClass();\r
- TableDefinition<?> def = define(clazz).createIfRequired(this);\r
- savepoint = prepareSavepoint();\r
- for (T t : list) {\r
- long key = def.insert(this, t, true);\r
- identities.add(key);\r
- }\r
- commit(savepoint);\r
- } catch (IciqlException e) {\r
- rollback(savepoint);\r
- throw e;\r
- }\r
- return identities;\r
- }\r
-\r
- public <T> void updateAll(List<T> list) {\r
- if (list.size() == 0) {\r
- return;\r
- }\r
- Savepoint savepoint = null;\r
- try {\r
- Class<?> clazz = list.get(0).getClass();\r
- TableDefinition<?> def = define(clazz).createIfRequired(this);\r
- savepoint = prepareSavepoint();\r
- for (T t : list) {\r
- def.update(this, t);\r
- }\r
- commit(savepoint);\r
- } catch (IciqlException e) {\r
- rollback(savepoint);\r
- throw e;\r
- }\r
- }\r
-\r
- public <T> void deleteAll(List<T> list) {\r
- if (list.size() == 0) {\r
- return;\r
- }\r
- Savepoint savepoint = null;\r
- try {\r
- Class<?> clazz = list.get(0).getClass();\r
- TableDefinition<?> def = define(clazz).createIfRequired(this);\r
- savepoint = prepareSavepoint();\r
- for (T t : list) {\r
- def.delete(this, t);\r
- }\r
- commit(savepoint);\r
- } catch (IciqlException e) {\r
- rollback(savepoint);\r
- throw e;\r
- }\r
- }\r
-\r
- PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {\r
- IciqlException.checkUnmappedField(sql);\r
- try {\r
- if (returnGeneratedKeys) {\r
- return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);\r
- }\r
- return conn.prepareStatement(sql);\r
- } catch (SQLException e) {\r
- throw IciqlException.fromSQL(sql, e);\r
- }\r
- }\r
-\r
- Savepoint prepareSavepoint() {\r
- // don't change auto-commit mode.\r
- // don't create save point.\r
- if (!autoSavePoint || !dialect.supportsSavePoints()) {\r
- return null;\r
- }\r
- // create a savepoint\r
- Savepoint savepoint = null;\r
- try {\r
- conn.setAutoCommit(false);\r
- savepoint = conn.setSavepoint();\r
- } catch (SQLFeatureNotSupportedException e) {\r
- // jdbc driver does not support save points\r
- } catch (SQLException e) {\r
- throw new IciqlException(e, "Could not create save point");\r
- }\r
- return savepoint;\r
- }\r
-\r
- void commit(Savepoint savepoint) {\r
- if (savepoint != null) {\r
- try {\r
- conn.commit();\r
- conn.setAutoCommit(true);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e, "Failed to commit pending transactions");\r
- }\r
- }\r
- }\r
-\r
- void rollback(Savepoint savepoint) {\r
- if (savepoint != null) {\r
- try {\r
- conn.rollback(savepoint);\r
- conn.setAutoCommit(true);\r
- } catch (SQLException s) {\r
- throw new IciqlException(s, "Failed to rollback transactions");\r
- }\r
- }\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- <T> TableDefinition<T> getTableDefinition(Class<T> clazz) {\r
- return (TableDefinition<T>) classMap.get(clazz);\r
- }\r
-\r
- /**\r
- * Run a SQL query directly against the database.\r
- *\r
- * Be sure to close the ResultSet with\r
- *\r
- * <pre>\r
- * JdbcUtils.closeSilently(rs, true);\r
- * </pre>\r
- *\r
- * @param sql\r
- * the SQL statement\r
- * @param args\r
- * optional object arguments for x=? tokens in query\r
- * @return the result set\r
- */\r
- public ResultSet executeQuery(String sql, List<?> args) {\r
- return executeQuery(sql, args.toArray());\r
- }\r
-\r
- /**\r
- * Run a SQL query directly against the database.\r
- *\r
- * Be sure to close the ResultSet with\r
- *\r
- * <pre>\r
- * JdbcUtils.closeSilently(rs, true);\r
- * </pre>\r
- *\r
- * @param sql\r
- * the SQL statement\r
- * @param args\r
- * optional object arguments for x=? tokens in query\r
- * @return the result set\r
- */\r
- public ResultSet executeQuery(String sql, Object... args) {\r
- try {\r
- if (args.length == 0) {\r
- return conn.createStatement().executeQuery(sql);\r
- } else {\r
- PreparedStatement stat = conn.prepareStatement(sql);\r
- int i = 1;\r
- for (Object arg : args) {\r
- stat.setObject(i++, arg);\r
- }\r
- return stat.executeQuery();\r
- }\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- /**\r
- * Run a SQL query directly against the database and map the results to the\r
- * model class.\r
- *\r
- * @param modelClass\r
- * the model class to bind the query ResultSet rows into.\r
- * @param sql\r
- * the SQL statement\r
- * @return the result set\r
- */\r
- public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, List<?> args) {\r
- return executeQuery(modelClass, sql, args.toArray());\r
- }\r
-\r
- /**\r
- * Run a SQL query directly against the database and map the results to the\r
- * model class.\r
- *\r
- * @param modelClass\r
- * the model class to bind the query ResultSet rows into.\r
- * @param sql\r
- * the SQL statement\r
- * @return the result set\r
- */\r
- public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, Object... args) {\r
- ResultSet rs = null;\r
- try {\r
- if (args.length == 0) {\r
- rs = conn.createStatement().executeQuery(sql);\r
- } else {\r
- PreparedStatement stat = conn.prepareStatement(sql);\r
- int i = 1;\r
- for (Object arg : args) {\r
- stat.setObject(i++, arg);\r
- }\r
- rs = stat.executeQuery();\r
- }\r
- boolean wildcardSelect = sql.toLowerCase().startsWith("select *")\r
- || sql.toLowerCase().startsWith("select distinct *");\r
- return buildObjects(modelClass, wildcardSelect, rs);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- } finally {\r
- JdbcUtils.closeSilently(rs, true);\r
- }\r
- }\r
-\r
- /**\r
- * Run a SQL statement directly against the database.\r
- *\r
- * @param sql\r
- * the SQL statement\r
- * @return the update count\r
- */\r
- public int executeUpdate(String sql, Object... args) {\r
- Statement stat = null;\r
- try {\r
- int updateCount;\r
- if (args.length == 0) {\r
- stat = conn.createStatement();\r
- updateCount = stat.executeUpdate(sql);\r
- } else {\r
- PreparedStatement ps = conn.prepareStatement(sql);\r
- int i = 1;\r
- for (Object arg : args) {\r
- ps.setObject(i++, arg);\r
- }\r
- updateCount = ps.executeUpdate();\r
- stat = ps;\r
- }\r
- return updateCount;\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- } finally {\r
- JdbcUtils.closeSilently(stat);\r
- }\r
- }\r
-\r
- /**\r
- * Allow to enable/disable globally createIfRequired in TableDefinition.\r
- * For advanced user wanting to gain full control of transactions.\r
- * Default value is false.\r
- * @param skipCreate\r
- */\r
- public void setSkipCreate(boolean skipCreate) {\r
- this.skipCreate = skipCreate;\r
- }\r
-\r
- public boolean getSkipCreate() {\r
- return this.skipCreate;\r
- }\r
-\r
- /**\r
- * Allow to enable/disable usage of save point.\r
- * For advanced user wanting to gain full control of transactions.\r
- * Default value is false.\r
- * @param autoSavePoint\r
- */\r
- public void setAutoSavePoint(boolean autoSavePoint) {\r
- this.autoSavePoint = autoSavePoint;\r
- }\r
-\r
- public boolean getAutoSavePoint() {\r
- return this.autoSavePoint;\r
- }\r
-\r
-}\r
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ * Copyright 2012 Alex Telepov.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iciql;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import com.iciql.DbUpgrader.DefaultDbUpgrader;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IQVersion;
+import com.iciql.Iciql.IQView;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+import com.iciql.util.WeakIdentityHashMap;
+
+/**
+ * This class represents a connection to a database.
+ */
+
+public class Db implements AutoCloseable {
+
+ /**
+ * This map It holds unique tokens that are generated by functions such as
+ * Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It
+ * doesn't actually hold column tokens, as those are bound to the query
+ * itself.
+ */
+ private static final Map<Object, Token> TOKENS;
+
+ private static final Map<String, Class<? extends SQLDialect>> DIALECTS;
+
+ private final Connection conn;
+ private final Map<Class<?>, TableDefinition<?>> classMap = Collections
+ .synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>());
+ private final SQLDialect dialect;
+ private DbUpgrader dbUpgrader = new DefaultDbUpgrader();
+ private final Set<Class<?>> upgradeChecked = Collections.synchronizedSet(new HashSet<Class<?>>());
+
+ private boolean skipCreate;
+ private boolean autoSavePoint = true;
+
+ static {
+ TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap<Object, Token>());
+ DIALECTS = Collections.synchronizedMap(new HashMap<String, Class<? extends SQLDialect>>());
+ // can register by...
+ // 1. Connection class name
+ // 2. DatabaseMetaData.getDatabaseProductName()
+ DIALECTS.put("Apache Derby", SQLDialectDerby.class);
+ DIALECTS.put("H2", SQLDialectH2.class);
+ DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class);
+ DIALECTS.put("MySQL", SQLDialectMySQL.class);
+ DIALECTS.put("PostgreSQL", SQLDialectPostgreSQL.class);
+ DIALECTS.put("Microsoft SQL Server", SQLDialectMSSQL.class);
+ DIALECTS.put("SQLite", SQLDialectSQLite.class);
+ }
+
+ private Db(Connection conn) {
+ this.conn = conn;
+ String databaseName = null;
+ try {
+ DatabaseMetaData data = conn.getMetaData();
+ databaseName = data.getDatabaseProductName();
+ } catch (SQLException s) {
+ throw new IciqlException(s, "failed to retrieve database metadata!");
+ }
+ dialect = getDialect(databaseName, conn.getClass().getName());
+ dialect.configureDialect(this);
+ }
+
+ /**
+ * Register a new/custom dialect class. You can use this method to replace
+ * any existing dialect or to add a new one.
+ *
+ * @param token
+ * the fully qualified name of the connection class or the
+ * expected result of DatabaseMetaData.getDatabaseProductName()
+ * @param dialectClass
+ * the dialect class to register
+ */
+ public static void registerDialect(String token, Class<? extends SQLDialect> dialectClass) {
+ DIALECTS.put(token, dialectClass);
+ }
+
+ SQLDialect getDialect(String databaseName, String className) {
+ Class<? extends SQLDialect> dialectClass = null;
+ if (DIALECTS.containsKey(className)) {
+ // dialect registered by connection class name
+ dialectClass = DIALECTS.get(className);
+ } else if (DIALECTS.containsKey(databaseName)) {
+ // dialect registered by database name
+ dialectClass = DIALECTS.get(databaseName);
+ } else {
+ // did not find a match, use default
+ dialectClass = SQLDialectDefault.class;
+ }
+ return instance(dialectClass);
+ }
+
+ static <X> X registerToken(X x, Token token) {
+ TOKENS.put(x, token);
+ return x;
+ }
+
+ static Token getToken(Object x) {
+ return TOKENS.get(x);
+ }
+
+ static <T> T instance(Class<T> clazz) {
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, null, null);
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url, String user, String password) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, user, password);
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url, String user, char[] password) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password));
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ /**
+ * Create a new database instance using a data source. This method is fast,
+ * so that you can always call open() / close() on usage.
+ *
+ * @param ds
+ * the data source
+ * @return the database instance.
+ */
+ public static Db open(DataSource ds) {
+ try {
+ return new Db(ds.getConnection());
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(Connection conn) {
+ return new Db(conn);
+ }
+
+
+
+ /**
+ * Convenience function to avoid import statements in application code.
+ */
+ public void activateConsoleLogger() {
+ IciqlLogger.activateConsoleLogger();
+ }
+
+ /**
+ * Convenience function to avoid import statements in application code.
+ */
+ public void deactivateConsoleLogger() {
+ IciqlLogger.deactivateConsoleLogger();
+ }
+
+ public <T> boolean insert(T t) {
+ Class<?> clazz = t.getClass();
+ long rc = define(clazz).createIfRequired(this).insert(this, t, false);
+ if (rc == 0) {
+ throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);
+ }
+ return rc == 1;
+ }
+
+ public <T> long insertAndGetKey(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).insert(this, t, true);
+ }
+
+ /**
+ * Upsert INSERTS if the record does not exist or UPDATES the record if it
+ * does exist. Not all databases support MERGE and the syntax varies with
+ * the database.
+ *
+ * If the database does not support a MERGE or INSERT OR REPLACE INTO syntax
+ * the dialect can try to simulate a merge by implementing:
+ * <p>
+ * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0)
+ * <p>
+ * iciql will check the affected row count returned by the internal merge
+ * method and if the affected row count = 0, it will issue an update.
+ * <p>
+ * See the Derby dialect for an implementation of this technique.
+ * <p>
+ * If the dialect does not support merge an IciqlException will be thrown.
+ *
+ * @param t
+ */
+ public <T> void upsert(T t) {
+ Class<?> clazz = t.getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ int rc = def.merge(this, t);
+ if (rc == 0) {
+ rc = def.update(this, t);
+ }
+ if (rc == 0) {
+ throw new IciqlException("upsert failed");
+ }
+ }
+
+ /**
+ * Merge INSERTS if the record does not exist or UPDATES the record if it
+ * does exist. Not all databases support MERGE and the syntax varies with
+ * the database.
+ *
+ * If the database does not support a MERGE or INSERT OR REPLACE INTO syntax
+ * the dialect can try to simulate a merge by implementing:
+ * <p>
+ * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0)
+ * <p>
+ * iciql will check the affected row count returned by the internal merge
+ * method and if the affected row count = 0, it will issue an update.
+ * <p>
+ * See the Derby dialect for an implementation of this technique.
+ * <p>
+ * If the dialect does not support merge an IciqlException will be thrown.
+ *
+ * @param t
+ */
+ public <T> void merge(T t) {
+ upsert(t);
+ }
+
+ public <T> boolean update(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).update(this, t) == 1;
+ }
+
+ public <T> boolean delete(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).delete(this, t) == 1;
+ }
+
+ public <T extends Object> Query<T> from(T alias) {
+ Class<?> clazz = alias.getClass();
+ define(clazz).createIfRequired(this);
+ return Query.from(this, alias);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> boolean dropTable(Class<? extends T> modelClass) {
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ SQLStatement stat = new SQLStatement(this);
+ getDialect().prepareDropTable(stat, def);
+ IciqlLogger.drop(stat.getSQL());
+ int rc = 0;
+ try {
+ rc = stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {
+ throw e;
+ }
+ }
+ // remove this model class from the table definition cache
+ classMap.remove(modelClass);
+ // remove this model class from the upgrade checked cache
+ upgradeChecked.remove(modelClass);
+ return rc == 1;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> boolean dropView(Class<? extends T> modelClass) {
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ SQLStatement stat = new SQLStatement(this);
+ getDialect().prepareDropView(stat, def);
+ IciqlLogger.drop(stat.getSQL());
+ int rc = 0;
+ try {
+ rc = stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {
+ throw e;
+ }
+ }
+ // remove this model class from the table definition cache
+ classMap.remove(modelClass);
+ // remove this model class from the upgrade checked cache
+ upgradeChecked.remove(modelClass);
+ return rc == 1;
+ }
+
+ public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) {
+ return buildObjects(modelClass, false, rs);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> List<T> buildObjects(Class<? extends T> modelClass, boolean wildcardSelect, ResultSet rs) {
+ List<T> result = new ArrayList<T>();
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ try {
+ int[] columns = def.mapColumns(wildcardSelect, rs);
+ while (rs.next()) {
+ T item = Utils.newObject(modelClass);
+ def.readRow(dialect, item, rs, columns);
+ result.add(item);
+ }
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ return result;
+ }
+
+ Db upgradeDb() {
+ if (!upgradeChecked.contains(dbUpgrader.getClass())) {
+ // flag as checked immediately because calls are nested.
+ upgradeChecked.add(dbUpgrader.getClass());
+
+ IQVersion model = dbUpgrader.getClass().getAnnotation(IQVersion.class);
+ if (model.value() == 0) {
+ // try superclass
+ Class<?> superClass = dbUpgrader.getClass().getSuperclass();
+ if (superClass.isAnnotationPresent(IQVersion.class)) {
+ model = superClass.getAnnotation(IQVersion.class);
+ }
+ }
+ if (model.value() > 0) {
+ DbVersion v = new DbVersion();
+ // (SCHEMA="" && TABLE="") == DATABASE
+ DbVersion dbVersion = from(v).where(v.schemaName).is("").and(v.tableName).is("")
+ .selectFirst();
+ if (dbVersion == null) {
+ // database has no version registration, but model specifies
+ // version: insert DbVersion entry and return.
+ DbVersion newDb = new DbVersion(model.value());
+ // database is an older version than the model
+ boolean success = dbUpgrader.upgradeDatabase(this, 0, newDb.version);
+ if (success) {
+ insert(newDb);
+ }
+ } else {
+ // database has a version registration:
+ // check to see if upgrade is required.
+ if ((model.value() > dbVersion.version) && (dbUpgrader != null)) {
+ // database is an older version than the model
+ boolean success = dbUpgrader.upgradeDatabase(this, dbVersion.version, model.value());
+ if (success) {
+ dbVersion.version = model.value();
+ update(dbVersion);
+ }
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ <T> void upgradeTable(TableDefinition<T> model) {
+ if (!upgradeChecked.contains(model.getModelClass())) {
+ // flag is checked immediately because calls are nested
+ upgradeChecked.add(model.getModelClass());
+
+ if (model.tableVersion > 0) {
+ // table is using iciql version tracking.
+ DbVersion v = new DbVersion();
+ String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName;
+ DbVersion dbVersion = from(v).where(v.schemaName).is(schema).and(v.tableName)
+ .is(model.tableName).selectFirst();
+ if (dbVersion == null) {
+ // table has no version registration, but model specifies
+ // version: insert DbVersion entry
+ DbVersion newTable = new DbVersion(model.tableVersion);
+ newTable.schemaName = schema;
+ newTable.tableName = model.tableName;
+ insert(newTable);
+ } else {
+ // table has a version registration:
+ // check if upgrade is required
+ if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) {
+ // table is an older version than model
+ boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName,
+ dbVersion.version, model.tableVersion);
+ if (success) {
+ dbVersion.version = model.tableVersion;
+ update(dbVersion);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ <T> TableDefinition<T> define(Class<T> clazz) {
+ TableDefinition<T> def = getTableDefinition(clazz);
+ if (def == null) {
+ upgradeDb();
+ def = new TableDefinition<T>(clazz);
+ def.mapFields(this);
+ classMap.put(clazz, def);
+ if (Iciql.class.isAssignableFrom(clazz)) {
+ T t = instance(clazz);
+ Iciql table = (Iciql) t;
+ Define.define(def, table);
+ } else if (clazz.isAnnotationPresent(IQTable.class)) {
+ // annotated classes skip the Define().define() static
+ // initializer
+ T t = instance(clazz);
+ def.mapObject(t);
+ } else if (clazz.isAnnotationPresent(IQView.class)) {
+ // annotated classes skip the Define().define() static
+ // initializer
+ T t = instance(clazz);
+ def.mapObject(t);
+ }
+ }
+ return def;
+ }
+
+ <T> boolean hasCreated(Class<T> clazz) {
+ return upgradeChecked.contains(clazz);
+ }
+
+ public synchronized void setDbUpgrader(DbUpgrader upgrader) {
+ if (!upgrader.getClass().isAnnotationPresent(IQVersion.class)) {
+ throw new IciqlException("DbUpgrader must be annotated with " + IQVersion.class.getSimpleName());
+ }
+ this.dbUpgrader = upgrader;
+ upgradeChecked.clear();
+ }
+
+ public SQLDialect getDialect() {
+ return dialect;
+ }
+
+ public Connection getConnection() {
+ return conn;
+ }
+
+ @Override
+ public void close() {
+ try {
+ conn.close();
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public <A> TestCondition<A> test(A x) {
+ return new TestCondition<A>(x);
+ }
+
+ public <T> void insertAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ PreparedStatement ps = def.createInsertStatement(this, t, false);
+ int rc = ps.executeUpdate();
+ if (rc == 0) {
+ throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);
+ }
+ }
+ commit(savepoint);
+ } catch (SQLException e) {
+ rollback(savepoint);
+ throw new IciqlException(e);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ public <T> List<Long> insertAllAndGetKeys(List<T> list) {
+ List<Long> identities = new ArrayList<Long>();
+ if (list.size() == 0) {
+ return identities;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ long key = def.insert(this, t, true);
+ identities.add(key);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ return identities;
+ }
+
+ public <T> void updateAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ def.update(this, t);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ public <T> void deleteAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ def.delete(this, t);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {
+ IciqlException.checkUnmappedField(sql);
+ try {
+ if (returnGeneratedKeys) {
+ return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+ }
+ return conn.prepareStatement(sql);
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(sql, e);
+ }
+ }
+
+ Savepoint prepareSavepoint() {
+ // don't change auto-commit mode.
+ // don't create save point.
+ if (!autoSavePoint || !dialect.supportsSavePoints()) {
+ return null;
+ }
+ // create a savepoint
+ Savepoint savepoint = null;
+ try {
+ conn.setAutoCommit(false);
+ savepoint = conn.setSavepoint();
+ } catch (SQLFeatureNotSupportedException e) {
+ // jdbc driver does not support save points
+ } catch (SQLException e) {
+ throw new IciqlException(e, "Could not create save point");
+ }
+ return savepoint;
+ }
+
+ void commit(Savepoint savepoint) {
+ if (savepoint != null) {
+ try {
+ conn.commit();
+ conn.setAutoCommit(true);
+ } catch (SQLException e) {
+ throw new IciqlException(e, "Failed to commit pending transactions");
+ }
+ }
+ }
+
+ void rollback(Savepoint savepoint) {
+ if (savepoint != null) {
+ try {
+ conn.rollback(savepoint);
+ conn.setAutoCommit(true);
+ } catch (SQLException s) {
+ throw new IciqlException(s, "Failed to rollback transactions");
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> TableDefinition<T> getTableDefinition(Class<T> clazz) {
+ return (TableDefinition<T>) classMap.get(clazz);
+ }
+
+ /**
+ * Run a SQL query directly against the database.
+ *
+ * Be sure to close the ResultSet with
+ *
+ * <pre>
+ * JdbcUtils.closeSilently(rs, true);
+ * </pre>
+ *
+ * @param sql
+ * the SQL statement
+ * @param args
+ * optional object arguments for x=? tokens in query
+ * @return the result set
+ */
+ public ResultSet executeQuery(String sql, List<?> args) {
+ return executeQuery(sql, args.toArray());
+ }
+
+ /**
+ * Run a SQL query directly against the database.
+ *
+ * Be sure to close the ResultSet with
+ *
+ * <pre>
+ * JdbcUtils.closeSilently(rs, true);
+ * </pre>
+ *
+ * @param sql
+ * the SQL statement
+ * @param args
+ * optional object arguments for x=? tokens in query
+ * @return the result set
+ */
+ public ResultSet executeQuery(String sql, Object... args) {
+ try {
+ if (args.length == 0) {
+ return conn.createStatement().executeQuery(sql);
+ } else {
+ PreparedStatement stat = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ stat.setObject(i++, arg);
+ }
+ return stat.executeQuery();
+ }
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ /**
+ * Run a SQL query directly against the database and map the results to the
+ * model class.
+ *
+ * @param modelClass
+ * the model class to bind the query ResultSet rows into.
+ * @param sql
+ * the SQL statement
+ * @return the result set
+ */
+ public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, List<?> args) {
+ return executeQuery(modelClass, sql, args.toArray());
+ }
+
+ /**
+ * Run a SQL query directly against the database and map the results to the
+ * model class.
+ *
+ * @param modelClass
+ * the model class to bind the query ResultSet rows into.
+ * @param sql
+ * the SQL statement
+ * @return the result set
+ */
+ public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, Object... args) {
+ ResultSet rs = null;
+ try {
+ if (args.length == 0) {
+ rs = conn.createStatement().executeQuery(sql);
+ } else {
+ PreparedStatement stat = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ stat.setObject(i++, arg);
+ }
+ rs = stat.executeQuery();
+ }
+ boolean wildcardSelect = sql.toLowerCase().startsWith("select *")
+ || sql.toLowerCase().startsWith("select distinct *");
+ return buildObjects(modelClass, wildcardSelect, rs);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ }
+
+ /**
+ * Run a SQL statement directly against the database.
+ *
+ * @param sql
+ * the SQL statement
+ * @return the update count
+ */
+ public int executeUpdate(String sql, Object... args) {
+ Statement stat = null;
+ try {
+ int updateCount;
+ if (args.length == 0) {
+ stat = conn.createStatement();
+ updateCount = stat.executeUpdate(sql);
+ } else {
+ PreparedStatement ps = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ ps.setObject(i++, arg);
+ }
+ updateCount = ps.executeUpdate();
+ stat = ps;
+ }
+ return updateCount;
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ } finally {
+ JdbcUtils.closeSilently(stat);
+ }
+ }
+
+ /**
+ * Allow to enable/disable globally createIfRequired in TableDefinition.
+ * For advanced user wanting to gain full control of transactions.
+ * Default value is false.
+ * @param skipCreate
+ */
+ public void setSkipCreate(boolean skipCreate) {
+ this.skipCreate = skipCreate;
+ }
+
+ public boolean getSkipCreate() {
+ return this.skipCreate;
+ }
+
+ /**
+ * Allow to enable/disable usage of save point.
+ * For advanced user wanting to gain full control of transactions.
+ * Default value is false.
+ * @param autoSavePoint
+ */
+ public void setAutoSavePoint(boolean autoSavePoint) {
+ this.autoSavePoint = autoSavePoint;
+ }
+
+ public boolean getAutoSavePoint() {
+ return this.autoSavePoint;
+ }
+
+}