001: //$Id: OneToManyPersister.java 10040 2006-06-22 19:51:43Z steve.ebersole@jboss.com $
002: package org.hibernate.persister.collection;
003:
004: import java.io.Serializable;
005: import java.sql.PreparedStatement;
006: import java.sql.SQLException;
007: import java.util.Iterator;
008:
009: import org.hibernate.HibernateException;
010: import org.hibernate.MappingException;
011: import org.hibernate.jdbc.Expectation;
012: import org.hibernate.jdbc.Expectations;
013: import org.hibernate.cache.CacheConcurrencyStrategy;
014: import org.hibernate.cache.CacheException;
015: import org.hibernate.cfg.Configuration;
016: import org.hibernate.collection.PersistentCollection;
017: import org.hibernate.engine.SessionFactoryImplementor;
018: import org.hibernate.engine.SessionImplementor;
019: import org.hibernate.engine.SubselectFetch;
020: import org.hibernate.exception.JDBCExceptionHelper;
021: import org.hibernate.loader.collection.BatchingCollectionInitializer;
022: import org.hibernate.loader.collection.CollectionInitializer;
023: import org.hibernate.loader.collection.SubselectOneToManyLoader;
024: import org.hibernate.loader.entity.CollectionElementLoader;
025: import org.hibernate.mapping.Collection;
026: import org.hibernate.persister.entity.Joinable;
027: import org.hibernate.persister.entity.OuterJoinLoadable;
028: import org.hibernate.pretty.MessageHelper;
029: import org.hibernate.sql.Update;
030: import org.hibernate.util.ArrayHelper;
031:
032: /**
033: * Collection persister for one-to-many associations.
034: *
035: * @author Gavin King
036: */
037: public class OneToManyPersister extends AbstractCollectionPersister {
038:
039: private final boolean cascadeDeleteEnabled;
040: private final boolean keyIsNullable;
041: private final boolean keyIsUpdateable;
042:
043: protected boolean isRowDeleteEnabled() {
044: return keyIsUpdateable && keyIsNullable;
045: }
046:
047: protected boolean isRowInsertEnabled() {
048: return keyIsUpdateable;
049: }
050:
051: public boolean isCascadeDeleteEnabled() {
052: return cascadeDeleteEnabled;
053: }
054:
055: public OneToManyPersister(Collection collection,
056: CacheConcurrencyStrategy cache, Configuration cfg,
057: SessionFactoryImplementor factory) throws MappingException,
058: CacheException {
059: super (collection, cache, cfg, factory);
060: cascadeDeleteEnabled = collection.getKey()
061: .isCascadeDeleteEnabled()
062: && factory.getDialect().supportsCascadeDelete();
063: keyIsNullable = collection.getKey().isNullable();
064: keyIsUpdateable = collection.getKey().isUpdateable();
065: }
066:
067: /**
068: * Generate the SQL UPDATE that updates all the foreign keys to null
069: */
070: protected String generateDeleteString() {
071:
072: Update update = new Update(getDialect()).setTableName(
073: qualifiedTableName).addColumns(keyColumnNames, "null")
074: .setPrimaryKeyColumnNames(keyColumnNames);
075:
076: if (hasIndex && !indexContainsFormula)
077: update.addColumns(indexColumnNames, "null");
078:
079: if (hasWhere)
080: update.setWhere(sqlWhereString);
081:
082: if (getFactory().getSettings().isCommentsEnabled()) {
083: update.setComment("delete one-to-many " + getRole());
084: }
085:
086: return update.toStatementString();
087: }
088:
089: /**
090: * Generate the SQL UPDATE that updates a foreign key to a value
091: */
092: protected String generateInsertRowString() {
093:
094: Update update = new Update(getDialect()).setTableName(
095: qualifiedTableName).addColumns(keyColumnNames);
096:
097: if (hasIndex && !indexContainsFormula)
098: update.addColumns(indexColumnNames);
099:
100: //identifier collections not supported for 1-to-many
101: if (getFactory().getSettings().isCommentsEnabled()) {
102: update.setComment("create one-to-many row " + getRole());
103: }
104:
105: return update.setPrimaryKeyColumnNames(elementColumnNames)
106: .toStatementString();
107: }
108:
109: /**
110: * Not needed for one-to-many association
111: */
112: protected String generateUpdateRowString() {
113: return null;
114: }
115:
116: /**
117: * Generate the SQL UPDATE that updates a particular row's foreign
118: * key to null
119: */
120: protected String generateDeleteRowString() {
121:
122: Update update = new Update(getDialect()).setTableName(
123: qualifiedTableName).addColumns(keyColumnNames, "null");
124:
125: if (hasIndex && !indexContainsFormula)
126: update.addColumns(indexColumnNames, "null");
127:
128: if (getFactory().getSettings().isCommentsEnabled()) {
129: update.setComment("delete one-to-many row " + getRole());
130: }
131:
132: //use a combination of foreign key columns and pk columns, since
133: //the ordering of removal and addition is not guaranteed when
134: //a child moves from one parent to another
135: String[] rowSelectColumnNames = ArrayHelper.join(
136: keyColumnNames, elementColumnNames);
137: return update.setPrimaryKeyColumnNames(rowSelectColumnNames)
138: .toStatementString();
139: }
140:
141: public boolean consumesEntityAlias() {
142: return true;
143: }
144:
145: public boolean consumesCollectionAlias() {
146: return true;
147: }
148:
149: public boolean isOneToMany() {
150: return true;
151: }
152:
153: public boolean isManyToMany() {
154: return false;
155: }
156:
157: protected int doUpdateRows(Serializable id,
158: PersistentCollection collection, SessionImplementor session)
159: throws HibernateException {
160:
161: // we finish all the "removes" first to take care of possible unique
162: // constraints and so that we can take better advantage of batching
163:
164: try {
165: int count = 0;
166: if (isRowDeleteEnabled()) {
167: boolean useBatch = true;
168: PreparedStatement st = null;
169: // update removed rows fks to null
170: try {
171: int i = 0;
172:
173: Iterator entries = collection.entries(this );
174: int offset = 1;
175: Expectation expectation = Expectations.NONE;
176: while (entries.hasNext()) {
177:
178: Object entry = entries.next();
179: if (collection.needsUpdating(entry, i,
180: elementType)) { // will still be issued when it used to be null
181: if (st == null) {
182: String sql = getSQLDeleteRowString();
183: if (isDeleteCallable()) {
184: expectation = Expectations
185: .appropriateExpectation(getDeleteCheckStyle());
186: useBatch = expectation
187: .canBeBatched();
188: st = useBatch ? session
189: .getBatcher()
190: .prepareBatchCallableStatement(
191: sql)
192: : session
193: .getBatcher()
194: .prepareCallableStatement(
195: sql);
196: offset += expectation.prepare(st);
197: } else {
198: st = session
199: .getBatcher()
200: .prepareBatchStatement(
201: getSQLDeleteRowString());
202: }
203: }
204: int loc = writeKey(st, id, offset, session);
205: writeElementToWhere(st, collection
206: .getSnapshotElement(entry, i), loc,
207: session);
208: if (useBatch) {
209: session.getBatcher().addToBatch(
210: expectation);
211: } else {
212: expectation.verifyOutcome(st
213: .executeUpdate(), st, -1);
214: }
215: count++;
216: }
217: i++;
218: }
219: } catch (SQLException sqle) {
220: if (useBatch) {
221: session.getBatcher().abortBatch(sqle);
222: }
223: throw sqle;
224: } finally {
225: if (!useBatch) {
226: session.getBatcher().closeStatement(st);
227: }
228: }
229: }
230:
231: if (isRowInsertEnabled()) {
232: Expectation expectation = Expectations
233: .appropriateExpectation(getInsertCheckStyle());
234: boolean callable = isInsertCallable();
235: boolean useBatch = expectation.canBeBatched();
236: String sql = getSQLInsertRowString();
237: PreparedStatement st = null;
238: // now update all changed or added rows fks
239: try {
240: int i = 0;
241: Iterator entries = collection.entries(this );
242: while (entries.hasNext()) {
243: Object entry = entries.next();
244: int offset = 1;
245: if (collection.needsUpdating(entry, i,
246: elementType)) {
247: if (useBatch) {
248: if (st == null) {
249: if (callable) {
250: st = session
251: .getBatcher()
252: .prepareBatchCallableStatement(
253: sql);
254: } else {
255: st = session.getBatcher()
256: .prepareBatchStatement(
257: sql);
258: }
259: }
260: } else {
261: if (callable) {
262: st = session.getBatcher()
263: .prepareCallableStatement(
264: sql);
265: } else {
266: st = session.getBatcher()
267: .prepareStatement(sql);
268: }
269: }
270:
271: offset += expectation.prepare(st);
272:
273: int loc = writeKey(st, id, offset, session);
274: if (hasIndex && !indexContainsFormula) {
275: loc = writeIndexToWhere(st, collection
276: .getIndex(entry, i, this ), loc,
277: session);
278: }
279:
280: writeElementToWhere(st, collection
281: .getElement(entry), loc, session);
282:
283: if (useBatch) {
284: session.getBatcher().addToBatch(
285: expectation);
286: } else {
287: expectation.verifyOutcome(st
288: .executeUpdate(), st, -1);
289: }
290: count++;
291: }
292: i++;
293: }
294: } catch (SQLException sqle) {
295: if (useBatch) {
296: session.getBatcher().abortBatch(sqle);
297: }
298: throw sqle;
299: } finally {
300: if (!useBatch) {
301: session.getBatcher().closeStatement(st);
302: }
303: }
304: }
305:
306: return count;
307: } catch (SQLException sqle) {
308: throw JDBCExceptionHelper.convert(
309: getSQLExceptionConverter(), sqle,
310: "could not update collection rows: "
311: + MessageHelper.collectionInfoString(this ,
312: id, getFactory()),
313: getSQLInsertRowString());
314: }
315: }
316:
317: public String selectFragment(Joinable rhs, String rhsAlias,
318: String lhsAlias, String entitySuffix,
319: String collectionSuffix, boolean includeCollectionColumns) {
320: StringBuffer buf = new StringBuffer();
321: if (includeCollectionColumns) {
322: // buf.append( selectFragment( lhsAlias, "" ) )//ignore suffix for collection columns!
323: buf.append(selectFragment(lhsAlias, collectionSuffix))
324: .append(", ");
325: }
326: OuterJoinLoadable ojl = (OuterJoinLoadable) getElementPersister();
327: return buf.append(ojl.selectFragment(lhsAlias, entitySuffix))//use suffix for the entity columns
328: .toString();
329: }
330:
331: /**
332: * Create the <tt>OneToManyLoader</tt>
333: *
334: * @see org.hibernate.loader.collection.OneToManyLoader
335: */
336: protected CollectionInitializer createCollectionInitializer(
337: java.util.Map enabledFilters) throws MappingException {
338: return BatchingCollectionInitializer
339: .createBatchingOneToManyInitializer(this , batchSize,
340: getFactory(), enabledFilters);
341: }
342:
343: public String fromJoinFragment(String alias, boolean innerJoin,
344: boolean includeSubclasses) {
345: return ((Joinable) getElementPersister()).fromJoinFragment(
346: alias, innerJoin, includeSubclasses);
347: }
348:
349: public String whereJoinFragment(String alias, boolean innerJoin,
350: boolean includeSubclasses) {
351: return ((Joinable) getElementPersister()).whereJoinFragment(
352: alias, innerJoin, includeSubclasses);
353: }
354:
355: public String getTableName() {
356: return ((Joinable) getElementPersister()).getTableName();
357: }
358:
359: public String filterFragment(String alias) throws MappingException {
360: String result = super .filterFragment(alias);
361: if (getElementPersister() instanceof Joinable) {
362: result += ((Joinable) getElementPersister())
363: .oneToManyFilterFragment(alias);
364: }
365: return result;
366:
367: }
368:
369: protected CollectionInitializer createSubselectInitializer(
370: SubselectFetch subselect, SessionImplementor session) {
371: return new SubselectOneToManyLoader(this , subselect
372: .toSubselectString(getCollectionType()
373: .getLHSPropertyName()), subselect.getResult(),
374: subselect.getQueryParameters(), subselect
375: .getNamedParameterLocMap(), session
376: .getFactory(), session.getEnabledFilters());
377: }
378:
379: public Object getElementByIndex(Serializable key, Object index,
380: SessionImplementor session, Object owner) {
381: return new CollectionElementLoader(this, getFactory(), session
382: .getEnabledFilters()).loadElement(session, key,
383: incrementIndexByBase(index));
384: }
385:
386: }
|