Source code: org/objectstyle/cayenne/dba/oracle/OraclePkGenerator.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56
57 package org.objectstyle.cayenne.dba.oracle;
58
59 import java.sql.Connection;
60 import java.sql.ResultSet;
61 import java.sql.SQLException;
62 import java.sql.Statement;
63 import java.util.ArrayList;
64 import java.util.Iterator;
65 import java.util.List;
66
67 import org.objectstyle.cayenne.CayenneRuntimeException;
68 import org.objectstyle.cayenne.access.DataNode;
69 import org.objectstyle.cayenne.dba.JdbcPkGenerator;
70 import org.objectstyle.cayenne.map.DbEntity;
71 import org.objectstyle.cayenne.map.DbKeyGenerator;
72
73 /**
74 * Sequence-based primary key generator implementation for Oracle.
75 * Uses Oracle sequences to generate primary key values. This approach is
76 * at least 50% faster when tested with Oracle compared to the lookup table
77 * approach.
78 *
79 * <p>When using Cayenne key caching mechanism, make sure that sequences in
80 * the database have "INCREMENT BY" greater or equal to OraclePkGenerator
81 * "pkCacheSize" property value. If this is not the case, you will need to
82 * adjust PkGenerator value accordingly. For example when sequence is
83 * incremented by 1 each time, use the following code:</p>
84 *
85 * <pre>
86 * dataNode.getAdapter().getPkGenerator().setPkCacheSize(1);
87 * </pre>
88 *
89 * @author Andrei Adamchik
90 */
91 public class OraclePkGenerator extends JdbcPkGenerator {
92 private static final String _SEQUENCE_PREFIX = "pk_";
93
94 public void createAutoPk(DataNode node, List dbEntities) throws Exception {
95 List sequences = getExistingSequences(node);
96
97 // create needed sequences
98 Iterator it = dbEntities.iterator();
99 while (it.hasNext()) {
100 DbEntity ent = (DbEntity) it.next();
101 if (!sequences.contains(sequenceName(ent))) {
102 runUpdate(node, createSequenceString(ent));
103 }
104 }
105 }
106
107 public List createAutoPkStatements(List dbEntities) {
108 List list = new ArrayList();
109 Iterator it = dbEntities.iterator();
110 while (it.hasNext()) {
111 DbEntity ent = (DbEntity) it.next();
112 list.add(createSequenceString(ent));
113 }
114
115 return list;
116 }
117
118 public void dropAutoPk(DataNode node, List dbEntities) throws Exception {
119 List sequences = getExistingSequences(node);
120
121 // drop obsolete sequences
122 Iterator it = dbEntities.iterator();
123 while (it.hasNext()) {
124 DbEntity ent = (DbEntity) it.next();
125 if (sequences.contains(stripSchemaName(sequenceName(ent)))) {
126 runUpdate(node, dropSequenceString(ent));
127 }
128 }
129 }
130
131 public List dropAutoPkStatements(List dbEntities) {
132 List list = new ArrayList();
133 Iterator it = dbEntities.iterator();
134 while (it.hasNext()) {
135 DbEntity ent = (DbEntity) it.next();
136 list.add(dropSequenceString(ent));
137 }
138
139 return list;
140 }
141
142 protected String createSequenceString(DbEntity ent) {
143 StringBuffer buf = new StringBuffer();
144 buf
145 .append("CREATE SEQUENCE ")
146 .append(sequenceName(ent))
147 .append(" START WITH 200")
148 .append(" INCREMENT BY ")
149 .append(pkCacheSize(ent));
150 return buf.toString();
151 }
152
153 /**
154 * Returns a SQL string needed to drop any database objects associated
155 * with automatic primary key generation process for a specific DbEntity.
156 */
157 protected String dropSequenceString(DbEntity ent) {
158 StringBuffer buf = new StringBuffer();
159 buf.append("DROP SEQUENCE ").append(sequenceName(ent));
160 return buf.toString();
161 }
162
163 /**
164 * Generates primary key by calling Oracle sequence corresponding to the
165 * <code>dbEntity</code>. Executed SQL looks like this:
166 *
167 * <pre>
168 * SELECT pk_table_name.nextval FROM DUAL
169 * </pre>
170 */
171 protected int pkFromDatabase(DataNode node, DbEntity ent) throws Exception {
172
173 DbKeyGenerator pkGenerator = ent.getPrimaryKeyGenerator();
174 String pkGeneratingSequenceName;
175 if (pkGenerator != null
176 && DbKeyGenerator.ORACLE_TYPE.equals(pkGenerator.getGeneratorType())
177 && pkGenerator.getGeneratorName() != null)
178 pkGeneratingSequenceName = pkGenerator.getGeneratorName();
179 else
180 pkGeneratingSequenceName = sequenceName(ent);
181
182 Connection con = node.getDataSource().getConnection();
183 try {
184 Statement st = con.createStatement();
185 try {
186 ResultSet rs =
187 st.executeQuery(
188 "SELECT " + pkGeneratingSequenceName + ".nextval FROM DUAL");
189 try {
190 //Object pk = null;
191 if (!rs.next()) {
192 throw new CayenneRuntimeException(
193 "Error generating pk for DbEntity " + ent.getName());
194 }
195 return rs.getInt(1);
196 }
197 finally {
198 rs.close();
199 }
200 }
201 finally {
202 st.close();
203 }
204 }
205 finally {
206 con.close();
207 }
208 }
209
210 protected int pkCacheSize(DbEntity entity) {
211 // use custom generator if possible
212 DbKeyGenerator keyGenerator = entity.getPrimaryKeyGenerator();
213 if (keyGenerator != null
214 && DbKeyGenerator.ORACLE_TYPE.equals(keyGenerator.getGeneratorType())
215 && keyGenerator.getGeneratorName() != null) {
216
217 Integer size = keyGenerator.getKeyCacheSize();
218 return (size != null && size.intValue() >= 1)
219 ? size.intValue()
220 : super.getPkCacheSize();
221 }
222 else {
223 return super.getPkCacheSize();
224 }
225 }
226
227 /** Returns expected primary key sequence name for a DbEntity. */
228 protected String sequenceName(DbEntity entity) {
229
230 // use custom generator if possible
231 DbKeyGenerator keyGenerator = entity.getPrimaryKeyGenerator();
232 if (keyGenerator != null
233 && DbKeyGenerator.ORACLE_TYPE.equals(keyGenerator.getGeneratorType())
234 && keyGenerator.getGeneratorName() != null) {
235
236 return keyGenerator.getGeneratorName().toLowerCase();
237 }
238 else {
239 String entName = entity.getName();
240 String seqName = _SEQUENCE_PREFIX + entName.toLowerCase();
241
242 if (entity.getSchema() != null && entity.getSchema().length() > 0) {
243 seqName = entity.getSchema() + "." + seqName;
244 }
245 return seqName;
246 }
247 }
248
249 protected String stripSchemaName(String sequenceName) {
250 int ind = sequenceName.indexOf('.');
251 return (ind >= 0) ? sequenceName.substring(ind + 1) : sequenceName;
252 }
253
254 /**
255 * Fetches a list of existing sequences that might match Cayenne
256 * generated ones.
257 */
258 protected List getExistingSequences(DataNode node) throws SQLException {
259
260 // check existing sequences
261 Connection con = node.getDataSource().getConnection();
262
263 try {
264 Statement sel = con.createStatement();
265 try {
266 ResultSet rs =
267 sel.executeQuery("SELECT LOWER(SEQUENCE_NAME) FROM ALL_SEQUENCES");
268 try {
269 List sequenceList = new ArrayList();
270 while (rs.next()) {
271 sequenceList.add(rs.getString(1));
272 }
273 return sequenceList;
274 }
275 finally {
276 rs.close();
277 }
278 }
279 finally {
280 sel.close();
281 }
282 }
283 finally {
284 con.close();
285 }
286 }
287 }