View Javadoc

1   package org.apache.ojb.broker.util.sequence;
2   
3   /* Copyright 2003-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import org.apache.commons.lang.SystemUtils;
19  import org.apache.ojb.broker.PersistenceBroker;
20  import org.apache.ojb.broker.platforms.PlatformException;
21  import org.apache.ojb.broker.accesslayer.LookupException;
22  import org.apache.ojb.broker.metadata.ClassDescriptor;
23  import org.apache.ojb.broker.metadata.FieldDescriptor;
24  import org.apache.ojb.broker.query.Query;
25  import org.apache.ojb.broker.util.logging.Logger;
26  import org.apache.ojb.broker.util.logging.LoggerFactory;
27  
28  import java.sql.CallableStatement;
29  import java.sql.SQLException;
30  import java.sql.Statement;
31  import java.sql.Connection;
32  
33  /**
34   * This solution will give those seeking an oracle-style
35   * sequence generator a final answer (Identity columns really suck).
36   * <br/>
37   * The <code>SequenceManagerStoredProcedureImpl</code> implementation enabled database
38   * sequence key generation for all databases (e.g. MSSQL, MySQL, DB2, ...)
39   * with a <b>JDBC 2.0</b> compliant driver.
40   * <br/>
41   * First add a new table <code>OJB_NEXTVAL_SEQ</code> to
42   * your database.
43   * <pre>
44   * CREATE TABLE OJB_NEXTVAL_SEQ
45   * (
46   *     SEQ_NAME    VARCHAR(150) NOT NULL,
47   *     MAX_KEY     BIGINT,
48   *     CONSTRAINT SYS_PK_OJB_NEXTVAL_SEQ PRIMARY KEY(SEQ_NAME)
49   * )
50   * </pre>
51   * You will also need the stored procedure OJB_NEXTVAL
52   * will will take care of giving you a guaranteed unique
53   * sequence number, in multi server environments.
54   * <br/>
55   * <pre>
56   * CREATE PROCEDURE ojb_nextval_proc @SEQ_NAME varchar(100)
57   *              AS
58   *		declare @MAX_KEY BIGINT
59   *              -- return an error if sequence does not exist
60   *              -- so we will know if someone truncates the table
61   *              set @MAX_KEY = 0
62   *
63   *              UPDATE OJB_NEXTVAL_SEQ
64   *              SET    @MAX_KEY = MAX_KEY = MAX_KEY + 1
65   *              WHERE  SEQ_NAME = @SEQ_NAME
66   *
67   *		if @MAX_KEY = 0
68   *			select 1/0
69   *		else
70   *			select @MAX_KEY
71   *
72   *              RETURN @MAX_KEY
73   * </pre>
74   * <br/>
75   * It is possible to define a <code>sequence-name</code>
76   * field-descriptor attribute in the repository file. If
77   * such an attribute was not found, the implementation build
78   * an extent aware sequence name by its own.
79   * <br/>
80   * Keep in mind when define a sequence name, that you are responsible
81   * to be aware of extents, that is: if you ask for an uid for an
82   * interface with several
83   * implementor classes, or a baseclass with several subclasses the returned
84   * uid have to be unique accross all tables representing objects of the
85   * extent in question. Thus you have to use the same <code>sequence-name</code>
86   * for all extents.
87   *
88   * <p>
89   * Implementation configuration properties:
90   * </p>
91   *
92   * <table cellspacing="2" cellpadding="2" border="3" frame="box">
93   * <tr>
94   *     <td><strong>Property Key</strong></td>
95   *     <td><strong>Property Values</strong></td>
96   * </tr>
97   * <tr>
98   *     <td>autoNaming</td>
99   *     <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.1 2007-08-24 22:17:29 ewestfal Exp $
124  */
125 public class SequenceManagerStoredProcedureImpl extends AbstractSequenceManager
126 {
127     private Logger log = LoggerFactory.getLogger(SequenceManagerStoredProcedureImpl.class);
128     protected static final String PROCEDURE_NAME = "ojb_nextval_proc";
129     protected static final String SEQ_NAME_STRING = "SEQ_NAME";
130     protected static final String SEQ_ID_STRING = "MAX_KEY";
131     protected static final String SEQ_TABLE_NAME = "OJB_NEXTVAL_SEQ";
132 
133     /**
134      * Constructor
135      * @param broker
136      */
137     public SequenceManagerStoredProcedureImpl(PersistenceBroker broker)
138     {
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, long maxKey)
149     {
150         return "insert into " + SEQ_TABLE_NAME + " ("
151                 + SEQ_NAME_STRING + "," + SEQ_ID_STRING +
152                 ") values ('" + sequenceName + "'," + 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) throws SequenceManagerException
163     {
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         {
174             targetBroker.beginTransaction();
175             needsCommit = true;
176         }
177         try
178         {
179             // lookup sequence name
180             String sequenceName = calculateSequenceName(field);
181             try
182             {
183                 result = buildNextSequence(targetBroker, field.getClassDescriptor(), sequenceName);
184                 /*
185                 if 0 was returned we assume that the stored procedure
186                 did not work properly.
187                 */
188                 if (result == 0)
189                 {
190                     throw new SequenceManagerException("No incremented value retrieved");
191                 }
192             }
193             catch (Exception e)
194             {
195                 // maybe the sequence was not created
196                 log.info("Could not grab next key, message was " + e.getMessage() +
197                         " - try to write a new sequence entry to database");
198                 try
199                 {
200                     // on create, make sure to get the max key for the table first
201                     long maxKey = SequenceManagerHelper.getMaxForExtent(targetBroker, field);
202                     createSequence(targetBroker, field, sequenceName, maxKey);
203                 }
204                 catch (Exception e1)
205                 {
206                     String eol = SystemUtils.LINE_SEPARATOR;
207                     throw new SequenceManagerException(eol + "Could not grab next id, failed with " + eol +
208                             e.getMessage() + eol + "Creation of new sequence failed with " +
209                             eol + e1.getMessage() + eol, e1);
210                 }
211                 try
212                 {
213                     result = buildNextSequence(targetBroker, field.getClassDescriptor(), sequenceName);
214                 }
215                 catch (Exception e1)
216                 {
217                     throw new SequenceManagerException("Could not grab next id although a sequence seems to exist", e);
218                 }
219             }
220         }
221         finally
222         {
223             if(targetBroker != null && needsCommit)
224             {
225                 targetBroker.commitTransaction();
226             }
227         }
228         return result;
229     }
230 
231     /**
232      * Calls the stored procedure stored procedure throws an
233      * error if it doesn't exist.
234      * @param broker
235      * @param cld
236      * @param sequenceName
237      * @return
238      * @throws LookupException
239      * @throws SQLException
240      */
241     protected long buildNextSequence(PersistenceBroker broker, ClassDescriptor cld, String sequenceName)
242             throws LookupException, SQLException, PlatformException
243     {
244         CallableStatement cs = null;
245         try
246         {
247             Connection con = broker.serviceConnectionManager().getConnection();
248             cs = getPlatform().prepareNextValProcedureStatement(con, PROCEDURE_NAME, sequenceName);
249             cs.executeUpdate();
250             return cs.getLong(1);
251         }
252         finally
253         {
254             try
255             {
256                 if (cs != null)
257                     cs.close();
258             }
259             catch (SQLException ignore)
260             {
261                 // ignore it
262             }
263         }
264     }
265 
266     /**
267      * Creates new row in table
268      * @param broker
269      * @param field
270      * @param sequenceName
271      * @param maxKey
272      * @throws Exception
273      */
274     protected void createSequence(PersistenceBroker broker, FieldDescriptor field,
275                                   String sequenceName, long maxKey) throws Exception
276     {
277         Statement stmt = null;
278         try
279         {
280             stmt = broker.serviceStatementManager().getGenericStatement(field.getClassDescriptor(), Query.NOT_SCROLLABLE);
281             stmt.execute(sp_createSequenceQuery(sequenceName, maxKey));
282         }
283         catch (Exception e)
284         {
285             log.error(e);
286             throw new SequenceManagerException("Could not create new row in "+SEQ_TABLE_NAME+" table - TABLENAME=" +
287                     sequenceName + " field=" + field.getColumnName(), e);
288         }
289         finally
290         {
291             try
292             {
293                 if (stmt != null) stmt.close();
294             }
295             catch (SQLException sqle)
296             {
297                 if(log.isDebugEnabled())
298                     log.debug("Threw SQLException while in createSequence and closing stmt", sqle);
299                 // ignore it
300             }
301         }
302     }
303 }