git-svn-id: https://samskivert.googlecode.com/svn/trunk@652 6335cc39-0255-0410-8fd6-9bcaacd3b74c

This commit is contained in:
mdb
2002-03-15 01:06:03 +00:00
parent 9346b88369
commit a266e05d65
2 changed files with 234 additions and 31 deletions
@@ -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:
*
* <pre>
* // 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);
* </pre>
*/
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;
}
@@ -254,7 +254,7 @@ public class Table {
sql += ")"; sql += ")";
insertStmt = session.connection.prepareStatement(sql); insertStmt = session.connection.prepareStatement(sql);
} }
bindUpdateVariables(insertStmt, obj); bindUpdateVariables(insertStmt, obj, null);
insertStmt.executeUpdate(); insertStmt.executeUpdate();
insertStmt.clearParameters(); insertStmt.clearParameters();
// } catch(SQLException ex) { session.handleSQLException(ex); } // } catch(SQLException ex) { session.handleSQLException(ex); }
@@ -297,7 +297,7 @@ public class Table {
insertStmt = session.connection.prepareStatement(sql); insertStmt = session.connection.prepareStatement(sql);
} }
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
bindUpdateVariables(insertStmt, objects[i]); bindUpdateVariables(insertStmt, objects[i], null);
insertStmt.addBatch(); insertStmt.addBatch();
} }
insertStmt.executeBatch(); insertStmt.executeBatch();
@@ -305,6 +305,15 @@ public class Table {
// } catch(SQLException ex) { session.handleSQLException(ex); } // } 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 /** Update record in the table using table's primary key to locate
* record in the table and values of fields of specified object <I>obj</I> * record in the table and values of fields of specified object <I>obj</I>
* to alter record fields. * to alter record fields.
@@ -313,11 +322,30 @@ public class Table {
* updated record fields * updated record fields
* *
* @return number of objects actually updated * @return number of objects actually updated
*/ */
public int update(Object obj) public int update(Object obj)
throws SQLException 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 <I>obj</I>
* 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 /** 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) public synchronized int update(Object obj, Session session)
throws SQLException 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 <I>obj</I>
* 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) { if (primaryKeys == null) {
throw new NoPrimaryKeyError(this); throw new NoPrimaryKeyError(this);
@@ -342,21 +390,32 @@ public class Table {
int nUpdated = 0; int nUpdated = 0;
// try { // try {
checkConnection(session); checkConnection(session);
if (updateStmt == null) { PreparedStatement ustmt;
String sql = "update " + name + " set " + listOfAssignments // if we have a field mask, we need to create a custom update
+ " where " + primaryKeys[0] + " = ?"; // statement
for (int i = 1; i < primaryKeys.length; i++) { if (mask != null) {
sql += " and " + primaryKeys[i] + " = ?"; String sql = "update " + name + " set " +
} buildListOfAssignments(mask)
updateStmt = session.connection.prepareStatement(sql); + 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++) { for (int i = 0; i < primaryKeys.length; i++) {
fields[primaryKeyIndices[i]].bindVariable(updateStmt, obj, int fidx = primaryKeyIndices[i];
nColumns+i+1); fields[fidx].bindVariable(ustmt, obj, column+i+1);
} }
bindUpdateVariables(updateStmt, obj); nUpdated = ustmt.executeUpdate();
nUpdated = updateStmt.executeUpdate(); ustmt.clearParameters();
updateStmt.clearParameters();
// } catch(SQLException ex) { session.handleSQLException(ex); } // } catch(SQLException ex) { session.handleSQLException(ex); }
return nUpdated; return nUpdated;
} }
@@ -400,19 +459,16 @@ public class Table {
checkConnection(session); checkConnection(session);
if (updateStmt == null) { if (updateStmt == null) {
String sql = "update " + name + " set " + listOfAssignments String sql = "update " + name + " set " + listOfAssignments
+ " where " + primaryKeys[0] + " = ?"; + buildUpdateWhere();
for (int i = 1; i < primaryKeys.length; i++) {
sql += " and " + primaryKeys[i] + " = ?";
}
updateStmt = session.connection.prepareStatement(sql); updateStmt = session.connection.prepareStatement(sql);
} }
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
int column = bindUpdateVariables(updateStmt, objects[i], null);
for (int j = 0; j < primaryKeys.length; j++) { for (int j = 0; j < primaryKeys.length; j++) {
fields[primaryKeyIndices[j]].bindVariable(updateStmt, int fidx = primaryKeyIndices[j];
objects[i], fields[fidx].bindVariable(
nColumns+1+j); updateStmt, objects[i], column+1+j);
} }
bindUpdateVariables(updateStmt, objects[i]);
updateStmt.addBatch(); updateStmt.addBatch();
} }
int rc[] = updateStmt.executeBatch(); int rc[] = updateStmt.executeBatch();
@@ -571,6 +627,7 @@ public class Table {
static private Class serializableClass; static private Class serializableClass;
private FieldDescriptor[] fields; private FieldDescriptor[] fields;
private FieldMask fMask;
private int nFields; // length of "fields" array private int nFields; // length of "fields" array
private int nColumns; // number of atomic fields in "fields" array private int nColumns; // number of atomic fields in "fields" array
@@ -620,6 +677,7 @@ public class Table {
nFields = buildFieldsList(fieldsVector, cls, ""); nFields = buildFieldsList(fieldsVector, cls, "");
fields = new FieldDescriptor[nFields]; fields = new FieldDescriptor[nFields];
fieldsVector.copyInto(fields); fieldsVector.copyInto(fields);
fMask = new FieldMask(fields);
try { try {
constructor = cls.getDeclaredConstructor(new Class[0]); constructor = cls.getDeclaredConstructor(new Class[0]);
@@ -846,6 +904,24 @@ public class Table {
return n; 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 { protected final Object load(ResultSet result) throws SQLException {
Object obj; Object obj;
@@ -887,11 +963,12 @@ public class Table {
return column; return column;
} }
protected final void bindUpdateVariables(PreparedStatement pstmt, protected final int bindUpdateVariables(PreparedStatement pstmt,
Object obj) Object obj,
FieldMask mask)
throws SQLException throws SQLException
{ {
bindUpdateVariables(pstmt, obj, 0, nFields, 0); return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask);
} }
protected final void bindQueryVariables(PreparedStatement pstmt, protected final void bindQueryVariables(PreparedStatement pstmt,
@@ -908,6 +985,16 @@ public class Table {
result.updateRow(); 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) protected final String buildQueryList(Object qbe)
{ {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
@@ -919,13 +1006,18 @@ public class Table {
} }
private final int bindUpdateVariables(PreparedStatement pstmt, Object obj, private final int bindUpdateVariables(PreparedStatement pstmt, Object obj,
int i, int end, int column) int i, int end, int column,
FieldMask mask)
throws SQLException throws SQLException
{ {
try { try {
while (i < end) { while (i < end) {
FieldDescriptor fd = fields[i++]; FieldDescriptor fd = fields[i++];
Object comp = null; 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.isBuiltin() && (comp = fd.field.get(obj)) == null) {
if (fd.isCompound()) { if (fd.isCompound()) {
int nComponents = fd.outType-FieldDescriptor.tCompound; int nComponents = fd.outType-FieldDescriptor.tCompound;
@@ -944,8 +1036,8 @@ public class Table {
} else { } else {
if (!fd.bindVariable(pstmt, obj, ++column)) { if (!fd.bindVariable(pstmt, obj, ++column)) {
int nComponents = fd.outType-FieldDescriptor.tCompound; int nComponents = fd.outType-FieldDescriptor.tCompound;
column = bindUpdateVariables(pstmt, comp, column = bindUpdateVariables(
i,i+nComponents,column-1); pstmt, comp, i, i+nComponents,column-1, mask);
i += nComponents; i += nComponents;
} }
} }