KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cayenne > dba > JdbcPkGenerator


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne.dba;
21
22 import java.sql.Connection JavaDoc;
23 import java.sql.DatabaseMetaData JavaDoc;
24 import java.sql.ResultSet JavaDoc;
25 import java.sql.SQLException JavaDoc;
26 import java.sql.Statement JavaDoc;
27 import java.sql.Types JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Collections JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Map JavaDoc;
34
35 import org.apache.cayenne.CayenneRuntimeException;
36 import org.apache.cayenne.access.DataNode;
37 import org.apache.cayenne.access.OperationObserver;
38 import org.apache.cayenne.access.QueryLogger;
39 import org.apache.cayenne.access.ResultIterator;
40 import org.apache.cayenne.map.DbAttribute;
41 import org.apache.cayenne.map.DbEntity;
42 import org.apache.cayenne.map.DbKeyGenerator;
43 import org.apache.cayenne.query.Query;
44 import org.apache.cayenne.query.SQLTemplate;
45 import org.apache.cayenne.util.IDUtil;
46
47 /**
48  * Default primary key generator implementation. Uses a lookup table named
49  * "AUTO_PK_SUPPORT" to search and increment primary keys for tables.
50  *
51  * @author Andrus Adamchik
52  */

53 public class JdbcPkGenerator implements PkGenerator {
54
55     public static final int DEFAULT_PK_CACHE_SIZE = 20;
56
57     protected Map JavaDoc pkCache = new HashMap JavaDoc();
58     protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE;
59
60     public void createAutoPk(DataNode node, List JavaDoc dbEntities) throws Exception JavaDoc {
61         // check if a table exists
62

63         // create AUTO_PK_SUPPORT table
64
if (!autoPkTableExists(node)) {
65             runUpdate(node, pkTableCreateString());
66         }
67
68         // delete any existing pk entries
69
runUpdate(node, pkDeleteString(dbEntities));
70
71         // insert all needed entries
72
Iterator JavaDoc it = dbEntities.iterator();
73         while (it.hasNext()) {
74             DbEntity ent = (DbEntity) it.next();
75             runUpdate(node, pkCreateString(ent.getName()));
76         }
77     }
78
79     public List JavaDoc createAutoPkStatements(List JavaDoc dbEntities) {
80         List JavaDoc list = new ArrayList JavaDoc();
81
82         list.add(pkTableCreateString());
83         list.add(pkDeleteString(dbEntities));
84
85         Iterator JavaDoc it = dbEntities.iterator();
86         while (it.hasNext()) {
87             DbEntity ent = (DbEntity) it.next();
88             list.add(pkCreateString(ent.getName()));
89         }
90
91         return list;
92     }
93
94     /**
95      * Drops table named "AUTO_PK_SUPPORT" if it exists in the database.
96      */

97     public void dropAutoPk(DataNode node, List JavaDoc dbEntities) throws Exception JavaDoc {
98         if (autoPkTableExists(node)) {
99             runUpdate(node, dropAutoPkString());
100         }
101     }
102
103     public List JavaDoc dropAutoPkStatements(List JavaDoc dbEntities) {
104         List JavaDoc list = new ArrayList JavaDoc();
105         list.add(dropAutoPkString());
106         return list;
107     }
108
109     protected String JavaDoc pkTableCreateString() {
110         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
111         buf
112                 .append("CREATE TABLE AUTO_PK_SUPPORT (")
113                 .append(" TABLE_NAME CHAR(100) NOT NULL,")
114                 .append(" NEXT_ID INTEGER NOT NULL,")
115                 .append(" PRIMARY KEY(TABLE_NAME)")
116                 .append(")");
117
118         return buf.toString();
119     }
120
121     protected String JavaDoc pkDeleteString(List JavaDoc dbEntities) {
122         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
123         buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN (");
124         int len = dbEntities.size();
125         for (int i = 0; i < len; i++) {
126             if (i > 0) {
127                 buf.append(", ");
128             }
129             DbEntity ent = (DbEntity) dbEntities.get(i);
130             buf.append('\'').append(ent.getName()).append('\'');
131         }
132         buf.append(')');
133         return buf.toString();
134     }
135
136     protected String JavaDoc pkCreateString(String JavaDoc entName) {
137         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
138         buf
139                 .append("INSERT INTO AUTO_PK_SUPPORT")
140                 .append(" (TABLE_NAME, NEXT_ID)")
141                 .append(" VALUES ('")
142                 .append(entName)
143                 .append("', 200)");
144         return buf.toString();
145     }
146
147     protected String JavaDoc pkSelectString(String JavaDoc entName) {
148         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
149         buf.append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '").append(
150                 entName).append('\'');
151         return buf.toString();
152     }
153
154     protected String JavaDoc pkUpdateString(String JavaDoc entName) {
155         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
156         buf.append("UPDATE AUTO_PK_SUPPORT").append(" SET NEXT_ID = NEXT_ID + ").append(
157                 pkCacheSize).append(" WHERE TABLE_NAME = '").append(entName).append('\'');
158         return buf.toString();
159     }
160
161     protected String JavaDoc dropAutoPkString() {
162         return "DROP TABLE AUTO_PK_SUPPORT";
163     }
164
165     /**
166      * Checks if AUTO_PK_TABLE already exists in the database.
167      */

168     protected boolean autoPkTableExists(DataNode node) throws SQLException JavaDoc {
169         Connection JavaDoc con = node.getDataSource().getConnection();
170         boolean exists = false;
171         try {
172             DatabaseMetaData JavaDoc md = con.getMetaData();
173             ResultSet JavaDoc tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null);
174             try {
175                 exists = tables.next();
176             }
177             finally {
178                 tables.close();
179             }
180         }
181         finally {
182             // return connection to the pool
183
con.close();
184         }
185
186         return exists;
187     }
188
189     /**
190      * Runs JDBC update over a Connection obtained from DataNode. Returns a number of
191      * objects returned from update.
192      *
193      * @throws SQLException in case of query failure.
194      */

195     public int runUpdate(DataNode node, String JavaDoc sql) throws SQLException JavaDoc {
196         QueryLogger.logQuery(sql, Collections.EMPTY_LIST);
197
198         Connection JavaDoc con = node.getDataSource().getConnection();
199         try {
200             Statement JavaDoc upd = con.createStatement();
201             try {
202                 return upd.executeUpdate(sql);
203             }
204             finally {
205                 upd.close();
206             }
207         }
208         finally {
209             con.close();
210         }
211     }
212
213     /**
214      * <p>
215      * Generates new (unique and non-repeating) primary key for specified dbEntity.
216      * </p>
217      * <p>
218      * This implementation is naive since it does not lock the database rows when
219      * executing select and subsequent update. Adapter-specific implementations are more
220      * robust.
221      * </p>
222      */

223
224     public Object JavaDoc generatePkForDbEntity(DataNode node, DbEntity ent) throws Exception JavaDoc {
225
226         // check for binary pk
227
Object JavaDoc binPK = binaryPK(ent);
228         if (binPK != null) {
229             return binPK;
230         }
231
232         DbKeyGenerator pkGenerator = ent.getPrimaryKeyGenerator();
233         int cacheSize;
234         if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null)
235             cacheSize = pkGenerator.getKeyCacheSize().intValue();
236         else
237             cacheSize = pkCacheSize;
238
239         // if no caching, always generate fresh
240
if (cacheSize <= 1) {
241             return new Integer JavaDoc(pkFromDatabase(node, ent));
242         }
243
244         synchronized (pkCache) {
245             PkRange r = (PkRange) pkCache.get(ent.getName());
246
247             if (r == null) {
248                 // created exhaused PkRange
249
r = new PkRange(1, 0);
250                 pkCache.put(ent.getName(), r);
251             }
252
253             if (r.isExhausted()) {
254                 int val = pkFromDatabase(node, ent);
255                 r.reset(val, val + cacheSize - 1);
256             }
257
258             return r.getNextPrimaryKey();
259         }
260     }
261
262     /**
263      * @return a binary PK if DbEntity has a BINARY or VARBINARY pk, null otherwise. This
264      * method will likely be deprecated in 1.1 in favor of a more generic
265      * soultion.
266      * @since 1.0.2
267      */

268     protected byte[] binaryPK(DbEntity entity) {
269         List JavaDoc pkColumns = entity.getPrimaryKey();
270         if (pkColumns.size() == 1) {
271             DbAttribute pk = (DbAttribute) pkColumns.get(0);
272             if (pk.getMaxLength() > 0
273                     && (pk.getType() == Types.BINARY || pk.getType() == Types.VARBINARY)) {
274                 return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength());
275             }
276         }
277
278         return null;
279     }
280
281     /**
282      * Performs primary key generation ignoring cache. Generates a range of primary keys
283      * as specified by "pkCacheSize" bean property.
284      * <p>
285      * This method is called internally from "generatePkForDbEntity" and then generated
286      * range of key values is saved in cache for performance. Subclasses that implement
287      * different primary key generation solutions should override this method, not
288      * "generatePkForDbEntity".
289      * </p>
290      */

291     protected int pkFromDatabase(DataNode node, DbEntity ent) throws Exception JavaDoc {
292         String JavaDoc select = "SELECT #result('NEXT_ID' 'int' 'NEXT_ID') "
293                 + "FROM AUTO_PK_SUPPORT "
294                 + "WHERE TABLE_NAME = '"
295                 + ent.getName()
296                 + '\'';
297
298         // run queries via DataNode to utilize its transactional behavior
299
List JavaDoc queries = new ArrayList JavaDoc(2);
300         queries.add(new SQLTemplate(ent, select));
301         queries.add(new SQLTemplate(ent, pkUpdateString(ent.getName())));
302
303         PkRetrieveProcessor observer = new PkRetrieveProcessor(ent.getName());
304         node.performQueries(queries, observer);
305         return observer.getId();
306     }
307
308     /**
309      * Returns a size of the entity primary key cache. Default value is 20. If cache size
310      * is set to a value less or equals than "one", no primary key caching is done.
311      */

312     public int getPkCacheSize() {
313         return pkCacheSize;
314     }
315
316     /**
317      * Sets the size of the entity primary key cache. If <code>pkCacheSize</code>
318      * parameter is less than 1, cache size is set to "one".
319      * <p>
320      * <i>Note that our tests show that setting primary key cache value to anything much
321      * bigger than 20 does not give any significant performance increase. Therefore it
322      * does not make sense to use bigger values, since this may potentially create big
323      * gaps in the database primary key sequences in cases like application crashes or
324      * restarts. </i>
325      * </p>
326      */

327     public void setPkCacheSize(int pkCacheSize) {
328         this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize;
329     }
330
331     public void reset() {
332         pkCache.clear();
333     }
334
335     /**
336      * OperationObserver for primary key retrieval.
337      */

338     final class PkRetrieveProcessor implements OperationObserver {
339
340         Number JavaDoc id;
341         String JavaDoc entityName;
342
343         PkRetrieveProcessor(String JavaDoc entityName) {
344             this.entityName = entityName;
345         }
346
347         public boolean isIteratedResult() {
348             return false;
349         }
350
351         public int getId() {
352             if (id == null) {
353                 throw new CayenneRuntimeException("No key was retrieved for entity "
354                         + entityName);
355             }
356
357             return id.intValue();
358         }
359
360         public void nextDataRows(Query query, List JavaDoc dataRows) {
361
362             // process selected object, issue an update query
363
if (dataRows == null || dataRows.size() == 0) {
364                 throw new CayenneRuntimeException(
365                         "Error generating PK : entity not supported: " + entityName);
366             }
367
368             if (dataRows.size() > 1) {
369                 throw new CayenneRuntimeException(
370                         "Error generating PK : too many rows for entity: " + entityName);
371             }
372
373             Map JavaDoc lastPk = (Map JavaDoc) dataRows.get(0);
374             id = (Number JavaDoc) lastPk.get("NEXT_ID");
375         }
376
377         public void nextCount(Query query, int resultCount) {
378             if (resultCount != 1) {
379                 throw new CayenneRuntimeException("Error generating PK for entity '"
380                         + entityName
381                         + "': update count is wrong - "
382                         + resultCount);
383             }
384         }
385
386         public void nextBatchCount(Query query, int[] resultCount) {
387         }
388
389         public void nextGeneratedDataRows(Query query, ResultIterator keysIterator) {
390         }
391
392         public void nextDataRows(Query q, ResultIterator it) {
393         }
394
395         public void nextQueryException(Query query, Exception JavaDoc ex) {
396
397             throw new CayenneRuntimeException("Error generating PK for entity '"
398                     + entityName
399                     + "'.", ex);
400         }
401
402         public void nextGlobalException(Exception JavaDoc ex) {
403
404             throw new CayenneRuntimeException("Error generating PK for entity: "
405                     + entityName, ex);
406         }
407     }
408 }
409
Popular Tags