diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldMask.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldMask.java new file mode 100644 index 00000000..be9b469f --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldMask.java @@ -0,0 +1,111 @@ +// +// $Id: FieldMask.java,v 1.1 2002/03/15 01:06:03 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.jora; + +import java.util.Arrays; +import java.util.HashMap; + +/** + * Provides support for doing partial updates to objects in a JORA table. + * A field mask can be obtained for a particular table, fields marked as + * modified and the object subsequently updated in a fairly + * straightforward manner: + * + *
+ * // updating parts of a table that contains User objects
+ * User user = // load user object from table
+ * FieldMask mask = table.getFieldMask();
+ * user.firstName = newFirstName;
+ * mask.setModified("firstName");
+ * user.lastName = newLastName;
+ * mask.setModified("lastName");
+ * table.update(user, mask);
+ *
+ */
+public class FieldMask
+ implements Cloneable
+{
+ /**
+ * Creates a field mask for a {@link Table} that uses the supplied
+ * field descriptors.
+ */
+ public FieldMask (FieldDescriptor[] descrips)
+ {
+ // create a mapping from field name to descriptor index
+ _descripMap = new HashMap();
+ int dcount = descrips.length;
+ for (int i = 0; i < dcount; i++) {
+ _descripMap.put(descrips[i].field.getName(), new Integer(i));
+ }
+ // create our modified flags
+ _modified = new boolean[dcount];
+ }
+
+ /**
+ * Returns true if the field with the specified index is modified.
+ */
+ public final boolean isModified (int index)
+ {
+ return _modified[index];
+ }
+
+ /**
+ * Marks the specified field as modified.
+ */
+ public void setModified (String fieldName)
+ {
+ Integer index = (Integer)_descripMap.get(fieldName);
+ if (index == null) {
+ String errmsg = "";
+ throw new IllegalArgumentException(errmsg);
+ }
+ _modified[index.intValue()] = true;
+ }
+
+ /**
+ * Clears out the modification state of the fields in this mask.
+ */
+ public void clear ()
+ {
+ Arrays.fill(_modified, false);
+ }
+
+ /**
+ * Creates a copy of this field mask, with all fields set to
+ * not-modified.
+ */
+ public Object clone ()
+ {
+ try {
+ FieldMask mask = (FieldMask)super.clone();
+ mask._modified = new boolean[_modified.length];
+ return mask;
+ } catch (CloneNotSupportedException cnse) {
+ throw new RuntimeException("Oh god, the clones!");
+ }
+ }
+
+ /** Modified flags for each field of an object in this table. */
+ protected boolean[] _modified;
+
+ /** A mapping from field names to field descriptor index. */
+ protected HashMap _descripMap;
+}
diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java
index e80c100a..74003b24 100644
--- a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java
+++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java
@@ -254,7 +254,7 @@ public class Table {
sql += ")";
insertStmt = session.connection.prepareStatement(sql);
}
- bindUpdateVariables(insertStmt, obj);
+ bindUpdateVariables(insertStmt, obj, null);
insertStmt.executeUpdate();
insertStmt.clearParameters();
// } catch(SQLException ex) { session.handleSQLException(ex); }
@@ -297,7 +297,7 @@ public class Table {
insertStmt = session.connection.prepareStatement(sql);
}
for (int i = 0; i < objects.length; i++) {
- bindUpdateVariables(insertStmt, objects[i]);
+ bindUpdateVariables(insertStmt, objects[i], null);
insertStmt.addBatch();
}
insertStmt.executeBatch();
@@ -305,6 +305,15 @@ public class Table {
// } catch(SQLException ex) { session.handleSQLException(ex); }
}
+ /** Returns a field mask that can be configured and used to update
+ * subsets of entire objects via calls to {@link
+ * #update(Object,FieldMask)}.
+ */
+ public FieldMask getFieldMask ()
+ {
+ return (FieldMask)fMask.clone();
+ }
+
/** Update record in the table using table's primary key to locate
* record in the table and values of fields of specified object obj
* to alter record fields.
@@ -313,11 +322,30 @@ public class Table {
* updated record fields
*
* @return number of objects actually updated
- */
+ */
public int update(Object obj)
throws SQLException
{
- return update(obj, session);
+ return update(obj, null, null);
+ }
+
+ /** Update record in the table using table's primary key to locate
+ * record in the table and values of fields of specified object obj
+ * to alter record fields. Only the fields marked as modified in the
+ * supplied field mask will be updated in the database.
+ *
+ * @param obj object specifing value of primary key and new values of
+ * updated record fields
+ * @param mask a {@link FieldMask} instance configured to indicate
+ * which of the object's fields are modified and should be written to
+ * the database.
+ *
+ * @return number of objects actually updated
+ */
+ public int update(Object obj, FieldMask mask)
+ throws SQLException
+ {
+ return update(obj, mask, session);
}
/** Update record in the table using table's primary key to locate
@@ -332,6 +360,26 @@ public class Table {
*/
public synchronized int update(Object obj, Session session)
throws SQLException
+ {
+ return update(obj, null, session);
+ }
+
+ /** Update record in the table using table's primary key to locate
+ * record in the table and values of fields of specified object obj
+ * to alter record fields. Only the fields marked as modified in the
+ * supplied field mask will be updated in the database.
+ *
+ * @param obj object specifing value of primary key and new values of
+ * updated record fields
+ * @param mask a {@link FieldMask} instance configured to indicate
+ * which of the object's fields are modified and should be written to
+ * the database.
+ * @param session user database session
+ *
+ * @return number of objects actually updated
+ */
+ public synchronized int update(Object obj, FieldMask mask, Session session)
+ throws SQLException
{
if (primaryKeys == null) {
throw new NoPrimaryKeyError(this);
@@ -342,21 +390,32 @@ public class Table {
int nUpdated = 0;
// try {
checkConnection(session);
- if (updateStmt == null) {
- String sql = "update " + name + " set " + listOfAssignments
- + " where " + primaryKeys[0] + " = ?";
- for (int i = 1; i < primaryKeys.length; i++) {
- sql += " and " + primaryKeys[i] + " = ?";
- }
- updateStmt = session.connection.prepareStatement(sql);
- }
+ PreparedStatement ustmt;
+ // if we have a field mask, we need to create a custom update
+ // statement
+ if (mask != null) {
+ String sql = "update " + name + " set " +
+ buildListOfAssignments(mask)
+ + buildUpdateWhere();
+ ustmt = session.connection.prepareStatement(sql);
+ } else {
+ // otherwise we can use our "full-object-update" statement
+ if (updateStmt == null) {
+ String sql = "update " + name + " set " + listOfAssignments
+ + buildUpdateWhere();
+ updateStmt = session.connection.prepareStatement(sql);
+ }
+ ustmt = updateStmt;
+ }
+ // bind the update variables
+ int column = bindUpdateVariables(ustmt, obj, mask);
+ // bind the keys
for (int i = 0; i < primaryKeys.length; i++) {
- fields[primaryKeyIndices[i]].bindVariable(updateStmt, obj,
- nColumns+i+1);
+ int fidx = primaryKeyIndices[i];
+ fields[fidx].bindVariable(ustmt, obj, column+i+1);
}
- bindUpdateVariables(updateStmt, obj);
- nUpdated = updateStmt.executeUpdate();
- updateStmt.clearParameters();
+ nUpdated = ustmt.executeUpdate();
+ ustmt.clearParameters();
// } catch(SQLException ex) { session.handleSQLException(ex); }
return nUpdated;
}
@@ -400,19 +459,16 @@ public class Table {
checkConnection(session);
if (updateStmt == null) {
String sql = "update " + name + " set " + listOfAssignments
- + " where " + primaryKeys[0] + " = ?";
- for (int i = 1; i < primaryKeys.length; i++) {
- sql += " and " + primaryKeys[i] + " = ?";
- }
+ + buildUpdateWhere();
updateStmt = session.connection.prepareStatement(sql);
}
for (int i = 0; i < objects.length; i++) {
+ int column = bindUpdateVariables(updateStmt, objects[i], null);
for (int j = 0; j < primaryKeys.length; j++) {
- fields[primaryKeyIndices[j]].bindVariable(updateStmt,
- objects[i],
- nColumns+1+j);
+ int fidx = primaryKeyIndices[j];
+ fields[fidx].bindVariable(
+ updateStmt, objects[i], column+1+j);
}
- bindUpdateVariables(updateStmt, objects[i]);
updateStmt.addBatch();
}
int rc[] = updateStmt.executeBatch();
@@ -571,6 +627,7 @@ public class Table {
static private Class serializableClass;
private FieldDescriptor[] fields;
+ private FieldMask fMask;
private int nFields; // length of "fields" array
private int nColumns; // number of atomic fields in "fields" array
@@ -620,6 +677,7 @@ public class Table {
nFields = buildFieldsList(fieldsVector, cls, "");
fields = new FieldDescriptor[nFields];
fieldsVector.copyInto(fields);
+ fMask = new FieldMask(fields);
try {
constructor = cls.getDeclaredConstructor(new Class[0]);
@@ -846,6 +904,24 @@ public class Table {
return n;
}
+ protected final String buildListOfAssignments (FieldMask mask)
+ {
+ StringBuffer sql = new StringBuffer();
+ int fcount = fields.length;
+ for (int i = 0; i < fcount; i++) {
+ // skip non-modified fields
+ if (!mask.isModified(i)) {
+ continue;
+ }
+ // separate fields by a comma
+ if (sql.length() > 0) {
+ sql.append(",");
+ }
+ // append the necessary SQL to update this column
+ sql.append(fields[i].name).append("=?");
+ }
+ return sql.toString();
+ }
protected final Object load(ResultSet result) throws SQLException {
Object obj;
@@ -887,11 +963,12 @@ public class Table {
return column;
}
- protected final void bindUpdateVariables(PreparedStatement pstmt,
- Object obj)
+ protected final int bindUpdateVariables(PreparedStatement pstmt,
+ Object obj,
+ FieldMask mask)
throws SQLException
{
- bindUpdateVariables(pstmt, obj, 0, nFields, 0);
+ return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask);
}
protected final void bindQueryVariables(PreparedStatement pstmt,
@@ -908,6 +985,16 @@ public class Table {
result.updateRow();
}
+ protected final String buildUpdateWhere()
+ {
+ StringBuffer sql = new StringBuffer();
+ sql.append(" where ").append(primaryKeys[0]).append(" = ?");
+ for (int i = 1; i < primaryKeys.length; i++) {
+ sql.append(" and ").append(primaryKeys[i]).append(" = ?");
+ }
+ return sql.toString();
+ }
+
protected final String buildQueryList(Object qbe)
{
StringBuffer buf = new StringBuffer();
@@ -919,13 +1006,18 @@ public class Table {
}
private final int bindUpdateVariables(PreparedStatement pstmt, Object obj,
- int i, int end, int column)
+ int i, int end, int column,
+ FieldMask mask)
throws SQLException
{
try {
while (i < end) {
FieldDescriptor fd = fields[i++];
Object comp = null;
+ // skip non-modified fields
+ if (mask != null && !mask.isModified(i-1)) {
+ continue;
+ }
if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) {
if (fd.isCompound()) {
int nComponents = fd.outType-FieldDescriptor.tCompound;
@@ -944,8 +1036,8 @@ public class Table {
} else {
if (!fd.bindVariable(pstmt, obj, ++column)) {
int nComponents = fd.outType-FieldDescriptor.tCompound;
- column = bindUpdateVariables(pstmt, comp,
- i,i+nComponents,column-1);
+ column = bindUpdateVariables(
+ pstmt, comp, i, i+nComponents,column-1, mask);
i += nComponents;
}
}