68
|
1 /*
|
|
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
|
|
3
|
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5 of this software and associated documentation files (the "Software"), to deal
|
|
6 in the Software without restriction, including without limitation the rights
|
|
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8 copies of the Software, and to permit persons to whom the Software is
|
|
9 furnished to do so, subject to the following conditions:
|
|
10
|
|
11 The above copyright notice and this permission notice shall be included in
|
|
12 all copies or substantial portions of the Software.
|
|
13
|
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20 THE SOFTWARE.
|
|
21 */
|
|
22
|
|
23 package fschmidt.db.base;
|
|
24
|
|
25 import java.sql.SQLException;
|
|
26 import java.sql.PreparedStatement;
|
|
27 import java.sql.Connection;
|
|
28 import java.util.Map;
|
|
29 import java.util.HashMap;
|
|
30 import java.util.Iterator;
|
|
31 import org.slf4j.Logger;
|
|
32 import org.slf4j.LoggerFactory;
|
|
33 import fschmidt.db.DbTable;
|
|
34 import fschmidt.db.DbKey;
|
|
35 import fschmidt.db.DbKeySetter;
|
|
36 import fschmidt.db.DbObject;
|
|
37 import fschmidt.db.DbRecord;
|
|
38 import fschmidt.db.SQLRuntimeException;
|
|
39 import fschmidt.db.DbExpression;
|
|
40 import fschmidt.db.LongKey;
|
|
41 import fschmidt.db.DbUtils;
|
|
42 import fschmidt.db.DbArcana;
|
|
43 import fschmidt.db.ListenerList;
|
|
44 import fschmidt.db.extend.DbRecordExt;
|
|
45 import fschmidt.db.extend.DbTableExt;
|
|
46 import fschmidt.db.extend.DbTransaction;
|
|
47
|
|
48
|
|
49 final class DbRecordImpl<K extends DbKey,V extends DbObject<K,V>> implements DbRecordExt<K,V> {
|
|
50 private static final Logger logger = LoggerFactory.getLogger(DbRecordImpl.class);
|
|
51
|
|
52 public static boolean hasStackTrace = false;
|
|
53 private final V obj;
|
|
54 private K key;
|
|
55 private final DbTableExt<K,V> table;
|
|
56 private Map<String,Object> fields = new HashMap<String,Object>();
|
|
57 private boolean isInDb;
|
|
58 private boolean isStale = false;
|
|
59 // private final long whenLoaded = System.currentTimeMillis();
|
|
60 private DbTransaction trans;
|
|
61
|
|
62 public DbRecordImpl(DbTableExt<K,V> table,V obj,K key) {
|
|
63 this.table = table;
|
|
64 this.obj = obj;
|
|
65 this.key = key;
|
|
66 this.isInDb = true;
|
|
67 this.trans = table.getDbDatabaseExt().getTransaction();
|
|
68 }
|
|
69
|
|
70 public DbRecordImpl(DbTableExt<K,V> table,V obj) {
|
|
71 this.table = table;
|
|
72 this.obj = obj;
|
|
73 this.key = null;
|
|
74 this.isInDb = false;
|
|
75 this.trans = table.getDbDatabaseExt().getTransaction();
|
|
76 }
|
|
77
|
|
78 public V getDbObject() {
|
|
79 return obj;
|
|
80 }
|
|
81
|
|
82 public K getPrimaryKey() {
|
|
83 return key;
|
|
84 }
|
|
85
|
|
86 public DbTable<K,V> getDbTable() {
|
|
87 return table;
|
|
88 }
|
|
89
|
|
90 public Map<String,Object> fields() {
|
|
91 return fields;
|
|
92 }
|
|
93
|
|
94 /*
|
|
95 public long whenLoaded() {
|
|
96 return whenLoaded;
|
|
97 }
|
|
98 */
|
|
99 private void checkTrans() {
|
|
100 if( trans != table.getDbDatabaseExt().getTransaction() ) {
|
|
101 if( trans==null )
|
|
102 throw new IllegalStateException("record read outside of a transaction cannot be updated inside a transaction");
|
|
103 else
|
|
104 throw new IllegalStateException("transaction mismatch");
|
|
105 }
|
|
106 }
|
|
107
|
|
108 public void insert() {
|
|
109 if( isInDb() )
|
|
110 throw new IllegalStateException("already in database");
|
|
111 try {
|
|
112 ListenerList<V> list = table.getPreInsertListeners();
|
|
113 list.event(obj);
|
|
114 store();
|
|
115 this.trans = table.getDbDatabaseExt().getTransaction();
|
|
116 final Map<String,Object> updatedFields = fields;
|
|
117 final Exception ex = hasStackTrace ? new Exception("in "+Thread.currentThread()) : null;
|
|
118 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
|
|
119 public void run() {
|
|
120 ListenerList<V> list = table.getPostInsertListeners();
|
|
121 if( !list.isEmpty() ) {
|
|
122 V obj2 = DbUtils.getGoodCopy(obj);
|
|
123 if( obj2==null ) {
|
|
124 DbRecord<K,V> rec = obj.getDbRecord();
|
|
125 K key = rec.getPrimaryKey();
|
|
126 if( ex != null )
|
|
127 logger.error("insert null in "+Thread.currentThread()+" table="+table+" key="+key, ex);
|
|
128 throw new NullPointerException("in "+Thread.currentThread()+" table="+table+" key="+key);
|
|
129 }
|
|
130 DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord();
|
|
131 try {
|
|
132 list.event(obj2);
|
|
133 } finally {
|
|
134 updatedFields.clear();
|
|
135 }
|
|
136 }
|
|
137 }
|
|
138 } );
|
|
139 } finally {
|
|
140 fields = new HashMap<String,Object>();
|
|
141 makeStale();
|
|
142 }
|
|
143 }
|
|
144
|
|
145 public void update() {
|
|
146 checkTrans();
|
|
147 if( !isInDb() )
|
|
148 throw new IllegalStateException("not in database");
|
|
149 try {
|
|
150 ListenerList<V> list = table.getPreUpdateListeners();
|
|
151 list.event(obj);
|
|
152 if( fields.isEmpty() ) {
|
|
153 table.uncache(key);
|
|
154 } else {
|
|
155 store();
|
|
156 }
|
|
157 final Map<String,Object> updatedFields = fields;
|
|
158 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
|
|
159 public void run() {
|
|
160 ListenerList<V> list = table.getPostUpdateListeners();
|
|
161 if( !list.isEmpty() && isInDb ) {
|
|
162 V obj2 = DbUtils.getGoodCopy(obj);
|
|
163 if( obj2==null )
|
|
164 return; // presumably deleted later in transaction
|
|
165 DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord();
|
|
166 try {
|
|
167 list.event(obj2);
|
|
168 } finally {
|
|
169 updatedFields.clear();
|
|
170 }
|
|
171 }
|
|
172 }
|
|
173 } );
|
|
174 } finally {
|
|
175 fields = new HashMap<String,Object>();
|
|
176 }
|
|
177 }
|
|
178
|
|
179 private void store() {
|
|
180 StringBuilder buf = new StringBuilder();
|
|
181 try {
|
|
182 DbArcana arcana = table.getDbDatabase().arcana();
|
|
183 int n = 0;
|
|
184 if( isInDb() ) { // update
|
|
185 buf.append( "update " );
|
|
186 buf.append( table.getTableName() );
|
|
187 buf.append( " set " );
|
|
188 boolean first = true;
|
|
189 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
|
|
190 if( first ) {
|
|
191 first = false;
|
|
192 } else {
|
|
193 buf.append( ',' );
|
|
194 }
|
|
195 buf.append( arcana.quoteIdentifier(entry.getKey()) );
|
|
196 if( entry.getValue() instanceof DbExpression ) {
|
|
197 buf.append( '=' );
|
|
198 buf.append( entry.getValue() );
|
|
199 } else {
|
|
200 buf.append( "=?" );
|
|
201 n++;
|
|
202 }
|
|
203 }
|
|
204 } else { // insert
|
|
205 buf.append( "insert into " );
|
|
206 buf.append( table.getTableName() );
|
|
207 if( fields.isEmpty() ) {
|
|
208 buf.append( " DEFAULT VALUES" );
|
|
209 } else {
|
|
210 buf.append( " (" );
|
|
211 boolean first = true;
|
|
212 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
|
|
213 if( first ) {
|
|
214 first = false;
|
|
215 } else {
|
|
216 buf.append( ',' );
|
|
217 }
|
|
218 buf.append( arcana.quoteIdentifier(entry.getKey()) );
|
|
219 }
|
|
220 buf.append( ") values (" );
|
|
221 first = true;
|
|
222 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
|
|
223 if( first ) {
|
|
224 first = false;
|
|
225 } else {
|
|
226 buf.append(',');
|
|
227 }
|
|
228 if( entry.getValue() instanceof DbExpression ) {
|
|
229 buf.append( entry.getValue() );
|
|
230 } else {
|
|
231 buf.append( '?' );
|
|
232 n++;
|
|
233 }
|
|
234 }
|
|
235 buf.append( ")" );
|
|
236 }
|
|
237 }
|
|
238 Connection con = table.getDbDatabaseExt().getConnection();
|
|
239 try {
|
|
240 DbKeySetter<K> keySetter = table.getKeySetter();
|
|
241 PreparedStatement stmt;
|
|
242 if( isInDb() ) {
|
|
243 stmt = keySetter.prepareStatement(
|
|
244 con, key, buf.toString(), 1+n
|
|
245 );
|
|
246 } else {
|
|
247 stmt = con.prepareStatement( buf.toString() );
|
|
248 }
|
|
249 int idx = 1;
|
|
250 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
|
|
251 Object value = entry.getValue();
|
|
252 if( value instanceof DbExpression )
|
|
253 continue;
|
|
254 try {
|
|
255 arcana.setValue(stmt,idx++,value);
|
|
256 } catch(SQLException e) {
|
|
257 throw new SQLRuntimeException("field="+entry.getKey()+" value="+value,e);
|
|
258 }
|
|
259 }
|
|
260 stmt.executeUpdate();
|
|
261 boolean wasInDb = isInDb;
|
|
262 isInDb = true;
|
|
263 stmt.close();
|
|
264 if( !wasInDb ) {
|
|
265 key = keySetter.refreshKeyAfterInsert(con,this);
|
|
266 }
|
|
267 } finally {
|
|
268 try {
|
|
269 con.close();
|
|
270 } catch(SQLException e) {
|
|
271 logger.error("couldn't close connection",e);
|
|
272 throw e;
|
|
273 } catch(RuntimeException e) {
|
|
274 logger.error("couldn't close connection",e);
|
|
275 throw e;
|
|
276 }
|
|
277 }
|
|
278 } catch(SQLException e) {
|
|
279 throw new SQLRuntimeException("key = "+key+" sql = "+buf.toString(),e);
|
|
280 } finally {
|
|
281 table.uncache(key);
|
|
282 }
|
|
283 }
|
|
284
|
|
285 public void delete() {
|
|
286 checkTrans();
|
|
287 ListenerList<V> list = table.getPreDeleteListeners();
|
|
288 list.event(obj);
|
|
289 if( !isInDb() )
|
|
290 throw new IllegalStateException("not in database");
|
|
291 try {
|
|
292 Connection con = table.getDbDatabaseExt().getConnection();
|
|
293 PreparedStatement stmt = table.getKeySetter().prepareStatement(
|
|
294 con, key, "delete from "+table.getTableName(), 1
|
|
295 );
|
|
296 stmt.executeUpdate();
|
|
297 isInDb = false;
|
|
298 stmt.close();
|
|
299 con.close();
|
|
300 /*
|
|
301 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
|
|
302 public void run() {
|
|
303 table.getPostDeleteListeners().event(obj);
|
|
304 }
|
|
305 } );
|
|
306 */
|
|
307 } catch(SQLException e) {
|
|
308 throw new SQLRuntimeException("key = "+key,e);
|
|
309 } finally {
|
|
310 table.uncache(key);
|
|
311 fields.clear();
|
|
312 }
|
|
313 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
|
|
314 public void run() {
|
|
315 ListenerList<V> list = table.getPostDeleteListeners();
|
|
316 list.event(obj);
|
|
317 }
|
|
318 } );
|
|
319 }
|
|
320
|
|
321 public boolean isStale() {
|
|
322 return isStale || (isInDb || trans!=null) && trans != table.getDbDatabaseExt().getTransaction();
|
|
323 }
|
|
324
|
|
325 public void makeStale() {
|
|
326 isStale = true;
|
|
327 }
|
|
328
|
|
329 public boolean isInDb() {
|
|
330 return isInDb;
|
|
331 }
|
|
332
|
|
333 }
|