|
0
|
1 |
package hirondelle.web4j.database;
|
|
|
2 |
|
|
|
3 |
import java.util.*;
|
|
|
4 |
import java.util.logging.*;
|
|
|
5 |
import java.sql.Connection;
|
|
|
6 |
import hirondelle.web4j.util.Util;
|
|
|
7 |
import hirondelle.web4j.util.Args;
|
|
|
8 |
|
|
|
9 |
/**
|
|
|
10 |
Perform the simplest kinds of transactions.
|
|
|
11 |
|
|
|
12 |
<P>Executes a number of SQL statements, in the same order as passed to the constructor.
|
|
|
13 |
Each SQL statement is executed once and only once, without any iteration.
|
|
|
14 |
Each SQL statement must perform an <tt>INSERT</tt>, <tt>DELETE</tt>, or <tt>UPDATE</tt> operation.
|
|
|
15 |
No <tt>SELECT</tt>s are allowed.
|
|
|
16 |
|
|
|
17 |
<P><span class="highlight">It is likely that a significant fraction (perhaps as high as 50%) of all
|
|
|
18 |
transactions can be implemented with this class. </span>
|
|
|
19 |
|
|
|
20 |
<P>Example use case:
|
|
|
21 |
<PRE>
|
|
|
22 |
SqlId[] sqlIds = {ADD_THIS, DELETE_THAT};
|
|
|
23 |
Object[] params = {blah.getX(), blah.getY(), blah.getZ(), blah.getQ()};
|
|
|
24 |
Tx doStuff = new TxSimple(sqlIds, params);
|
|
|
25 |
int numRecordsAffected = doStuff.executeTx();
|
|
|
26 |
</PRE>
|
|
|
27 |
|
|
|
28 |
<P>In this example, <tt>blah</tt> will usually represent a Model Object. The data in <tt>params</tt>
|
|
|
29 |
is divided up among the various <tt>sqlIds</tt>. (This division is performed internally by this class.)
|
|
|
30 |
|
|
|
31 |
<P>This class uses strong ordering conventions. <span class="highlight">The order of
|
|
|
32 |
items in each array passed to the constructor is very important</span> (see {@link #TxSimple(SqlId[], Object[])}).
|
|
|
33 |
*/
|
|
|
34 |
public final class TxSimple implements Tx {
|
|
|
35 |
|
|
|
36 |
/**
|
|
|
37 |
Constructor.
|
|
|
38 |
|
|
|
39 |
<P><tt>aAllParams</tt> includes the parameters for all operations, concatenated in a single array. Has at least
|
|
|
40 |
one member.
|
|
|
41 |
|
|
|
42 |
<P>In general, successive pieces of <tt>aAllParams</tt> are used to populate each corresponding
|
|
|
43 |
statement, in their order of execution. The order of items is here <em>doubly</em> important: the
|
|
|
44 |
first N parameters are used against the first SQL statement, the next M parameters are
|
|
|
45 |
used against the second SQL statement, and so on. (This breakdown is deduced internally.)
|
|
|
46 |
|
|
|
47 |
<P>And as usual, within each subset of <tt>aAllParams</tt>
|
|
|
48 |
corresponding to an SQL statement, the order of parameters matches the order
|
|
|
49 |
of <tt>'?'</tt> placeholders appearing in the underlying SQL statement.
|
|
|
50 |
|
|
|
51 |
@param aSqlIds identifiers for the operations to be performed, <em>in the order of their intended execution</em>. Has
|
|
|
52 |
at least one member. All of the operations are against the same database.
|
|
|
53 |
@param aAllParams the parameters for all operations, concatenated in a single array. Has at least
|
|
|
54 |
one member. (See above for important requirements on their order.)
|
|
|
55 |
*/
|
|
|
56 |
public TxSimple(SqlId[] aSqlIds, Object[] aAllParams){
|
|
|
57 |
Args.checkForPositive(aSqlIds.length);
|
|
|
58 |
Args.checkForPositive(aAllParams.length);
|
|
|
59 |
|
|
|
60 |
fSqlIds = aSqlIds;
|
|
|
61 |
fParams = aAllParams;
|
|
|
62 |
fParamsForStatement = new LinkedHashMap<SqlId, Object[]>();
|
|
|
63 |
divideUpParams();
|
|
|
64 |
}
|
|
|
65 |
|
|
|
66 |
public int executeTx() throws DAOException {
|
|
|
67 |
Tx transaction = new SimpleTransaction(getDbName());
|
|
|
68 |
return transaction.executeTx();
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
// PRIVATE //
|
|
|
72 |
private SqlId[] fSqlIds;
|
|
|
73 |
private Object[] fParams;
|
|
|
74 |
private Map<SqlId, Object[]> fParamsForStatement;
|
|
|
75 |
private static final Object[] EMPTY = new Object[0];
|
|
|
76 |
private static final Logger fLogger = Util.getLogger(TxSimple.class);
|
|
|
77 |
|
|
|
78 |
private final class SimpleTransaction extends TxTemplate {
|
|
|
79 |
SimpleTransaction(String aDbName){
|
|
|
80 |
super(aDbName);
|
|
|
81 |
}
|
|
|
82 |
public int executeMultipleSqls(Connection aConnection) throws DAOException {
|
|
|
83 |
int result = 0;
|
|
|
84 |
for (SqlId sqlId : fSqlIds){
|
|
|
85 |
Object[] params = fParamsForStatement.get(sqlId);
|
|
|
86 |
SqlEditor editor = null;
|
|
|
87 |
if( params.length == 0 ){
|
|
|
88 |
editor = SqlEditor.forTx(sqlId, aConnection);
|
|
|
89 |
}
|
|
|
90 |
else {
|
|
|
91 |
editor = SqlEditor.forTx(sqlId, aConnection, params);
|
|
|
92 |
}
|
|
|
93 |
result = result + editor.editDatabase();
|
|
|
94 |
}
|
|
|
95 |
return result;
|
|
|
96 |
}
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
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 |
}
|