001: package org.apache.ojb.broker.util.sequence;
002:
003: /* Copyright 2003-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import org.apache.commons.lang.SystemUtils;
019: import org.apache.ojb.broker.PersistenceBroker;
020: import org.apache.ojb.broker.platforms.PlatformException;
021: import org.apache.ojb.broker.accesslayer.LookupException;
022: import org.apache.ojb.broker.metadata.ClassDescriptor;
023: import org.apache.ojb.broker.metadata.FieldDescriptor;
024: import org.apache.ojb.broker.query.Query;
025: import org.apache.ojb.broker.util.logging.Logger;
026: import org.apache.ojb.broker.util.logging.LoggerFactory;
027:
028: import java.sql.CallableStatement;
029: import java.sql.SQLException;
030: import java.sql.Statement;
031: import java.sql.Connection;
032:
033: /**
034: * This solution will give those seeking an oracle-style
035: * sequence generator a final answer (Identity columns really suck).
036: * <br/>
037: * The <code>SequenceManagerStoredProcedureImpl</code> implementation enabled database
038: * sequence key generation for all databases (e.g. MSSQL, MySQL, DB2, ...)
039: * with a <b>JDBC 2.0</b> compliant driver.
040: * <br/>
041: * First add a new table <code>OJB_NEXTVAL_SEQ</code> to
042: * your database.
043: * <pre>
044: * CREATE TABLE OJB_NEXTVAL_SEQ
045: * (
046: * SEQ_NAME VARCHAR(150) NOT NULL,
047: * MAX_KEY BIGINT,
048: * CONSTRAINT SYS_PK_OJB_NEXTVAL_SEQ PRIMARY KEY(SEQ_NAME)
049: * )
050: * </pre>
051: * You will also need the stored procedure OJB_NEXTVAL
052: * will will take care of giving you a guaranteed unique
053: * sequence number, in multi server environments.
054: * <br/>
055: * <pre>
056: * CREATE PROCEDURE ojb_nextval_proc @SEQ_NAME varchar(100)
057: * AS
058: * declare @MAX_KEY BIGINT
059: * -- return an error if sequence does not exist
060: * -- so we will know if someone truncates the table
061: * set @MAX_KEY = 0
062: *
063: * UPDATE OJB_NEXTVAL_SEQ
064: * SET @MAX_KEY = MAX_KEY = MAX_KEY + 1
065: * WHERE SEQ_NAME = @SEQ_NAME
066: *
067: * if @MAX_KEY = 0
068: * select 1/0
069: * else
070: * select @MAX_KEY
071: *
072: * RETURN @MAX_KEY
073: * </pre>
074: * <br/>
075: * It is possible to define a <code>sequence-name</code>
076: * field-descriptor attribute in the repository file. If
077: * such an attribute was not found, the implementation build
078: * an extent aware sequence name by its own.
079: * <br/>
080: * Keep in mind when define a sequence name, that you are responsible
081: * to be aware of extents, that is: if you ask for an uid for an
082: * interface with several
083: * implementor classes, or a baseclass with several subclasses the returned
084: * uid have to be unique accross all tables representing objects of the
085: * extent in question. Thus you have to use the same <code>sequence-name</code>
086: * for all extents.
087: *
088: * <p>
089: * Implementation configuration properties:
090: * </p>
091: *
092: * <table cellspacing="2" cellpadding="2" border="3" frame="box">
093: * <tr>
094: * <td><strong>Property Key</strong></td>
095: * <td><strong>Property Values</strong></td>
096: * </tr>
097: * <tr>
098: * <td>autoNaming</td>
099: * <td>
100: * Default was 'true'. If set 'true' OJB try to build a
101: * sequence name automatic if none found in field-descriptor
102: * and set this generated name as <code>sequence-name</code>
103: * in field-descriptor. If set 'false' OJB throws an exception
104: * if none sequence name was found in field-descriptor.
105: * </td>
106: * </tr>
107: * </table>
108: *
109: * <p>
110: * <b>Limitations:</b>
111: * <ul>
112: * <li>do not use when other application use the native key generation ditto</li>
113: * </ul>
114: * </p>
115: * <br/>
116: * <br/>
117: *
118: * @author Ryan Vanderwerf
119: * @author Edson Carlos Ericksson Richter
120: * @author Rajeev Kaul
121: * @author Thomas Mahler
122: * @author Armin Waibel
123: * @version $Id: SequenceManagerStoredProcedureImpl.java,v 1.11.2.2 2005/12/21 22:28:41 tomdz Exp $
124: */
125: public class SequenceManagerStoredProcedureImpl extends
126: AbstractSequenceManager {
127: private Logger log = LoggerFactory
128: .getLogger(SequenceManagerStoredProcedureImpl.class);
129: protected static final String PROCEDURE_NAME = "ojb_nextval_proc";
130: protected static final String SEQ_NAME_STRING = "SEQ_NAME";
131: protected static final String SEQ_ID_STRING = "MAX_KEY";
132: protected static final String SEQ_TABLE_NAME = "OJB_NEXTVAL_SEQ";
133:
134: /**
135: * Constructor
136: * @param broker
137: */
138: public SequenceManagerStoredProcedureImpl(PersistenceBroker broker) {
139: super (broker);
140: }
141:
142: /**
143: * Insert syntax for our special table
144: * @param sequenceName
145: * @param maxKey
146: * @return sequence insert statement
147: */
148: protected String sp_createSequenceQuery(String sequenceName,
149: long maxKey) {
150: return "insert into " + SEQ_TABLE_NAME + " (" + SEQ_NAME_STRING
151: + "," + SEQ_ID_STRING + ") values ('" + sequenceName
152: + "'," + maxKey + ")";
153: }
154:
155: /**
156: * Gets the actual key - will create a new row with the max key of table if it
157: * does not exist.
158: * @param field
159: * @return
160: * @throws SequenceManagerException
161: */
162: protected long getUniqueLong(FieldDescriptor field)
163: throws SequenceManagerException {
164: boolean needsCommit = false;
165: long result = 0;
166: /*
167: arminw:
168: use the associated broker instance, check if broker was in tx or
169: we need to commit used connection.
170: */
171: PersistenceBroker targetBroker = getBrokerForClass();
172: if (!targetBroker.isInTransaction()) {
173: targetBroker.beginTransaction();
174: needsCommit = true;
175: }
176: try {
177: // lookup sequence name
178: String sequenceName = calculateSequenceName(field);
179: try {
180: result = buildNextSequence(targetBroker, field
181: .getClassDescriptor(), sequenceName);
182: /*
183: if 0 was returned we assume that the stored procedure
184: did not work properly.
185: */
186: if (result == 0) {
187: throw new SequenceManagerException(
188: "No incremented value retrieved");
189: }
190: } catch (Exception e) {
191: // maybe the sequence was not created
192: log
193: .info("Could not grab next key, message was "
194: + e.getMessage()
195: + " - try to write a new sequence entry to database");
196: try {
197: // on create, make sure to get the max key for the table first
198: long maxKey = SequenceManagerHelper
199: .getMaxForExtent(targetBroker, field);
200: createSequence(targetBroker, field, sequenceName,
201: maxKey);
202: } catch (Exception e1) {
203: String eol = SystemUtils.LINE_SEPARATOR;
204: throw new SequenceManagerException(eol
205: + "Could not grab next id, failed with "
206: + eol + e.getMessage() + eol
207: + "Creation of new sequence failed with "
208: + eol + e1.getMessage() + eol, e1);
209: }
210: try {
211: result = buildNextSequence(targetBroker, field
212: .getClassDescriptor(), sequenceName);
213: } catch (Exception e1) {
214: throw new SequenceManagerException(
215: "Could not grab next id although a sequence seems to exist",
216: e);
217: }
218: }
219: } finally {
220: if (targetBroker != null && needsCommit) {
221: targetBroker.commitTransaction();
222: }
223: }
224: return result;
225: }
226:
227: /**
228: * Calls the stored procedure stored procedure throws an
229: * error if it doesn't exist.
230: * @param broker
231: * @param cld
232: * @param sequenceName
233: * @return
234: * @throws LookupException
235: * @throws SQLException
236: */
237: protected long buildNextSequence(PersistenceBroker broker,
238: ClassDescriptor cld, String sequenceName)
239: throws LookupException, SQLException, PlatformException {
240: CallableStatement cs = null;
241: try {
242: Connection con = broker.serviceConnectionManager()
243: .getConnection();
244: cs = getPlatform().prepareNextValProcedureStatement(con,
245: PROCEDURE_NAME, sequenceName);
246: cs.executeUpdate();
247: return cs.getLong(1);
248: } finally {
249: try {
250: if (cs != null)
251: cs.close();
252: } catch (SQLException ignore) {
253: // ignore it
254: }
255: }
256: }
257:
258: /**
259: * Creates new row in table
260: * @param broker
261: * @param field
262: * @param sequenceName
263: * @param maxKey
264: * @throws Exception
265: */
266: protected void createSequence(PersistenceBroker broker,
267: FieldDescriptor field, String sequenceName, long maxKey)
268: throws Exception {
269: Statement stmt = null;
270: try {
271: stmt = broker.serviceStatementManager()
272: .getGenericStatement(field.getClassDescriptor(),
273: Query.NOT_SCROLLABLE);
274: stmt.execute(sp_createSequenceQuery(sequenceName, maxKey));
275: } catch (Exception e) {
276: log.error(e);
277: throw new SequenceManagerException(
278: "Could not create new row in " + SEQ_TABLE_NAME
279: + " table - TABLENAME=" + sequenceName
280: + " field=" + field.getColumnName(), e);
281: } finally {
282: try {
283: if (stmt != null)
284: stmt.close();
285: } catch (SQLException sqle) {
286: if (log.isDebugEnabled())
287: log
288: .debug(
289: "Threw SQLException while in createSequence and closing stmt",
290: sqle);
291: // ignore it
292: }
293: }
294: }
295: }
|