diff --git a/src/java/com/samskivert/jdbc/JORARepository.java b/src/java/com/samskivert/jdbc/JORARepository.java index 2e72ab0a..bad14346 100644 --- a/src/java/com/samskivert/jdbc/JORARepository.java +++ b/src/java/com/samskivert/jdbc/JORARepository.java @@ -30,7 +30,7 @@ import com.samskivert.jdbc.jora.*; * The JORA repository simplifies the process of building persistence * services that make use of the JORA object relational mapping package. * - * @see com.samskivert.jdbc.jora.Session + * @see com.samskivert.jdbc.jora.Table */ public abstract class JORARepository extends SimpleRepository { @@ -47,17 +47,12 @@ public abstract class JORARepository extends SimpleRepository { super(provider, dbident); - // our parent class will have already obtained a database connection - // and therefore ended up calling getConnection() which will create our - // session, so we can make use of it straight away - // create our tables - createTables(_session); + createTables(); } /** - * Inserts the supplied object into the specified table. The table must be - * configured to store items of the supplied type. + * Inserts the supplied object into the specified table. * * @return a call to {@link DatabaseLiaison#lastInsertedId} made * immediately following the insert. @@ -69,15 +64,14 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - table.insert(object); + table.insert(conn, object); return liaison.lastInsertedId(conn); } }); } /** - * Updates the supplied object in the specified table. The table must - * be configured to store items of the supplied type. + * Updates the supplied object in the specified table. * * @return the number of rows modified by the update. */ @@ -88,7 +82,26 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.update(object); + return table.update(conn, object); + } + }); + } + + /** + * Updates fields specified by the supplied field mask in the supplied + * object in the specified table. + * + * @return the number of rows modified by the update. + */ + protected int update (final Table table, final T object, + final FieldMask mask) + throws PersistenceException + { + return executeUpdate(new Operation() { + public Integer invoke (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + return table.update(conn, object, mask); } }); } @@ -106,7 +119,7 @@ public abstract class JORARepository extends SimpleRepository Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.select(query).toArrayList(); + return table.select(conn, query).toArrayList(); } }); } @@ -124,7 +137,43 @@ public abstract class JORARepository extends SimpleRepository Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.select(auxtable, query).toArrayList(); + return table.select(conn, auxtable, query).toArrayList(); + } + }); + } + + /** + * Loads all objects from the specified table that match the supplied + * example. + */ + protected ArrayList loadAllByExample ( + final Table table, final T example) + throws PersistenceException + { + return execute(new Operation>() { + public ArrayList invoke ( + Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + return table.queryByExample(conn, example).toArrayList(); + } + }); + } + + /** + * Loads all objects from the specified table that match the supplied + * example. + */ + protected ArrayList loadAllByExample ( + final Table table, final T example, final FieldMask mask) + throws PersistenceException + { + return execute(new Operation>() { + public ArrayList invoke ( + Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + return table.queryByExample(conn, example, mask).toArrayList(); } }); } @@ -141,7 +190,25 @@ public abstract class JORARepository extends SimpleRepository public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.select(query).get(); + return table.select(conn, query).get(); + } + }); + } + + /** + * Loads a single object from the specified table that matches the supplied + * query, joining with the supplied auxiliary table(s). Note: the + * query should match one or zero records, not more. + */ + protected T load ( + final Table table, final String auxtable, final String query) + throws PersistenceException + { + return execute(new Operation() { + public T invoke (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + return table.select(conn, auxtable, query).get(); } }); } @@ -158,7 +225,7 @@ public abstract class JORARepository extends SimpleRepository public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.queryByExample(example).get(); + return table.queryByExample(conn, example).get(); } }); } @@ -169,14 +236,14 @@ public abstract class JORARepository extends SimpleRepository * records, not more. */ protected T loadByExample ( - final Table table, final FieldMask mask, final T example) + final Table table, final T example, final FieldMask mask) throws PersistenceException { return execute(new Operation() { public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.queryByExample(example, mask).get(); + return table.queryByExample(conn, example, mask).get(); } }); } @@ -196,8 +263,8 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - if (table.update(object) == 0) { - table.insert(object); + if (table.update(conn, object) == 0) { + table.insert(conn, object); return liaison.lastInsertedId(conn); } return -1; @@ -221,7 +288,7 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.update(object, mask); + return table.update(conn, object, mask); } }); } @@ -244,7 +311,7 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.update(object, mask); + return table.update(conn, object, mask); } }); } @@ -261,30 +328,14 @@ public abstract class JORARepository extends SimpleRepository public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { - return table.delete(object); + return table.delete(conn, object); } }); } /** - * After the database session is begun, this function will be called - * to give the repository implementation the opportunity to create its - * table objects. - * - * @param session the session instance to use when creating your table - * instances. + * During construction, this function will be called to give the repository + * implementation the opportunity to create its table objects. */ - protected abstract void createTables (Session session); - - protected void gotConnection (Connection conn) - { - // create or update our JORA session - if (_session == null) { - _session = new Session(conn); - } else { - _session.setConnection(conn); - } - } - - protected Session _session; + protected abstract void createTables (); } diff --git a/src/java/com/samskivert/jdbc/MySQLLiaison.java b/src/java/com/samskivert/jdbc/MySQLLiaison.java index 4c0e8c65..47ce95fa 100644 --- a/src/java/com/samskivert/jdbc/MySQLLiaison.java +++ b/src/java/com/samskivert/jdbc/MySQLLiaison.java @@ -46,7 +46,7 @@ public class MySQLLiaison implements DatabaseLiaison String msg = sqe.getMessage(); return (msg != null && (msg.indexOf("Lost connection") != -1 || - msg.indexOf("Communication link failure") != -1 || + msg.indexOf("link failure") != -1 || msg.indexOf("Broken pipe") != -1)); } diff --git a/src/java/com/samskivert/jdbc/jora/Cursor.java b/src/java/com/samskivert/jdbc/jora/Cursor.java index d079f759..5a4c465a 100644 --- a/src/java/com/samskivert/jdbc/jora/Cursor.java +++ b/src/java/com/samskivert/jdbc/jora/Cursor.java @@ -1,12 +1,22 @@ -//-< Cursor.java >---------------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Cursor for navigation thru result of SELECT statement -//-------------------------------------------------------------------*--------* +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, 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; @@ -15,81 +25,67 @@ import java.sql.*; import com.samskivert.Log; -/** Cursor is used for successive access to records fetched by SELECT - * statement. As far as records can be retrived from several derived tables - * (polymorphic form of select), this class can issue several requests - * to database. Cursor also provides methods for updating/deleting current - * record. +/** + * Cursor is used for successive access to records fetched by SELECT statement. + * As far as records can be retrived from several derived tables (polymorphic + * form of select), this class can issue several requests to database. Cursor + * also provides methods for updating/deleting current record. */ public class Cursor { /** - * A cursor is initially positioned before its first row; the - * first call to next makes the first row the current row; the - * second call makes the second row the current row, etc. + * A cursor is initially positioned before its first row; the first call to + * next makes the first row the current row; the second call makes the + * second row the current row, etc. * - *

If an input stream from the previous row is open, it is - * implicitly closed. The ResultSet's warning chain is cleared - * when a new row is read. + *

If an input stream from the previous row is open, it is implicitly + * closed. The ResultSet's warning chain is cleared when a new row is read. * - * @return object constructed from fetched record or null if there - * are no more rows + * @return object constructed from fetched record or null if there are no + * more rows */ public V next () throws SQLException - { -// // if we closed everything up after the last call to next(), -// // table will be null here and we should bail immediately - if (table == null) { + { + // if we closed everything up after the last call to next(), + // table will be null here and we should bail immediately + if (_table == null) { return null; } -// if (nTables == 0) { -// return null; -// } -// do { - if (result == null) { -// if (table.isAbstract) { -// table = table.derived; -// continue; -// } - if (qbeObject != null) { - PreparedStatement qbeStmt; - synchronized(session.preparedStmtHash) { - Object s = session.preparedStmtHash.get(query); - if (s == null) { - qbeStmt= - session.connection.prepareStatement(query); - session.preparedStmtHash.put(query, qbeStmt); - } else { - qbeStmt = (PreparedStatement)s; - } + if (_result == null) { + if (_qbeObject != null) { + PreparedStatement qbeStmt; + synchronized (_table.preparedStmtHash) { + qbeStmt = _table.preparedStmtHash.get(_query); + if (qbeStmt == null) { + qbeStmt = _conn.prepareStatement(_query); + _table.preparedStmtHash.put(_query, qbeStmt); } - synchronized(qbeStmt) { - table.bindQueryVariables( - qbeStmt, qbeObject, qbeMask); - result = qbeStmt.executeQuery(); - qbeStmt.clearParameters(); - } - } else { - if (stmt == null) { - stmt = session.connection.createStatement(); - } - result = stmt.executeQuery(query); - } + } + synchronized (qbeStmt) { + _table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask); + _result = qbeStmt.executeQuery(); + qbeStmt.clearParameters(); + } + } else { + if (_stmt == null) { + _stmt = _conn.createStatement(); + } + _result = _stmt.executeQuery(_query); } - if (result.next()) { - return currObject = table.load(result); - } - result.close(); - result = null; - currObject = null; - table = null; -// table = table.derived; -// } while (--nTables != 0); + } + if (_result.next()) { + return _currObject = _table.load(_result); + } - if (stmt != null) { - stmt.close(); + _result.close(); + _result = null; + _currObject = null; + _table = null; + + if (_stmt != null) { + _stmt.close(); } return null; } @@ -111,77 +107,81 @@ public class Cursor if (spurious > 0) { Log.warning("Cursor.get() quietly tossed " + spurious + " spurious additional records. " + - "[query=" + query + "]."); + "[query=" + _query + "]."); } } return result; } - /** Update current record pointed by cursor. This method can be called - * only after next() method, which returns non-null object. This objects - * is used to update current record fields.

+ /** + * Update current record pointed by cursor. This method can be called only + * after next() method, which returns non-null object. This objects is used + * to update current record fields. * - * If you are going to update or delete selected records, you should add - * "for update" clause to select statement. So parameter of - * jora.Table.select() statement should contain "for update" - * clause: - * record.table.Select("where name='xyz' for update");

- * - * Attention! - * Not all database drivers support update operation with - * cursor. This method will not work with such database drivers. + *

If you are going to update or delete selected records, you should + * add "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: record.table.Select("where name='xyz' for + * update");

+ * + * Attention! Not all database drivers support update + * operation with cursor. This method will not work with such database + * drivers. */ public void update () throws SQLException { - if (currObject == null) { - throw new NoCurrentObjectError(); + if (_currObject == null) { + throw new IllegalStateException("No current object"); } - table.updateVariables(result, currObject); + _table.updateVariables(_result, _currObject); } - /** Delete current record pointed by cursor. This method can be called - * only after next() method, which returns non-null object.

+ /** + * Delete current record pointed by cursor. This method can be called only + * after next() method, which returns non-null object. * - * If you are going to update or delete selected records, you should add - * "for update" clause to select statement. So parameter of - * jora.Table.select() statement should contain "for update" - * clause: - * record.table.Select("where name='xyz' for update");

- * - * Attention! - * Not all database drivers support delete operation with cursor. - * This method will not work with such database drivers. + *

If you are going to update or delete selected records, you should + * add "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: record.table.Select("where name='xyz' for + * update");

+ * + * Attention! Not all database drivers support delete + * operation with cursor. This method will not work with such database + * drivers. */ public void delete () throws SQLException { - if (currObject == null) { - throw new NoCurrentObjectError(); + if (_currObject == null) { + throw new IllegalStateException("No current object"); } - result.deleteRow(); + _result.deleteRow(); } /** - * Close the Cursor, even if we haven't read all the possible - * objects. + * Close the Cursor, even if we haven't read all the possible objects. */ public void close () throws SQLException { - if (stmt != null) { - stmt.close(); // also automatically closes result - result = null; - stmt = null; + if (_result != null) { + _result.close(); + _result = null; + } + if (_stmt != null) { + _stmt.close(); + _stmt = null; } -// nTables = 0; } - /** Extracts no more than maxElements records from database and - * store them into array. It is possible to extract rest records - * by successive next() or toArray() calls. Selected objects should - * have now components of InputStream, Blob or Clob type, because - * their data will be not available after fetching next record. + /** + * Extracts no more than maxElements records from database and store + * them into array. It is possible to extract rest records by successive + * next() or toArray() calls. Selected objects should have now components + * of InputStream, Blob or Clob type, because their data will be not + * available after fetching next record. * * @param maxElements limitation for result array size (and also for number * of fetched records) @@ -198,58 +198,46 @@ public class Cursor return al; } - /** Store all objects returned by SELECT query into a list of Object. - * Selected objects should have now components of InputStream, Blob or - * Clob type, because their data will be not available after fetching - * next record. + /** + * Store all objects returned by SELECT query into a list of Object. + * Selected objects should have now components of InputStream, Blob or Clob + * type, because their data will be not available after fetching next + * record. * * @return Array with objects constructed from fetched records. */ - public ArrayList toArrayList() + public ArrayList toArrayList () throws SQLException { return toArrayList(Integer.MAX_VALUE); } - // Internals - - protected Cursor ( - Table table, Session session, int nTables, String query) + protected Cursor (Table table, Connection conn, String query) { - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; - } - this.table = table; - this.session = session; -// this.nTables = nTables; - this.query = query; + _table = table; + _conn = conn; + _query = query; } - protected Cursor (Table table, Session session, int nTables, V obj, + protected Cursor (Table table, Connection conn, V obj, FieldMask mask, boolean like) - { - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; - } - this.table = table; - this.session = session; -// this.nTables = nTables; - this.like = like; - qbeObject = obj; - qbeMask = mask; - query = table.buildQueryList(obj, mask, like); - stmt = null; + { + _table = table; + _conn = conn; + _like = like; + _qbeObject = obj; + _qbeMask = mask; + _query = table.buildQueryList(obj, mask, like); + _stmt = null; } - - private Table table; - private Session session; -// private int nTables; - private ResultSet result; - private String query; - private Statement stmt; - private V currObject; - private V qbeObject; - private FieldMask qbeMask; - private boolean like; + + protected Table _table; + protected Connection _conn; + protected ResultSet _result; + protected String _query; + protected Statement _stmt; + protected V _currObject, _qbeObject; + protected FieldMask _qbeMask; + protected boolean _like; } diff --git a/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java b/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java index e2baf710..3e3fc05a 100644 --- a/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java +++ b/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java @@ -1,12 +1,22 @@ -//-< FieldDescriptor.java >------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 25-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Table field descriptor -//-------------------------------------------------------------------*--------* +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, 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; @@ -14,10 +24,370 @@ import java.sql.*; import java.math.*; import java.lang.reflect.*; -class FieldDescriptor { +class FieldDescriptor +{ + protected FieldDescriptor (Field field, String name) + { + this.name = name; + this.field = field; + this.scale = -1; + } + + protected final boolean isAtomic () + { + return inType < tClosure; + } + + protected final boolean isCompound () + { + return inType >= tCompound; + } + + protected final boolean isBuiltin () + { + return inType <= t_boolean; + } + + protected final boolean bindVariable ( + PreparedStatement pstmt, Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + pstmt.setByte(column, field.getByte(obj)); + break; + case t_short: + pstmt.setShort(column, field.getShort(obj)); + break; + case t_int: + pstmt.setInt(column, field.getInt(obj)); + break; + case t_long: + pstmt.setLong(column, field.getLong(obj)); + break; + case t_float: + pstmt.setFloat(column, field.getFloat(obj)); + break; + case t_double: + pstmt.setDouble(column, field.getDouble(obj)); + break; + case t_boolean: + pstmt.setBoolean(column, field.getBoolean(obj)); + break; + case tByte: + pstmt.setByte(column, ((Byte)field.get(obj)).byteValue()); + break; + + case tShort: + pstmt.setShort(column, ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + pstmt.setInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + pstmt.setLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + pstmt.setFloat(column, ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + pstmt.setDouble(column,((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + pstmt.setBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + pstmt.setBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + pstmt.setString(column, (String)field.get(obj)); + break; + case tBytes: + pstmt.setBytes(column, (byte[])field.get(obj)); + break; + case tDate: + pstmt.setDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + pstmt.setTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + pstmt.setBinaryStream(column, in, in.available()); + break; + case tBlob: + pstmt.setBlob(column, (Blob)field.get(obj)); + break; + case tClob: + pstmt.setClob(column, (Clob)field.get(obj)); + break; + case tAsString: + pstmt.setString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + pstmt.setBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return bindVariable(pstmt, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean updateVariable ( + ResultSet result, Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + result.updateByte(column, field.getByte(obj)); + break; + case t_short: + result.updateShort(column, field.getShort(obj)); + break; + case t_int: + result.updateInt(column, field.getInt(obj)); + break; + case t_long: + result.updateLong(column, field.getLong(obj)); + break; + case t_float: + result.updateFloat(column, field.getFloat(obj)); + break; + case t_double: + result.updateDouble(column, field.getDouble(obj)); + break; + case t_boolean: + result.updateBoolean(column, field.getBoolean(obj)); + break; + + case tByte: + result.updateByte(column, ((Byte)field.get(obj)).byteValue()); + break; + case tShort: + result.updateShort(column, + ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + result.updateInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + result.updateLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + result.updateFloat(column, + ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + result.updateDouble(column, + ((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + result.updateBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + result.updateBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + result.updateString(column, (String)field.get(obj)); + break; + case tBytes: + result.updateBytes(column, (byte[])field.get(obj)); + break; + case tDate: + result.updateDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + result.updateTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + result.updateTimestamp(column, + (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + result.updateBinaryStream(column, in, in.available()); + break; + case tBlob: + Blob blob = (Blob)field.get(obj); + result.updateBinaryStream(column, + blob.getBinaryStream(), + (int)blob.length()); + break; + case tClob: + Clob clob = (Clob)field.get(obj); + result.updateCharacterStream(column, + clob.getCharacterStream(), + (int)clob.length()); + break; + case tAsString: + result.updateString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + result.updateBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return updateVariable(result, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean loadVariable ( + ResultSet result, Object obj, int column) + throws SQLException, IllegalAccessException + { + switch (inType) { + case t_byte: + field.setByte(obj, result.getByte(column)); + break; + case t_short: + field.setShort(obj, result.getShort(column)); + break; + case t_int: + field.setInt(obj, result.getInt(column)); + break; + case t_long: + field.setLong(obj, result.getLong(column)); + break; + case t_float: + field.setFloat(obj, result.getFloat(column)); + break; + case t_double: + field.setDouble(obj, result.getDouble(column)); + break; + case t_boolean: + field.setBoolean(obj, result.getBoolean(column)); + break; + + case tByte: + byte b = result.getByte(column); + field.set(obj, result.wasNull() ? null : new Byte(b)); + break; + case tShort: + short s = result.getShort(column); + field.set(obj, result.wasNull() ? null : new Short(s)); + break; + case tInteger: + int i = result.getInt(column); + field.set(obj, result.wasNull() ? null : new Integer(i)); + break; + case tLong: + long l = result.getLong(column); + field.set(obj, result.wasNull() ? null : new Long(l)); + break; + case tFloat: + float f = result.getFloat(column); + field.set(obj, result.wasNull() ? null : new Float(f)); + field.setFloat(obj, result.getFloat(column)); + break; + case tDouble: + double d = result.getDouble(column); + field.set(obj, result.wasNull() ? null : new Double(d)); + break; + case tBoolean: + boolean bl = result.getBoolean(column); + field.set(obj, result.wasNull() ? null : new Boolean(bl)); + break; + + case tDecimal: + field.set(obj, result.getBigDecimal(column)); + break; + case tString: + field.set(obj, result.getString(column)); + break; + case tBytes: + field.set(obj, result.getBytes(column)); + break; + case tDate: + field.set(obj, result.getDate(column)); + break; + case tTime: + field.set(obj, result.getTime(column)); + break; + case tTimestamp: + field.set(obj, result.getTimestamp(column)); + break; + case tStream: + field.set(obj, result.getBinaryStream(column)); + break; + case tBlob: + field.set(obj, result.getBlob(column)); + break; + case tClob: + field.set(obj, result.getClob(column)); + break; + case tClosure: + try { + java.io.InputStream input = result.getBinaryStream(column); + java.io.ObjectInputStream in = + new java.io.ObjectInputStream(input); + field.set(obj, in.readObject()); + in.close(); + } catch(ClassNotFoundException ex) { + throw new DataTransferError(ex); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + break; + default: + return false; + } + return true; + } + protected int inType; // type tag for field input (see constants below) - protected int outType; // type tag for field output - protected int scale; // scale for tDecimal type, + protected int outType; // type tag for field output + protected int scale; // scale for tDecimal type, protected String name; // full (compound) name of component protected Field field; // field info from java.lang.reflect @@ -52,379 +422,30 @@ class FieldDescriptor { protected static final int tCompound = 25; protected static final int[] sqlTypeMapping = { - Types.INTEGER, // t_byte - Types.INTEGER, // t_short - Types.INTEGER, // t_int - Types.BIGINT, // t_long - Types.FLOAT, // t_float - Types.DOUBLE, // t_double - Types.BIT, // t_boolean - Types.INTEGER, // tByte - Types.INTEGER, // tShort - Types.INTEGER, // tInteger - Types.BIGINT, // tLong - Types.FLOAT, // tFloat - Types.DOUBLE, // tDouble - Types.BIT, // tBoolean - Types.NUMERIC, // tDecimal - Types.VARCHAR, // tString - Types.VARBINARY,// tBytes - Types.DATE, // tDate - Types.TIME, // tTime - Types.TIMESTAMP, // tTimestamp - Types.LONGVARBINARY, // tStream - Types.LONGVARBINARY, // tBlob - Types.LONGVARCHAR, // tClob - Types.VARCHAR, // tAsString - Types.LONGVARBINARY // tClosure + Types.INTEGER, // t_byte + Types.INTEGER, // t_short + Types.INTEGER, // t_int + Types.BIGINT, // t_long + Types.FLOAT, // t_float + Types.DOUBLE, // t_double + Types.BIT, // t_boolean + Types.INTEGER, // tByte + Types.INTEGER, // tShort + Types.INTEGER, // tInteger + Types.BIGINT, // tLong + Types.FLOAT, // tFloat + Types.DOUBLE, // tDouble + Types.BIT, // tBoolean + Types.NUMERIC, // tDecimal + Types.VARCHAR, // tString + Types.VARBINARY,// tBytes + Types.DATE, // tDate + Types.TIME, // tTime + Types.TIMESTAMP, // tTimestamp + Types.LONGVARBINARY, // tStream + Types.LONGVARBINARY, // tBlob + Types.LONGVARCHAR, // tClob + Types.VARCHAR, // tAsString + Types.LONGVARBINARY // tClosure }; - - protected FieldDescriptor(Field field, String name) { - this.name = name; - this.field = field; - this.scale = -1; - } - - protected final boolean isAtomic() { return inType < tClosure; } - - protected final boolean isCompound() { return inType >= tCompound; } - - protected final boolean isBuiltin() { return inType <= t_boolean; } - - - protected final boolean bindVariable(PreparedStatement pstmt, - Object obj, int column) - throws SQLException - { - try { - switch (outType) { - case t_byte: - pstmt.setByte(column, field.getByte(obj)); - break; - case t_short: - pstmt.setShort(column, field.getShort(obj)); - break; - case t_int: - pstmt.setInt(column, field.getInt(obj)); - break; - case t_long: - pstmt.setLong(column, field.getLong(obj)); - break; - case t_float: - pstmt.setFloat(column, field.getFloat(obj)); - break; - case t_double: - pstmt.setDouble(column, field.getDouble(obj)); - break; - case t_boolean: - pstmt.setBoolean(column, field.getBoolean(obj)); - break; - case tByte: - pstmt.setByte(column, ((Byte)field.get(obj)).byteValue()); - break; - case tShort: - pstmt.setShort(column, ((Short)field.get(obj)).shortValue()); - break; - case tInteger: - pstmt.setInt(column, ((Integer)field.get(obj)).intValue()); - break; - case tLong: - pstmt.setLong(column, ((Long)field.get(obj)).longValue()); - break; - case tFloat: - pstmt.setFloat(column, ((Float)field.get(obj)).floatValue()); - break; - case tDouble: - pstmt.setDouble(column,((Double)field.get(obj)).doubleValue()); - break; - case tBoolean: - pstmt.setBoolean(column, - ((Boolean)field.get(obj)).booleanValue()); - break; - - case tDecimal: - pstmt.setBigDecimal(column, (BigDecimal)field.get(obj)); - break; - case tString: - pstmt.setString(column, (String)field.get(obj)); - break; - case tBytes: - pstmt.setBytes(column, (byte[])field.get(obj)); - break; - case tDate: - pstmt.setDate(column, (java.sql.Date)field.get(obj)); - break; - case tTime: - pstmt.setTime(column, (java.sql.Time)field.get(obj)); - break; - case tTimestamp: - pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj)); - break; - case tStream: - java.io.InputStream in = (java.io.InputStream)field.get(obj); - pstmt.setBinaryStream(column, in, in.available()); - break; - case tBlob: - pstmt.setBlob(column, (Blob)field.get(obj)); - break; - case tClob: - pstmt.setClob(column, (Clob)field.get(obj)); - break; - case tAsString: - pstmt.setString(column, field.get(obj).toString()); - break; - case tClosure: - // There is no reason to use piped streams because - // we need to pass total number of bytes to JDBC driver - java.io.ByteArrayOutputStream out = - new java.io.ByteArrayOutputStream(); - java.io.ObjectOutputStream clu = - new java.io.ObjectOutputStream(out); - clu.writeObject(field.get(obj)); - clu.close(); - pstmt.setBytes(column, out.toByteArray()); - break; - default: - return false; - } - } catch(SQLException ex) { - if (outType != tClosure && outType != tAsString) { - outType = tAsString; - return bindVariable(pstmt, obj, column); - } else { - throw ex; - } - } catch(IllegalAccessException ex) { - ex.printStackTrace(); - throw new IllegalAccessError(); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - return true; - } - - protected final boolean updateVariable(ResultSet result, - Object obj, int column) - throws SQLException - { - try { - switch (outType) { - case t_byte: - result.updateByte(column, field.getByte(obj)); - break; - case t_short: - result.updateShort(column, field.getShort(obj)); - break; - case t_int: - result.updateInt(column, field.getInt(obj)); - break; - case t_long: - result.updateLong(column, field.getLong(obj)); - break; - case t_float: - result.updateFloat(column, field.getFloat(obj)); - break; - case t_double: - result.updateDouble(column, field.getDouble(obj)); - break; - case t_boolean: - result.updateBoolean(column, field.getBoolean(obj)); - break; - case tByte: - result.updateByte(column, ((Byte)field.get(obj)).byteValue()); - break; - case tShort: - result.updateShort(column, - ((Short)field.get(obj)).shortValue()); - break; - case tInteger: - result.updateInt(column, ((Integer)field.get(obj)).intValue()); - break; - case tLong: - result.updateLong(column, ((Long)field.get(obj)).longValue()); - break; - case tFloat: - result.updateFloat(column, - ((Float)field.get(obj)).floatValue()); - break; - case tDouble: - result.updateDouble(column, - ((Double)field.get(obj)).doubleValue()); - break; - case tBoolean: - result.updateBoolean(column, - ((Boolean)field.get(obj)).booleanValue()); - break; - - case tDecimal: - result.updateBigDecimal(column, (BigDecimal)field.get(obj)); - break; - case tString: - result.updateString(column, (String)field.get(obj)); - break; - case tBytes: - result.updateBytes(column, (byte[])field.get(obj)); - break; - case tDate: - result.updateDate(column, (java.sql.Date)field.get(obj)); - break; - case tTime: - result.updateTime(column, (java.sql.Time)field.get(obj)); - break; - case tTimestamp: - result.updateTimestamp(column, - (java.sql.Timestamp)field.get(obj)); - break; - case tStream: - java.io.InputStream in = (java.io.InputStream)field.get(obj); - result.updateBinaryStream(column, in, in.available()); - break; - case tBlob: - Blob blob = (Blob)field.get(obj); - result.updateBinaryStream(column, - blob.getBinaryStream(), - (int)blob.length()); - break; - case tClob: - Clob clob = (Clob)field.get(obj); - result.updateCharacterStream(column, - clob.getCharacterStream(), - (int)clob.length()); - break; - case tAsString: - result.updateString(column, field.get(obj).toString()); - break; - case tClosure: - // There is no reason to use piped streams because - // we need to pass total number of bytes to JDBC driver - java.io.ByteArrayOutputStream out = - new java.io.ByteArrayOutputStream(); - java.io.ObjectOutputStream clu = - new java.io.ObjectOutputStream(out); - clu.writeObject(field.get(obj)); - clu.close(); - result.updateBytes(column, out.toByteArray()); - break; - default: - return false; - } - } catch(SQLException ex) { - if (outType != tClosure && outType != tAsString) { - outType = tAsString; - return updateVariable(result, obj, column); - } else { - throw ex; - } - } catch(IllegalAccessException ex) { - ex.printStackTrace(); - throw new IllegalAccessError(); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - return true; - } - - protected final boolean loadVariable(ResultSet result, - Object obj, int column) - throws SQLException, IllegalAccessException - { - switch (inType) { - case t_byte: - field.setByte(obj, result.getByte(column)); - break; - case t_short: - field.setShort(obj, result.getShort(column)); - break; - case t_int: - field.setInt(obj, result.getInt(column)); - break; - case t_long: - field.setLong(obj, result.getLong(column)); - break; - case t_float: - field.setFloat(obj, result.getFloat(column)); - break; - case t_double: - field.setDouble(obj, result.getDouble(column)); - break; - case t_boolean: - field.setBoolean(obj, result.getBoolean(column)); - break; - - case tByte: - byte b = result.getByte(column); - field.set(obj, result.wasNull() ? null : new Byte(b)); - break; - case tShort: - short s = result.getShort(column); - field.set(obj, result.wasNull() ? null : new Short(s)); - break; - case tInteger: - int i = result.getInt(column); - field.set(obj, result.wasNull() ? null : new Integer(i)); - break; - case tLong: - long l = result.getLong(column); - field.set(obj, result.wasNull() ? null : new Long(l)); - break; - case tFloat: - float f = result.getFloat(column); - field.set(obj, result.wasNull() ? null : new Float(f)); - field.setFloat(obj, result.getFloat(column)); - break; - case tDouble: - double d = result.getDouble(column); - field.set(obj, result.wasNull() ? null : new Double(d)); - break; - case tBoolean: - boolean bl = result.getBoolean(column); - field.set(obj, result.wasNull() ? null : new Boolean(bl)); - break; - - case tDecimal: - field.set(obj, result.getBigDecimal(column)); - break; - case tString: - field.set(obj, result.getString(column)); - break; - case tBytes: - field.set(obj, result.getBytes(column)); - break; - case tDate: - field.set(obj, result.getDate(column)); - break; - case tTime: - field.set(obj, result.getTime(column)); - break; - case tTimestamp: - field.set(obj, result.getTimestamp(column)); - break; - case tStream: - field.set(obj, result.getBinaryStream(column)); - break; - case tBlob: - field.set(obj, result.getBlob(column)); - break; - case tClob: - field.set(obj, result.getClob(column)); - break; - case tClosure: - try { - java.io.InputStream input = result.getBinaryStream(column); - java.io.ObjectInputStream in = - new java.io.ObjectInputStream(input); - field.set(obj, in.readObject()); - in.close(); - } catch(ClassNotFoundException ex) { - throw new DataTransferError(ex); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - break; - default: - return false; - } - return true; - } } - diff --git a/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java b/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java deleted file mode 100644 index a0e141a2..00000000 --- a/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java +++ /dev/null @@ -1,23 +0,0 @@ -//-< NoCurrentObjectError.java >-------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Exception raised when UPDATE/REMOVE operation is applied to Cursor -// with no current object -//-------------------------------------------------------------------*--------* - -package com.samskivert.jdbc.jora; - -/** Error raised when update/remove operation is applied to - * cursor with no current object (before first call of next() - * method). - */ -public class NoCurrentObjectError extends java.lang.Error { - NoCurrentObjectError() { - super("Cursor doesn't specify current object"); - } -} - diff --git a/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java b/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java deleted file mode 100644 index 4ed703dc..00000000 --- a/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java +++ /dev/null @@ -1,22 +0,0 @@ -//-< NoPrimaryKeyError.java >----------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Exception raised when UPDATE/REMOVE operation is appllied to Table with -// no primary key -//-------------------------------------------------------------------*--------* - -package com.samskivert.jdbc.jora; - -/** Error raised when unpdate/remove operation is invoked for the table - * with no correct primary key defined (key was not specified or - * type of the key component is not atomic). - */ -public class NoPrimaryKeyError extends java.lang.Error { - NoPrimaryKeyError(Table table) { - super("Table " + table.name + " has no atomic primary key"); - } -} diff --git a/src/java/com/samskivert/jdbc/jora/SQLError.java b/src/java/com/samskivert/jdbc/jora/SQLError.java deleted file mode 100644 index d2615198..00000000 --- a/src/java/com/samskivert/jdbc/jora/SQLError.java +++ /dev/null @@ -1,21 +0,0 @@ -//-< SQLError.java >-------------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Database error -//-------------------------------------------------------------------*--------* - -package com.samskivert.jdbc.jora; - -/** Database error. Exception SQLException was catched by JORA. - */ -public class SQLError extends java.lang.RuntimeException { - java.sql.SQLException ex; - SQLError(java.sql.SQLException x) { - super("Database session aborted due to critical error"); - ex = x; - } -} diff --git a/src/java/com/samskivert/jdbc/jora/Session.java b/src/java/com/samskivert/jdbc/jora/Session.java deleted file mode 100644 index 123a60a4..00000000 --- a/src/java/com/samskivert/jdbc/jora/Session.java +++ /dev/null @@ -1,198 +0,0 @@ -//-< Session.java >--------------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Database session abstraction -//-------------------------------------------------------------------*--------* - -package com.samskivert.jdbc.jora; - -import java.util.*; -import java.sql.*; - -import com.samskivert.Log; - -/** - * This class is reposnsible for establishing connection with database - * and handling database errors. - */ -public class Session { - public Connection connection; - - /** Session consructor - * - * @param driverClass class of database driver - */ - public Session(String driverClass) - { - driver = driverClass; - preparedStmtHash = new Hashtable(); - connectionID = 0; - } - - /** Construct a session with a pre-existing connection instance. In - * this case, {@link #open} should not be called on this session. - * - * @param connection the connection to use. - */ - public Session(Connection connection) - { - connectionID = 0; - preparedStmtHash = new Hashtable(); - setConnection(connection); - } - - /** Session consructor for ODBC bridge driver - */ - public Session() { this("sun.jdbc.odbc.JdbcOdbcDriver"); } - - /** Sets the connection that should be used by this session. - */ - public void setConnection(Connection conn) - { - // only up the connection id if this is a new connection - if (connection != conn) { - // clear out our prepared statement hash because we've got a - // new connection - for (PreparedStatement pstmt : preparedStmtHash.values()) { - try { - pstmt.close(); - } catch (SQLException sqe) { - Log.warning("Error closing cached prepared statement " + - "[error=" + sqe + "]."); - } - } - preparedStmtHash.clear(); - - // switch to our new connection - connection = conn; - connectionID += 1; - } - } - - /** Handler of database session errors. Programmer should override - * this method in derived class in order to provide application - * dependent error handling. - * - * @param ex exception thrown by some of JDBC methods - */ - public void handleSQLException(SQLException ex) { - // A SQLException was generated. Catch it and - // display the error information. Note that there - // could be multiple error objects chained - // together - System.out.println ("*** SQLException caught ***"); - SQLException x = ex; - while (ex != null) { - System.out.println("SQLState: " + ex.getSQLState ()); - System.out.println("Message: " + ex.getMessage ()); - System.out.println("Vendor: " + ex.getErrorCode ()); - ex = ex.getNextException(); - System.out.println (""); - } - throw new SQLError(x); // terminate program execution - } - - /** Open database session. - * Attempt to establish a connection to the given database URL. - * The DriverManager attempts to select an appropriate driver from - * the set of registered JDBC drivers. - * - * @param dataSource a database url of the form - * jdbc:subprotocol:subname - * @param user the database user on whose behalf the Connection is - * being made - * @param password the user's password - * @return true if session is succesfully openned, false otherwise - */ - public boolean open(String dataSource, String user, String password) - throws SQLException - { - try { - Class.forName(driver); - - connection = - DriverManager.getConnection(dataSource, user, password); - connectionID += 1; - } -// catch(SQLException ex) { -// handleSQLException(ex); -// return false; -// } - catch(ClassNotFoundException ex) { - return false; - } - return true; - } - - /** Close database session and release all resources holded by session. - */ - public void close() - throws SQLException - { - for (PreparedStatement pstmt : preparedStmtHash.values()) { - pstmt.close(); - } - preparedStmtHash.clear(); - if (connection != null) { - connection.close(); - } - } - - /** - * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, - * SQL statements that return nothing such as SQL DDL statements - * can be executed. - * - * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL - * statement that returns nothing - * @return either the row count for INSERT, UPDATE or DELETE or 0 - * for SQL statements that return nothing - */ - public int execute(String sql) - throws SQLException - { -// try { - Statement stmt = connection.createStatement(); - int result = stmt.executeUpdate(sql); - stmt.close(); - return result; -// } catch(SQLException ex) { handleSQLException(ex); } -// return -1; - } - - /** - * Commit makes all changes made since the previous - * commit/rollback permanent and releases any database locks - * currently held by the Connection. This method should only be - * used when auto commit has been disabled. - */ - public void commit() - throws SQLException - { -// try { - connection.commit(); -// } catch(SQLException ex) { handleSQLException(ex); } - } - - /** - * Rollback drops all changes made since the previous - * commit/rollback and releases any database locks currently held - * by the Connection. This method should only be used when auto - * commit has been disabled. - */ - public void rollback() - throws SQLException - { -// try { - connection.rollback(); -// } catch(SQLException ex) { handleSQLException(ex); } - } - - protected String driver; // driver class name - protected Hashtable preparedStmtHash; - protected int connectionID; -} diff --git a/src/java/com/samskivert/jdbc/jora/SessionThread.java b/src/java/com/samskivert/jdbc/jora/SessionThread.java deleted file mode 100644 index bf483f78..00000000 --- a/src/java/com/samskivert/jdbc/jora/SessionThread.java +++ /dev/null @@ -1,148 +0,0 @@ -//-< SessionThread.java >--------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 18-Jun-99 K.A. Knizhnik * / [] \ * -// Last update: 18-Jun-99 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Thread associated with database session -//-------------------------------------------------------------------*--------* - -package com.samskivert.jdbc.jora; - -/** - * Class representing thread associated with users database session. - * If there is single database session opened by application, that it is - * possible to associate it with Table object statically. Otherwise it is - * needed either to explicitly specify session object in each insert,select - * or update statement or associate session with thread by means of - * SessionThread class. - */ -public class SessionThread extends Thread { - Session session; - - /** - * Allocates a new SessionThread object and associate it with - * the specified session. This constructor has the same effect as - * SessionThread(session, null, null, - * gname), where gname is - * a newly generated name. Automatically generated names are of the - * form "Thread-"+n, where n is an integer. - *

- * Threads created this way must have overridden their - * run() method to actually do anything. - * - * @param session user database session associated with this thread - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session) { - this.session = session; - } - - /** - * Allocates a new SessionThread object and associate it with - * the specified session. This constructor has the same effect as - * SessionThread(session, target, null, - * gname), where gname is - * a newly generated name. Automatically generated names are of the - * form "Thread-"+n, where n is an integer. - *

- * Threads created this way must have overridden their - * run() method to actually do anything. - * - * @param session user database session associated with this thread - * @param target the object whose run method is called. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, Runnable target) { - super(target); - this.session = session; - } - - /** - * Allocates a new SessionThread object and associate it with - * the specified session. This constructor has the same effect as - * SessionThread(session, target, group, - * gname), where gname is - * a newly generated name. Automatically generated names are of the - * form "Thread-"+n, where n is an integer. - *

- * Threads created this way must have overridden their - * run() method to actually do anything. - * - * @param session user database session associated with this thread - * @param group the thread group. - * @param target the object whose run method is called. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, ThreadGroup group, Runnable target) { - super(target); - this.session = session; - } - - /** - * Allocates a new SessionThread object. This constructor has - * the same effect as SessionThread(session, null, null, name). - * - * @param session user database session associated with this thread - * @param name the name of the new thread. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, String name) { - super(name); - this.session = session; - } - - /** - * Allocates a new SessionThread object. This constructor has - * the same effect as SessionThread(session, group, null, name). - * - * @param session user database session associated with this thread - * @param group the thread group. - * @param name the name of the new thread. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, ThreadGroup group, String name) { - super(group, name); - this.session = session; - } - - /** - * Allocates a new SessionThread object. This constructor has - * the same effect as SessionThread(session, null, target, name) * - * @param session user database session associated with this thread - * @param target the object whose run method is called. - * @param name the name of the new thread. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, Runnable target, String name) { - super(target, name); - this.session = session; - } - - /** - * Allocates a new SessionThread object so that it has - * target as its run object, has the specified - * name as its name, and belongs to the thread group - * referred to by group. - *

- * @param session user database session associated with this thread - * @param group the thread group. - * @param target the object whose run method is called. - * @param name the name of the new thread. - * @see java.lang.Thread#Thread(java.lang.ThreadGroup, - * java.lang.Runnable, java.lang.String) - */ - public SessionThread(Session session, ThreadGroup group, Runnable target, - String name) - { - super(group, target, name); - this.session = session; - } -} diff --git a/src/java/com/samskivert/jdbc/jora/Table.java b/src/java/com/samskivert/jdbc/jora/Table.java index 10cdd53b..875983b8 100644 --- a/src/java/com/samskivert/jdbc/jora/Table.java +++ b/src/java/com/samskivert/jdbc/jora/Table.java @@ -1,12 +1,22 @@ -//-< Table.java >----------------------------------------------------*--------* -// JORA Version 2.0 (c) 1998 GARRET * ? * -// (Java Object Relational Adapter) * /\| * -// * / \ * -// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * -// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * -//-------------------------------------------------------------------*--------* -// Class representing database table -//-------------------------------------------------------------------*--------* +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, 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; @@ -14,579 +24,416 @@ import java.util.*; import java.sql.*; import java.lang.reflect.*; +import com.samskivert.jdbc.JDBCUtil; import com.samskivert.util.StringUtil; -/** Table class is used to establish mapping between corteges of database - * tables and Java classes. This class is responsible for constructing - * SQL statements for extracting, updating and deleting records of the - * database table. +/** + * Used to establish mapping between corteges of database tables and Java + * classes. This class is responsible for constructing SQL statements for + * extracting, updating and deleting records of the database table. */ public class Table { - /** Constructor for table object. Make association between Java class - * and database table. + /** + * Constructor for table object. Make association between Java class and + * database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class - * @param s session, which should be opened before first access to the table * @param key table's primary key. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. + * operations to locate record in the table. * @param mixedCaseConvert whether or not to convert mixed case field * names into underscore separated uppercase column names. */ - public Table(Class clazz, String tableName, Session s, String key, - boolean mixedCaseConvert) { - String[] keys = {key}; - init(clazz, tableName, s, keys, mixedCaseConvert); - } - - /** Constructor for table object. Make association between Java class - * and database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param key table's primary key. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - * @param s session, which should be opened before first access to the table - */ - public Table(Class clazz, String tableName, Session s, String key) { - String[] keys = {key}; - init(clazz, tableName, s, keys, false); - } - - /** Constructor for table object. Make association between Java class - * and database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param keys table primary keys. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - * @param s session, which should be opened before first access to the table - */ - public Table(Class clazz, String tableName, Session s, String[] keys) + public Table (Class clazz, String tableName, String key, + boolean mixedCaseConvert) { - init(clazz, tableName, s, keys, false); + String[] keys = {key}; + init(clazz, tableName, keys, mixedCaseConvert); } - /** Constructor for table object. Make association between Java class - * and database table. + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param key table's primary key. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + */ + public Table (Class clazz, String tableName, String key) { + String[] keys = {key}; + init(clazz, tableName, keys, false); + } + + /** + * Constructor for table object. Make association between Java class and + * database table. * * @param clazz the class that represents a row entry. * @param tableName name of database table mapped on this Java class * @param keys table primary keys. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - * @param s session, which should be opened before first access to the table + * operations to locate record in the table. + */ + public Table (Class clazz, String tableName, String[] keys) + { + init(clazz, tableName, keys, false); + } + + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param keys table primary keys. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. * @param mixedCaseConvert whether or not to convert mixed case field * names into underscore separated uppercase column names. */ - public Table(Class clazz, String tableName, Session s, String[] keys, - boolean mixedCaseConvert) { - init(clazz, tableName, s, keys, mixedCaseConvert); + public Table (Class clazz, String tableName, String[] keys, + boolean mixedCaseConvert) + { + init(clazz, tableName, keys, mixedCaseConvert); } - /** Select records from database table according to search condition + /** + * Select records from database table according to search condition * - * @param condition valid SQL condition expression started with WHERE - * or empty string if all records should be fetched. + * @param condition valid SQL condition expression started with WHERE or + * empty string if all records should be fetched. */ - public final Cursor select(String condition) { + public final Cursor select (Connection conn, String condition) + { String query = "select " + listOfFields + " from " + name + " " + condition; - return new Cursor(this, session, 1, query); + return new Cursor(this, conn, query); } - /** Select records from database table according to search condition - * including the specified (comma separated) extra tables into the - * SELECT clause to facilitate a join in determining the key. + /** + * Select records from database table according to search condition + * including the specified (comma separated) extra tables into the SELECT + * clause to facilitate a join in determining the key. * - * @param tables the (comma separated) names of extra tables to - * include in the SELECT clause. + * @param tables the (comma separated) names of extra tables to include in + * the SELECT clause. * @param condition valid SQL condition expression started with WHERE. */ - public final Cursor select(String tables, String condition) { + public final Cursor select (Connection conn, String tables, + String condition) + { String query = "select " + qualifiedListOfFields + " from " + name + "," + tables + " " + condition; - return new Cursor(this, session, 1, query); + return new Cursor(this, conn, query); } - /** Select records from database table according to search condition - * including the specified (comma separated) extra tables into the - * SELECT clause to facilitate a join in determining the key. To - * facilitate situations where data from multiple tables is being - * combined into a single object, the fields will not be qualified - * with the primary table name. + /** + * Select records from database table according to search condition + * including the specified (comma separated) extra tables into the SELECT + * clause to facilitate a join in determining the key. To facilitate + * situations where data from multiple tables is being combined into a + * single object, the fields will not be qualified with the primary table + * name. * - * @param tables the (comma separated) names of extra tables to - * include in the SELECT clause. + * @param tables the (comma separated) names of extra tables to include in + * the SELECT clause. * @param condition valid SQL condition expression started with WHERE. */ - public final Cursor join(String tables, String condition) { - String query = "select " + listOfFields + - " from " + name + "," + tables + " " + condition; - return new Cursor(this, session, 1, query); + public final Cursor join (Connection conn, String tables, + String condition) + { + String query = "select " + listOfFields + + " from " + name + "," + tables + " " + condition; + return new Cursor(this, conn, query); } - /** Like {@link #join} but does a straight join with the specified - * table. */ - public final Cursor straightJoin(String table, String condition) { + /** + * Like {@link #join} but does a straight join with the specified table. + */ + public final Cursor straightJoin (Connection conn, String table, + String condition) + { String query = "select " + listOfFields + " from " + name + " straight_join " + table + " " + condition; - return new Cursor(this, session, 1, query); + return new Cursor(this, conn, query); } - /** Select records from database table according to search condition + /** + * Select records from database table using obj object as template. * - * @param condition valid SQL condition expression started with WHERE - * or empty string if all records should be fetched. - * @param session user database session + * @param obj example object for search: selected objects should match all + * non-null fields. */ - public final Cursor select(String condition, Session session) { - String query = "select " + listOfFields + " from " + name + - " " + condition; - return new Cursor(this, session, 1, query); + public final Cursor queryByExample (Connection conn, T obj) + { + return new Cursor(this, conn, obj, null, false); } - /** Select records from database table using obj object as - * template. - * - * @param obj example object for search: selected objects should match - * all non-null fields. - */ - public final Cursor queryByExample(T obj) { - return new Cursor(this, session, 1, obj, null, false); - } - - /** Select records from database table using obj object as - * template for selection. + /** + * Select records from database table using obj object as template + * for selection. * * @param obj example object for search. - * @param mask field mask indicating which fields in the example - * object should be used when building the query. + * @param mask field mask indicating which fields in the example object + * should be used when building the query. */ - public final Cursor queryByExample(T obj, FieldMask mask) { - return new Cursor(this, session, 1, obj, mask, false); - } - - /** Select records from database table using obj object as - * template for selection. All non-builtin fields of this object, - * which are not null, are compared with correspondent table values. - * - * @param obj object for construction search condition: selected - * objects should match all non-null fields of specified object. - * @param session user database session. - */ - public final Cursor queryByExample(T obj, Session session) { - return queryByExample(obj, session, null); - } - - /** Select records from database table using obj object as - * template for selection. - * - * @param obj example object for search. - * @param session user database session. - * @param mask field mask indicating which fields in the example - * object should be used when building the query. - */ - public final Cursor queryByExample( - T obj, Session session, FieldMask mask) { - return new Cursor(this, session, 1, obj, mask, false); + public final Cursor queryByExample (Connection conn, T obj, + FieldMask mask) + { + return new Cursor(this, conn, obj, mask, false); } /** * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % - * in to do matching. + * matched using 'like' instead of equals, which allows you to send % in to + * do matching. */ - public final Cursor queryByLikeExample(T obj) { - return new Cursor(this, session, 1, obj, null, true); + public final Cursor queryByLikeExample (Connection conn, T obj) + { + return new Cursor(this, conn, obj, null, true); } /** * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % - * in to do matching. + * matched using 'like' instead of equals, which allows you to send % in to + * do matching. */ - public final Cursor queryByLikeExample(T obj, FieldMask mask) { - return new Cursor(this, session, 1, obj, mask, true); + public final Cursor queryByLikeExample (Connection conn, T obj, + FieldMask mask) + { + return new Cursor(this, conn, obj, mask, true); } /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % - * in to do matching. - */ - public final Cursor queryByLikeExample(T obj, Session session) { - return queryByLikeExample(obj, session, null); - } - - /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % - * in to do matching. - */ - public final Cursor queryByLikeExample( - T obj, Session session, FieldMask mask) { - return new Cursor(this, session, 1, obj, mask, true); - } - - /** Insert new record in the table. Values of inserted record fields - * are taken from specifed object. + * Insert new record in the table. Values of inserted record fields are + * taken from specifed object. * * @param obj object specifing values of inserted record fields */ - public void insert(T obj) + public synchronized void insert (Connection conn, T obj) throws SQLException { - insert(obj, session); + checkUpdateConnection(conn); + if (insertStmt == null) { + String sql = "insert into " + name + " (" + + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + insertStmt = conn.prepareStatement(sql); + } + bindUpdateVariables(insertStmt, obj, null); + insertStmt.executeUpdate(); + insertStmt.clearParameters(); } - /** Insert new record in the table using specified database session. - * Values of inserted record fields - * are taken from specifed object. - * - * @param obj object specifing values of inserted record fields - * @param session user database session - */ - public synchronized void insert(T obj, Session session) - throws SQLException - { - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; - } -// try { - checkConnection(session); - if (insertStmt == null) { - String sql = "insert into " + name + " (" - + listOfFields + ") values (?"; - for (int i = 1; i < nColumns; i++) { - sql += ",?"; - } - sql += ")"; - insertStmt = session.connection.prepareStatement(sql); - } - bindUpdateVariables(insertStmt, obj, null); - insertStmt.executeUpdate(); - insertStmt.clearParameters(); -// } catch(SQLException ex) { session.handleSQLException(ex); } - } - - /** Insert several new records in the table. Values of inserted records - * fields are taken from objects of specified array. + /** + * Insert several new records in the table. Values of inserted records + * fields are taken from objects of specified array. * * @param objects array with objects specifing values of inserted record * fields */ - public void insert(T[] objects) + public synchronized void insert (Connection conn, T[] objects) throws SQLException { - insert(objects, session); + checkUpdateConnection(conn); + if (insertStmt == null) { + String sql = "insert into " + name + " (" + + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + insertStmt = conn.prepareStatement(sql); + } + for (int i = 0; i < objects.length; i++) { + bindUpdateVariables(insertStmt, objects[i], null); + insertStmt.addBatch(); + } + insertStmt.executeBatch(); + insertStmt.clearParameters(); } - /** Insert several new records in the table. Values of inserted records - * fields are taken from objects of specified array. - * - * @param objects array with objects specifing values of inserted record - * fields - * @param session user database session - */ - public synchronized void insert(T[] objects, Session session) - throws SQLException - { - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; - } -// try { - checkConnection(session); - if (insertStmt == null) { - String sql = "insert into " + name + " (" - + listOfFields + ") values (?"; - for (int i = 1; i < nColumns; i++) { - sql += ",?"; - } - sql += ")"; - insertStmt = session.connection.prepareStatement(sql); - } - for (int i = 0; i < objects.length; i++) { - bindUpdateVariables(insertStmt, objects[i], null); - insertStmt.addBatch(); - } - insertStmt.executeBatch(); - insertStmt.clearParameters(); -// } 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(T,FieldMask)}. + /** + * Returns a field mask that can be configured and used to update subsets + * of entire objects via calls to {@link #update(T,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. + /** + * 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. * * @param obj object specifing value of primary key and new values of - * updated record fields + * updated record fields * * @return number of objects actually updated */ - public int update(T obj) + public int update (Connection conn, T obj) throws SQLException { - return update(obj, null, session); + return update(conn, obj, 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. + /** + * 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. + * 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(T obj, FieldMask mask) + public synchronized int update (Connection conn, T obj, FieldMask mask) throws SQLException { - return update(obj, mask, 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. - * - * @param obj object specifing value of primary key and new values of - * updated record fields - * @param session user database session - * - * @return number of objects actually updated - */ - public synchronized int update(T 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(T obj, FieldMask mask, Session session) - throws SQLException - { - if (primaryKeys == null) { - throw new NoPrimaryKeyError(this); - } - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; - } int nUpdated = 0; -// try { - checkConnection(session); - 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) + checkUpdateConnection(conn); + 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 = conn.prepareStatement(sql); + } else { + // otherwise we can use our "full-object-update" statement + if (updateStmt == null) { + String sql = "update " + name + " set " + listOfAssignments + 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; + updateStmt = conn.prepareStatement(sql); } - // bind the update variables - int column = bindUpdateVariables(ustmt, obj, mask); - // bind the keys - for (int i = 0; i < primaryKeys.length; i++) { - int fidx = primaryKeyIndices[i]; - fields[fidx].bindVariable(ustmt, obj, column+i+1); - } - nUpdated = ustmt.executeUpdate(); - ustmt.clearParameters(); -// } catch(SQLException ex) { session.handleSQLException(ex); } + ustmt = updateStmt; + } + // bind the update variables + int column = bindUpdateVariables(ustmt, obj, mask); + // bind the keys + for (int i = 0; i < primaryKeys.length; i++) { + int fidx = primaryKeyIndices[i]; + fields[fidx].bindVariable(ustmt, obj, column+i+1); + } + nUpdated = ustmt.executeUpdate(); + ustmt.clearParameters(); return nUpdated; } - /** Update set of records in the table using table's primary key to locate - * record in the table and values of fields of objects from sepecifed - * array objects to alter record fields. + /** + * Update set of records in the table using table's primary key to locate + * record in the table and values of fields of objects from sepecifed array + * objects to alter record fields. * * @param objects array of objects specifing primiray keys and and new * values of updated record fields * * @return number of objects actually updated */ - public int update(T[] objects) - throws SQLException - { - return update(objects, session); - } - - /** Update set of records in the table using table's primary key to locate - * record in the table and values of fields of objects from sepecifed - * array objects to alter record fields. - * - * @param objects array of objects specifing primiray keys and and new - * values of updated record fields - * @param session user database session - * - * @return number of objects actually updated - */ - public synchronized int update(T[] objects, Session session) + public synchronized int update (Connection conn, T[] objects) throws SQLException { if (primaryKeys == null) { - throw new NoPrimaryKeyError(this); - } - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; + throw new IllegalStateException( + "No primary key for table " + name + "."); } int nUpdated = 0; -// try { - checkConnection(session); - if (updateStmt == null) { - String sql = "update " + name + " set " + listOfAssignments - + 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++) { - int fidx = primaryKeyIndices[j]; - fields[fidx].bindVariable( - updateStmt, objects[i], column+1+j); - } - updateStmt.addBatch(); - } - int rc[] = updateStmt.executeBatch(); - for (int k = 0; k < rc.length; k++) { - nUpdated += rc[k]; - } - updateStmt.clearParameters(); -// } catch(SQLException ex) { session.handleSQLException(ex); } + checkUpdateConnection(conn); + if (updateStmt == null) { + String sql = "update " + name + " set " + listOfAssignments + + buildUpdateWhere(); + updateStmt = conn.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++) { + int fidx = primaryKeyIndices[j]; + fields[fidx].bindVariable( + updateStmt, objects[i], column+1+j); + } + updateStmt.addBatch(); + } + int rc[] = updateStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nUpdated += rc[k]; + } + updateStmt.clearParameters(); return nUpdated; } - /** Delete record with specified value of primary key from the table. + /** + * Delete record with specified value of primary key from the table. * * @param obj object containing value of primary key. - * - * @return number of objects actually deleted */ - public int delete(T obj) - throws SQLException - { - return delete(obj, session); - } - - /** Delete record with specified value of primary key from the table. - * - * @param obj object containing value of primary key. - * @param session user database session - */ - public synchronized int delete(T obj, Session session) + public synchronized int delete (Connection conn, T obj) throws SQLException { if (primaryKeys == null) { - throw new NoPrimaryKeyError(this); - } - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; + throw new IllegalStateException( + "No primary key for table " + name + "."); } int nDeleted = 0; -// try { - checkConnection(session); - if (deleteStmt == null) { - String sql = "delete from " + name + - " where " + primaryKeys[0] + " = ?"; - for (int i = 1; i < primaryKeys.length; i++) { - sql += " and " + primaryKeys[i] + " = ?"; - } - deleteStmt = session.connection.prepareStatement(sql); - } - for (int i = 0; i < primaryKeys.length; i++) { - fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1); - } - nDeleted = deleteStmt.executeUpdate(); - deleteStmt.clearParameters(); -// } catch(SQLException ex) { session.handleSQLException(ex); } + checkUpdateConnection(conn); + if (deleteStmt == null) { + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + deleteStmt = conn.prepareStatement(sql); + } + for (int i = 0; i < primaryKeys.length; i++) { + fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1); + } + nDeleted = deleteStmt.executeUpdate(); + deleteStmt.clearParameters(); return nDeleted; } - /** Delete records with specified primary keys from the table. + /** + * Delete records with specified primary keys from the table. * * @param objects array of objects containing values of primary key. * * @return number of objects actually deleted */ - public int delete(T[] objects) - throws SQLException - { - return delete(objects, session); - } - - /** Delete records with specified primary keys from the table. - * - * @param objects array of objects containing values of primary key. - * - * @return number of objects actually deleted - */ - public synchronized int delete(T[] objects, Session session) + public synchronized int delete (Connection conn, T[] objects) throws SQLException { if (primaryKeys == null) { - throw new NoPrimaryKeyError(this); - } - if (session == null) { - session = ((SessionThread)Thread.currentThread()).session; + throw new IllegalStateException( + "No primary key for table " + name + "."); } int nDeleted = 0; -// try { - checkConnection(session); - if (deleteStmt == null) { - String sql = "delete from " + name + - " where " + primaryKeys[0] + " = ?"; - for (int i = 1; i < primaryKeys.length; i++) { - sql += " and " + primaryKeys[i] + " = ?"; - } - deleteStmt = session.connection.prepareStatement(sql); - } - for (int i = 0; i < objects.length; i++) { - for (int j = 0; j < primaryKeys.length; j++) { - fields[primaryKeyIndices[j]].bindVariable(deleteStmt, - objects[i], j+1); - } - deleteStmt.addBatch(); - } - int rc[] = deleteStmt.executeBatch(); - for (int k = 0; k < rc.length; k++) { - nDeleted += rc[k]; - } - deleteStmt.clearParameters(); -// } catch(SQLException ex) { session.handleSQLException(ex); } + checkUpdateConnection(conn); + if (deleteStmt == null) { + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + deleteStmt = conn.prepareStatement(sql); + } + for (int i = 0; i < objects.length; i++) { + for (int j = 0; j < primaryKeys.length; j++) { + fields[primaryKeyIndices[j]].bindVariable( + deleteStmt, objects[i], j+1); + } + deleteStmt.addBatch(); + } + int rc[] = deleteStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nDeleted += rc[k]; + } + deleteStmt.clearParameters(); return nDeleted; } @@ -599,99 +446,44 @@ public class Table ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]"; } - /** Spearator of name components of compound field. For example, if Java - * class constains component "location" of Point class, which - * has two components "x" and "y", then database table should - * have columns "location_x" and "location_y" (if '_' is used - * as separator) + /** + * Spearator of name components of compound field. For example, if Java + * class constains component "location" of Point class, which has two + * components "x" and "y", then database table should have columns + * "location_x" and "location_y" (if '_' is used as separator). */ public static String fieldSeparator = "_"; - - // --- Implementation ----------------------------------------- - -// /** Is table abstract - not present in database. -// */ -// protected boolean isAbstract; -// protected Table derived; - - protected String name; - protected String listOfFields; - protected String qualifiedListOfFields; - protected String listOfAssignments; - protected Class cls; - protected Session session; - - protected boolean mixedCaseConvert = false; - - private FieldDescriptor[] fields; - private FieldMask fMask; - - private int nFields; // length of "fields" array - private int nColumns; // number of atomic fields in "fields" array - - private String primaryKeys[]; - private int primaryKeyIndices[]; - - protected int connectionID; - - private PreparedStatement updateStmt; - private PreparedStatement deleteStmt; - private PreparedStatement insertStmt; - -// private static Table allTables; - private Constructor constructor; - private static Method setBypass; - - private static Class serializableClass; - private static final Object[] bypassFlag = { new Boolean(true) }; - private static final Object[] constructorArgs = {}; - - static { - try { - serializableClass = Class.forName("java.io.Serializable"); - Class c = Class.forName("java.lang.reflect.AccessibleObject"); - Class[] param = { Boolean.TYPE }; - setBypass = c.getMethod("setAccessible", param); - } catch(Exception ex) {} - } - - - private final void init(Class clazz, String tableName, Session s, - String[] keys, boolean mixedCaseConvert) + protected final void init (Class clazz, String tableName, String[] keys, + boolean mixedCaseConvert) { name = tableName; this.mixedCaseConvert = mixedCaseConvert; - cls = clazz; -// isAbstract = tableName == null; - session = s; + _rowClass = clazz; primaryKeys = keys; listOfFields = ""; qualifiedListOfFields = ""; listOfAssignments = ""; - connectionID = 0; ArrayList fieldsVector = new ArrayList(); - nFields = buildFieldsList(fieldsVector, cls, ""); + nFields = buildFieldsList(fieldsVector, _rowClass, ""); fields = fieldsVector.toArray(new FieldDescriptor[nFields]); fMask = new FieldMask(fields); try { - constructor = cls.getDeclaredConstructor(new Class[0]); + constructor = _rowClass.getDeclaredConstructor(new Class[0]); setBypass.invoke(constructor, bypassFlag); } catch(Exception ex) {} - if (keys != null) { - if (keys.length == 0) { - throw new NoPrimaryKeyError(this); - } + if (keys != null && keys.length > 0) { primaryKeyIndices = new int[keys.length]; for (int j = keys.length; --j >= 0;) { int i = nFields; while (--i >= 0) { if (fields[i].name.equals(keys[j])) { if (!fields[i].isAtomic()) { - throw new NoPrimaryKeyError(this); + throw new IllegalArgumentException( + "Non-atomic primary key provided"); } primaryKeyIndices[j] = i; break; @@ -703,71 +495,49 @@ public class Table } } } -// insertIntoTableHierarchy(); } -// private final void insertIntoTableHierarchy() -// { -// Table t, prev = null; -// Table after = null; -// int nChilds = 0; -// for (t = allTables; t != null; prev = t, t = t.derived) { -// if (t.cls.isAssignableFrom(cls)) { -// if (primaryKeys == null && t.primaryKeys != null) { -// primaryKeys = t.primaryKeys; -// primaryKeyIndices = t.primaryKeyIndices; -// } -// if (session == null) { -// session = t.session; -// } -// t.nDerived += 1; -// after = t; -// } else if (cls.isAssignableFrom(t.cls)) { -// after = prev; -// do { -// if (cls.isAssignableFrom(t.cls)) { -// if (primaryKeys != null && t.primaryKeys == null) { -// t.primaryKeys = primaryKeys; -// t.primaryKeyIndices = primaryKeyIndices; -// } -// if (t.session == null) { -// t.session = session; -// } -// nChilds += 1; -// } -// } while ((t = t.derived) != null); -// break; -// } -// } -// if (after == null) { -// derived = allTables; -// allTables = this; -// } else { -// derived = after.derived; -// after.derived = this; -// } -// nDerived = nChilds; -// } - - private final void checkConnection(Session s) throws SQLException { - if (connectionID != s.connectionID) { + /** + * Flushes our insert, update and delete statements if the supplied + * connection differs from the one used to prepare them. + */ + protected void checkUpdateConnection (Connection conn) + throws SQLException + { + if (_updateConn != conn) { if (insertStmt != null) { - insertStmt.close(); + JDBCUtil.close(insertStmt); insertStmt = null; } if (updateStmt != null) { - updateStmt.close(); + JDBCUtil.close(updateStmt); updateStmt = null; } if (deleteStmt != null) { - deleteStmt.close(); + JDBCUtil.close(deleteStmt); deleteStmt = null; } - connectionID = s.connectionID; + _updateConn = conn; } } - private final String convertName (String name) + /** + * Flushes our cached Cursor prepared statements if the supplied connection + * differes from the one used to prepare them. + */ + protected void checkReadConnection (Connection conn) + throws SQLException + { + if (_readConn != conn) { + for (PreparedStatement pstmt : preparedStmtHash.values()) { + JDBCUtil.close(pstmt); + } + preparedStmtHash.clear(); + _readConn = conn; + } + } + + protected final String convertName (String name) { if (mixedCaseConvert) { return StringUtil.unStudlyName(name); @@ -776,12 +546,12 @@ public class Table } } - private final int buildFieldsList(ArrayList buf, Class cls, - String prefix) + protected final int buildFieldsList (ArrayList buf, + Class _rowClass, String prefix) { - Field[] f = cls.getDeclaredFields(); + Field[] f = _rowClass.getDeclaredFields(); - Class superclass = cls; + Class superclass = _rowClass; while ((superclass = superclass.getSuperclass()) != null) { Field[] inheritedFields = superclass.getDeclaredFields(); Field[] allFields = new Field[inheritedFields.length + f.length]; @@ -919,7 +689,7 @@ public class Table return obj; } - private final int load ( + protected final int load ( Object obj, int i, int end, int column, ResultSet result) throws SQLException { @@ -988,10 +758,10 @@ public class Table return "select " + listOfFields + " from " + name + buf; } - private final int bindUpdateVariables(PreparedStatement pstmt, Object obj, - int i, int end, int column, - FieldMask mask) - throws SQLException + protected final int bindUpdateVariables ( + PreparedStatement pstmt, Object obj, int i, int end, int column, + FieldMask mask) + throws SQLException { try { while (i < end) { @@ -1029,9 +799,9 @@ public class Table return column; } - private final int bindQueryVariables(PreparedStatement pstmt, Object obj, - int i, int end, int column, - FieldMask mask) + protected final int bindQueryVariables ( + PreparedStatement pstmt, Object obj, int i, int end, int column, + FieldMask mask) throws SQLException { try { @@ -1081,8 +851,9 @@ public class Table return column; } - private final void buildQueryList(StringBuffer buf, Object qbe, int i, - int end, FieldMask mask, boolean like) + protected final void buildQueryList ( + StringBuffer buf, Object qbe, int i, int end, FieldMask mask, + boolean like) { try { while (i < end) { @@ -1136,9 +907,9 @@ public class Table } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } } - protected final int updateVariables(ResultSet result, Object obj, - int i, int end, int column) - throws SQLException + protected final int updateVariables ( + ResultSet result, Object obj, int i, int end, int column) + throws SQLException { try { while (i < end) { @@ -1169,6 +940,48 @@ public class Table return column; } + protected String name; + protected String listOfFields; + protected String qualifiedListOfFields; + protected String listOfAssignments; + protected Class _rowClass; + + protected boolean mixedCaseConvert = false; + + protected FieldDescriptor[] fields; + protected FieldMask fMask; + + protected int nFields; // length of "fields" array + protected int nColumns; // number of atomic fields in "fields" array + + protected String primaryKeys[]; + protected int primaryKeyIndices[]; + + protected Connection _updateConn, _readConn; + + protected PreparedStatement updateStmt; + protected PreparedStatement deleteStmt; + protected PreparedStatement insertStmt; + + protected Constructor constructor; + protected static Method setBypass; + + protected static Class serializableClass; + protected static final Object[] bypassFlag = { new Boolean(true) }; + protected static final Object[] constructorArgs = {}; + + protected Hashtable preparedStmtHash = + new Hashtable(); + // used to identify byte[] fields - private static final byte[] BYTE_PROTO = new byte[0]; + protected static final byte[] BYTE_PROTO = new byte[0]; + + static { + try { + serializableClass = Class.forName("java.io.Serializable"); + Class c = Class.forName("java.lang.reflect.AccessibleObject"); + Class[] param = { Boolean.TYPE }; + setBypass = c.getMethod("setAccessible", param); + } catch(Exception ex) {} + } } diff --git a/src/java/com/samskivert/servlet/user/UserRepository.java b/src/java/com/samskivert/servlet/user/UserRepository.java index 469495df..72c03320 100644 --- a/src/java/com/samskivert/servlet/user/UserRepository.java +++ b/src/java/com/samskivert/servlet/user/UserRepository.java @@ -39,7 +39,6 @@ import com.samskivert.jdbc.JORARepository; import com.samskivert.jdbc.StaticConnectionProvider; import com.samskivert.jdbc.jora.Cursor; import com.samskivert.jdbc.jora.FieldMask; -import com.samskivert.jdbc.jora.Session; import com.samskivert.jdbc.jora.Table; import com.samskivert.util.HashIntMap; import com.samskivert.util.StringUtil; @@ -134,30 +133,15 @@ public class UserRepository extends JORARepository * @return the user associated with the specified session or null of * no session exists with the supplied identifier. */ - public User loadUserBySession (final String sessionKey) + public User loadUserBySession (String sessionKey) throws PersistenceException { - return execute(new Operation() { - public User invoke (Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - String query = "where authcode = '" + sessionKey + - "' AND sessions.userId = users.userId"; - // look up the user - Cursor ec = _utable.select("sessions", query); - - // fetch the user from the cursor - User user = ec.next(); - if (user != null) { - // call next() again to cause the cursor to close itself - ec.next(); - // configure the user record with its field mask - user.setDirtyMask(_utable.getFieldMask()); - } - - return user; - } - }); + User user = load(_utable, "where authcode = '" + sessionKey + "' AND " + + "sessions.userId = users.userId"); + if (user != null) { + user.setDirtyMask(_utable.getFieldMask()); + } + return user; } /** @@ -168,29 +152,15 @@ public class UserRepository extends JORARepository public HashIntMap loadUsersFromId (int[] userIds) throws PersistenceException { - // make sure we actually have something to do - if (userIds.length == 0) { - return new HashIntMap(); - } - - final String ids = genIdString(userIds); - - return execute(new Operation>() { - public HashIntMap invoke (Connection conn, - DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - Cursor ec = _utable.select( - "where userid in (" + ids + ")"); - User user; - HashIntMap data = new HashIntMap(); - while ((user = ec.next()) != null) { - user.setDirtyMask(_utable.getFieldMask()); - data.put(user.userId, user); - } - return data; + HashIntMap data = new HashIntMap(); + if (userIds.length > 0) { + String query = "where userid in (" + genIdString(userIds) + ")"; + for (User user : loadAll(_utable, query)) { + user.setDirtyMask(_utable.getFieldMask()); + data.put(user.userId, user); } - }); + } + return data; } /** @@ -204,16 +174,7 @@ public class UserRepository extends JORARepository public ArrayList lookupUsersByEmail (String email) throws PersistenceException { - final String where = "where email = " + - JDBCUtil.escape(email); - return execute(new Operation>() { - public ArrayList invoke (Connection conn, - DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - return _utable.select(where).toArrayList(); - } - }); + return loadAll(_utable, "where email = " + JDBCUtil.escape(email)); } /** @@ -227,20 +188,12 @@ public class UserRepository extends JORARepository public ArrayList lookupUsersWhere (final String where) throws PersistenceException { - return execute(new Operation>() { - public ArrayList invoke ( - Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - ArrayList users = - _utable.select("where " + where).toArrayList(); - for (User user : users) { - // configure the user record with its field mask - user.setDirtyMask(_utable.getFieldMask()); - } - return users; - } - }); + ArrayList users = loadAll(_utable, where); + for (User user : users) { + // configure the user record with its field mask + user.setDirtyMask(_utable.getFieldMask()); + } + return users; } /** @@ -259,17 +212,7 @@ public class UserRepository extends JORARepository // nothing doing! return false; } - - executeUpdate(new Operation() { - public Object invoke (Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - _utable.update(user, user.getDirtyMask()); - // nothing to return - return null; - } - }); - + update(_utable, user, user.getDirtyMask()); return true; } @@ -317,7 +260,7 @@ public class UserRepository extends JORARepository try { user.username = StringUtil.truncate( ii + "=" + oldName, 24); - _utable.update(user, mask); + _utable.update(conn, user, mask); return null; // nothing to return } catch (SQLException se) { if (!liaison.isDuplicateRowException(se)) { @@ -350,18 +293,8 @@ public class UserRepository extends JORARepository final Date expires = new Date(cal.getTime().getTime()); // insert the session into the database - executeUpdate(new Operation() { - public Object invoke (Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - _session.execute("insert into sessions " + - "(authcode, userId, expires) values('" + - authcode + "', " + user.userId + ", '" + - expires + "')"); - // nothing to return - return null; - } - }); + update("insert into sessions (authcode, userId, expires) values('" + + authcode + "', " + user.userId + ", '" + expires + "')"); // and let the user know what the session identifier is return authcode; @@ -373,16 +306,7 @@ public class UserRepository extends JORARepository public void pruneSessions () throws PersistenceException { - executeUpdate(new Operation() { - public Object invoke (Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - _session.execute("delete from sessions where " + - "expires <= CURRENT_DATE()"); - // nothing to return - return null; - } - }); + update("delete from sessions where expires <= CURRENT_DATE()"); } /** @@ -420,7 +344,7 @@ public class UserRepository extends JORARepository public Object invoke (Connection conn, DatabaseLiaison liaison) throws PersistenceException, SQLException { - Statement stmt = _session.connection.createStatement(); + Statement stmt = conn.createStatement(); try { String query = "select realname from users"; ResultSet rs = stmt.executeQuery(query); @@ -469,7 +393,7 @@ public class UserRepository extends JORARepository throws PersistenceException, SQLException { try { - _utable.insert(user); + _utable.insert(conn, user); // update the userid now that it's known user.userId = liaison.lastInsertedId(conn); // nothing to return @@ -489,31 +413,17 @@ public class UserRepository extends JORARepository } /** - * Loads up a user record that matches the specified where clause. - * Returns null if no record matches. + * Loads up a user record that matches the specified where clause. Returns + * null if no record matches. */ - protected User loadUserWhere (final String whereClause) + protected User loadUserWhere (String where) throws PersistenceException { - return execute(new Operation() { - public User invoke (Connection conn, DatabaseLiaison liaison) - throws PersistenceException, SQLException - { - // look up the user - Cursor ec = _utable.select(whereClause); - - // fetch the user from the cursor - User user = ec.next(); - if (user != null) { - // call next() again to cause the cursor to close itself - ec.next(); - // configure the user record with its field mask - user.setDirtyMask(_utable.getFieldMask()); - } - - return user; - } - }); + User user = load(_utable, where); + if (user != null) { + user.setDirtyMask(_utable.getFieldMask()); + } + return user; } protected String[] loadNames (int[] userIds, final String column) @@ -524,16 +434,14 @@ public class UserRepository extends JORARepository return new String[0]; } - final String ids = genIdString(userIds); - - final HashIntMap map = new HashIntMap(); - // do the query + final String ids = genIdString(userIds); + final HashIntMap map = new HashIntMap(); execute(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) throws PersistenceException, SQLException { - Statement stmt = _session.connection.createStatement(); + Statement stmt = conn.createStatement(); try { String query = "select userId, " + column + " from users " + @@ -559,14 +467,13 @@ public class UserRepository extends JORARepository for (int i = 0; i < userIds.length; i++) { result[i] = map.get(userIds[i]); } - return result; } /** - * Take the passed in int array and create the a string suitable for - * using in a SQL set query (I.e., "select foo, from bar where userid - * in (genIdString(userIds))"; ) + * Take the passed in int array and create the a string suitable for using + * in a SQL set query (I.e., "select foo, from bar where userid in + * (genIdString(userIds))"; ) */ protected String genIdString (int[] userIds) { @@ -583,10 +490,10 @@ public class UserRepository extends JORARepository } // documentation inherited - protected void createTables (Session session) + protected void createTables () { // create our table object - _utable = new Table(User.class, "users", session, "userId"); + _utable = new Table(User.class, "users", "userId"); } /** A wrapper that provides access to the userstable. */