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 += ")";
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 <I>obj</I>
* 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 <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
@@ -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 <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) {
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;
}
}