001 package hirondelle.web4j.database; 002 003 import java.util.*; 004 import java.util.logging.*; 005 import java.sql.Connection; 006 import hirondelle.web4j.util.Util; 007 import hirondelle.web4j.util.Args; 008 009 /** 010 Perform the simplest kinds of transactions. 011 012 <P>Executes a number of SQL statements, in the same order as passed to the constructor. 013 Each SQL statement is executed once and only once, without any iteration. 014 Each SQL statement must perform an <tt>INSERT</tt>, <tt>DELETE</tt>, or <tt>UPDATE</tt> operation. 015 No <tt>SELECT</tt>s are allowed. 016 017 <P><span class="highlight">It is likely that a significant fraction (perhaps as high as 50%) of all 018 transactions can be implemented with this class. </span> 019 020 <P>Example use case: 021 <PRE> 022 SqlId[] sqlIds = {ADD_THIS, DELETE_THAT}; 023 Object[] params = {blah.getX(), blah.getY(), blah.getZ(), blah.getQ()}; 024 Tx doStuff = new TxSimple(sqlIds, params); 025 int numRecordsAffected = doStuff.executeTx(); 026 </PRE> 027 028 <P>In this example, <tt>blah</tt> will usually represent a Model Object. The data in <tt>params</tt> 029 is divided up among the various <tt>sqlIds</tt>. (This division is performed internally by this class.) 030 031 <P>This class uses strong ordering conventions. <span class="highlight">The order of 032 items in each array passed to the constructor is very important</span> (see {@link #TxSimple(SqlId[], Object[])}). 033 */ 034 public final class TxSimple implements Tx { 035 036 /** 037 Constructor. 038 039 <P><tt>aAllParams</tt> includes the parameters for all operations, concatenated in a single array. Has at least 040 one member. 041 042 <P>In general, successive pieces of <tt>aAllParams</tt> are used to populate each corresponding 043 statement, in their order of execution. The order of items is here <em>doubly</em> important: the 044 first N parameters are used against the first SQL statement, the next M parameters are 045 used against the second SQL statement, and so on. (This breakdown is deduced internally.) 046 047 <P>And as usual, within each subset of <tt>aAllParams</tt> 048 corresponding to an SQL statement, the order of parameters matches the order 049 of <tt>'?'</tt> placeholders appearing in the underlying SQL statement. 050 051 @param aSqlIds identifiers for the operations to be performed, <em>in the order of their intended execution</em>. Has 052 at least one member. All of the operations are against the same database. 053 @param aAllParams the parameters for all operations, concatenated in a single array. Has at least 054 one member. (See above for important requirements on their order.) 055 */ 056 public TxSimple(SqlId[] aSqlIds, Object[] aAllParams){ 057 Args.checkForPositive(aSqlIds.length); 058 Args.checkForPositive(aAllParams.length); 059 060 fSqlIds = aSqlIds; 061 fParams = aAllParams; 062 fParamsForStatement = new LinkedHashMap<SqlId, Object[]>(); 063 divideUpParams(); 064 } 065 066 public int executeTx() throws DAOException { 067 Tx transaction = new SimpleTransaction(getDbName()); 068 return transaction.executeTx(); 069 } 070 071 // PRIVATE // 072 private SqlId[] fSqlIds; 073 private Object[] fParams; 074 private Map<SqlId, Object[]> fParamsForStatement; 075 private static final Object[] EMPTY = new Object[0]; 076 private static final Logger fLogger = Util.getLogger(TxSimple.class); 077 078 private final class SimpleTransaction extends TxTemplate { 079 SimpleTransaction(String aDbName){ 080 super(aDbName); 081 } 082 public int executeMultipleSqls(Connection aConnection) throws DAOException { 083 int result = 0; 084 for (SqlId sqlId : fSqlIds){ 085 Object[] params = fParamsForStatement.get(sqlId); 086 SqlEditor editor = null; 087 if( params.length == 0 ){ 088 editor = SqlEditor.forTx(sqlId, aConnection); 089 } 090 else { 091 editor = SqlEditor.forTx(sqlId, aConnection, params); 092 } 093 result = result + editor.editDatabase(); 094 } 095 return result; 096 } 097 } 098 099 private String getDbName() { 100 return fSqlIds[0].getDatabaseName(); 101 } 102 103 private void divideUpParams(){ 104 int startWith = 0; 105 int totalNumExpectedParams = 0; 106 for (SqlId sqlId : fSqlIds){ 107 int numExpectedParams = SqlStatement.getNumParameters(sqlId); 108 totalNumExpectedParams = totalNumExpectedParams + numExpectedParams; 109 chunkParams(startWith, numExpectedParams, sqlId); 110 startWith = startWith + numExpectedParams; 111 } 112 if ( totalNumExpectedParams != fParams.length ){ 113 throw new IllegalArgumentException( 114 "Size mismatch. Number of parameters passed as data: " + fParams.length + 115 ". Number of '?' items appearing in underlying SQL statements: " + totalNumExpectedParams 116 ); 117 } 118 fLogger.finest("Chunked params " + Util.logOnePerLine(fParamsForStatement)); 119 } 120 121 private void chunkParams(int aStartWithIdx, int aNumExpectedParams, SqlId aSqlId){ 122 int endWithIdx = aStartWithIdx + aNumExpectedParams - 1; 123 int maxIdx = fParams.length - 1; 124 fLogger.fine("Chunking params for " + aSqlId + ". Start with index " + aStartWithIdx + ", end with index " + endWithIdx); 125 if( aStartWithIdx > maxIdx ){ 126 throw new IllegalArgumentException("Error chunking parameter data. Starting-index (" + aStartWithIdx + ") greater than expected maximum (" + maxIdx + "). Mismatch between number of params passed as data, and number of params expected by underlying SQL statements."); 127 } 128 if ( endWithIdx > maxIdx ){ 129 throw new IllegalArgumentException("Error chunking parameter data. Ending-index (" + endWithIdx + ") greater than expected maximum (" + maxIdx + "). Mismatch between number of params passed as data, and number of params expected by underlying SQL statements."); 130 } 131 132 if( aNumExpectedParams == 0 ){ 133 fParamsForStatement.put(aSqlId, EMPTY); 134 } 135 else { 136 List list = Arrays.asList(fParams); 137 List sublist = list.subList(aStartWithIdx, endWithIdx + 1); 138 fParamsForStatement.put(aSqlId, sublist.toArray(EMPTY)); 139 } 140 } 141 }