]> source.dussan.org Git - iciql.git/commitdiff
Use savepoints for all bulk ops (insert all, update all, delete all)
authorJames Moger <james.moger@gmail.com>
Mon, 20 Aug 2012 13:59:00 +0000 (09:59 -0400)
committerJames Moger <james.moger@gmail.com>
Mon, 20 Aug 2012 13:59:00 +0000 (09:59 -0400)
docs/05_releases.mkd
src/com/iciql/Db.java
src/com/iciql/SQLStatement.java
src/com/iciql/TableDefinition.java

index 79543aa776fc4bc3962dd21a0b5d8d878255c910..c31a8b8d689a76ea0e176d10b1e1be3fed222358 100644 (file)
@@ -4,6 +4,10 @@
 \r
 **%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) &nbsp; *released %BUILDDATE%*\r
 \r
+- All bulk operations (insert all, update all, delete all) now use JDBC savepoints to ensure atomicity of the transaction\r
+\r
+**1.0.0** &nbsp; *released 2012-07-14*\r
+\r
 - Issue CREATE TABLE and CREATE INDEX statements once per-db instance/table-mapping\r
 - Fixed bug in using 0L primitive values in where clauses.  These were confused with the COUNT(*) function. (Github/kc5nra,issue 5)\r
 - Added support for single column subquery *select name, address from user_table where user_id in (select user_id from invoice table where paid = false)*\r
index 6187aba474bc44838dc001837ff36d7f01f166b4..6e23a5920107d934c27a0924c2f6be02aa72eb9a 100644 (file)
@@ -22,6 +22,8 @@ import java.sql.DatabaseMetaData;
 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
@@ -58,7 +60,7 @@ public class Db {
        private static final Map<Object, Token> TOKENS;\r
 \r
        private static final Map<String, Class<? extends SQLDialect>> DIALECTS;\r
-\r
+       \r
        private final Connection conn;\r
        private final Map<Class<?>, TableDefinition<?>> classMap = Collections\r
                        .synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>());\r
@@ -431,28 +433,88 @@ public class Db {
        }\r
 \r
        public <T> void insertAll(List<T> list) {\r
-               for (T t : list) {\r
-                       insert(t);\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
-               for (T t : list) {\r
-                       identities.add(insertAndGetKey(t));\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
-               for (T t : list) {\r
-                       update(t);\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
-               for (T t : list) {\r
-                       delete(t);\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
@@ -467,6 +529,42 @@ public class Db {
                        throw IciqlException.fromSQL(sql, e);\r
                }\r
        }\r
+       \r
+       Savepoint prepareSavepoint() {\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
index 69e26ed20a5093303e2320e08ba4bc8cf0401e6d..2f97829b63d244d97a5a85a4cbc488b337f05a50 100644 (file)
@@ -166,7 +166,7 @@ public class SQLStatement {
                }\r
        }\r
 \r
-       private PreparedStatement prepare(boolean returnGeneratedKeys) {\r
+       PreparedStatement prepare(boolean returnGeneratedKeys) {\r
                PreparedStatement prep = db.prepare(getSQL(), returnGeneratedKeys);\r
                for (int i = 0; i < params.size(); i++) {\r
                        Object o = params.get(i);\r
index b6a670b4f2c6b8656199dc93f6e6e99a0e23b41b..bd61b16bd1cf8609f869dab296c2b66493ed6b9f 100644 (file)
@@ -18,6 +18,7 @@
 package com.iciql;\r
 \r
 import java.lang.reflect.Field;\r
+import java.sql.PreparedStatement;\r
 import java.sql.ResultSet;\r
 import java.sql.SQLException;\r
 import java.util.ArrayList;\r
@@ -489,6 +490,38 @@ public class TableDefinition<T> {
                // return the value unchanged\r
                return value;\r
        }\r
+       \r
+       PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) {\r
+               SQLStatement stat = new SQLStatement(db);\r
+               StatementBuilder buff = new StatementBuilder("INSERT INTO ");\r
+               buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');\r
+               for (FieldDefinition field : fields) {\r
+                       if (skipInsertField(field, obj)) {\r
+                               continue;\r
+                       }\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(db.getDialect().prepareColumnName(field.columnName));\r
+               }\r
+               buff.append(") VALUES(");\r
+               buff.resetCount();\r
+               for (FieldDefinition field : fields) {\r
+                       if (skipInsertField(field, obj)) {\r
+                               continue;\r
+                       }\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append('?');\r
+                       Object value = getValue(obj, field);\r
+                       if (value == null && !field.nullable) {\r
+                               // try to interpret and instantiate a default value\r
+                               value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());\r
+                       }\r
+                       stat.addParameter(value);\r
+               }\r
+               buff.append(')');\r
+               stat.setSQL(buff.toString());\r
+               IciqlLogger.insert(stat.getSQL());\r
+               return stat.prepare(returnKey);\r
+       }\r
 \r
        long insert(Db db, Object obj, boolean returnKey) {\r
                SQLStatement stat = new SQLStatement(db);\r