1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.persister.entity;
26
27 import java.io.Serializable;
28 import java.sql.PreparedStatement;
29 import java.sql.ResultSet;
30 import java.sql.SQLException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.Comparator;
39
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.hibernate.AssertionFailure;
43 import org.hibernate.EntityMode;
44 import org.hibernate.FetchMode;
45 import org.hibernate.HibernateException;
46 import org.hibernate.LockMode;
47 import org.hibernate.MappingException;
48 import org.hibernate.QueryException;
49 import org.hibernate.StaleObjectStateException;
50 import org.hibernate.StaleStateException;
51 import org.hibernate.jdbc.Expectation;
52 import org.hibernate.jdbc.Expectations;
53 import org.hibernate.jdbc.TooManyRowsAffectedException;
54 import org.hibernate.dialect.lock.LockingStrategy;
55 import org.hibernate.cache.CacheConcurrencyStrategy;
56 import org.hibernate.cache.CacheKey;
57 import org.hibernate.cache.access.EntityRegionAccessStrategy;
58 import org.hibernate.cache.entry.CacheEntry;
59 import org.hibernate.cache.entry.CacheEntryStructure;
60 import org.hibernate.cache.entry.StructuredCacheEntry;
61 import org.hibernate.cache.entry.UnstructuredCacheEntry;
62 import org.hibernate.engine.CascadeStyle;
63 import org.hibernate.engine.CascadingAction;
64 import org.hibernate.engine.EntityEntry;
65 import org.hibernate.engine.Mapping;
66 import org.hibernate.engine.SessionFactoryImplementor;
67 import org.hibernate.engine.SessionImplementor;
68 import org.hibernate.engine.Versioning;
69 import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
70 import org.hibernate.engine.EntityKey;
71 import org.hibernate.engine.ValueInclusion;
72 import org.hibernate.exception.JDBCExceptionHelper;
73 import org.hibernate.id.IdentifierGenerator;
74 import org.hibernate.id.PostInsertIdentifierGenerator;
75 import org.hibernate.id.PostInsertIdentityPersister;
76 import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
77 import org.hibernate.id.insert.Binder;
78 import org.hibernate.intercept.LazyPropertyInitializer;
79 import org.hibernate.intercept.FieldInterceptionHelper;
80 import org.hibernate.intercept.FieldInterceptor;
81 import org.hibernate.loader.entity.BatchingEntityLoader;
82 import org.hibernate.loader.entity.CascadeEntityLoader;
83 import org.hibernate.loader.entity.EntityLoader;
84 import org.hibernate.loader.entity.UniqueEntityLoader;
85 import org.hibernate.mapping.Column;
86 import org.hibernate.mapping.Component;
87 import org.hibernate.mapping.PersistentClass;
88 import org.hibernate.mapping.Property;
89 import org.hibernate.mapping.Selectable;
90 import org.hibernate.metadata.ClassMetadata;
91 import org.hibernate.pretty.MessageHelper;
92 import org.hibernate.property.BackrefPropertyAccessor;
93 import org.hibernate.sql.Alias;
94 import org.hibernate.sql.Delete;
95 import org.hibernate.sql.Insert;
96 import org.hibernate.sql.JoinFragment;
97 import org.hibernate.sql.Select;
98 import org.hibernate.sql.SelectFragment;
99 import org.hibernate.sql.SimpleSelect;
100 import org.hibernate.sql.Template;
101 import org.hibernate.sql.Update;
102 import org.hibernate.tuple.entity.EntityMetamodel;
103 import org.hibernate.tuple.entity.EntityTuplizer;
104 import org.hibernate.tuple.Tuplizer;
105 import org.hibernate.type.AbstractComponentType;
106 import org.hibernate.type.AssociationType;
107 import org.hibernate.type.EntityType;
108 import org.hibernate.type.Type;
109 import org.hibernate.type.TypeFactory;
110 import org.hibernate.type.VersionType;
111 import org.hibernate.util.ArrayHelper;
112 import org.hibernate.util.CollectionHelper;
113 import org.hibernate.util.FilterHelper;
114 import org.hibernate.util.StringHelper;
115
116 /**
117 * Basic functionality for persisting an entity via JDBC
118 * through either generated or custom SQL
119 *
120 * @author Gavin King
121 */
122 public abstract class AbstractEntityPersister
123 implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable,
124 SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable {
125
126 private static final Logger log = LoggerFactory.getLogger( AbstractEntityPersister.class );
127
128 public static final String ENTITY_CLASS = "class";
129
130 // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131 private final SessionFactoryImplementor factory;
132 private final EntityRegionAccessStrategy cacheAccessStrategy;
133 private final boolean isLazyPropertiesCacheable;
134 private final CacheEntryStructure cacheEntryStructure;
135 private final EntityMetamodel entityMetamodel;
136 private final Map entityNameBySubclass = new HashMap();
137 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138
139 private final String[] rootTableKeyColumnNames;
140 private final String[] identifierAliases;
141 private final int identifierColumnSpan;
142 private final String versionColumnName;
143 private final boolean hasFormulaProperties;
144 private final int batchSize;
145 private final boolean hasSubselectLoadableCollections;
146 protected final String rowIdName;
147
148 private final Set lazyProperties;
149
150 // The optional SQL string defined in the where attribute
151 private final String sqlWhereString;
152 private final String sqlWhereStringTemplate;
153
154 //information about properties of this class,
155 //including inherited properties
156 //(only really needed for updatable/insertable properties)
157 private final int[] propertyColumnSpans;
158 private final String[] propertySubclassNames;
159 private final String[][] propertyColumnAliases;
160 private final String[][] propertyColumnNames;
161 private final String[][] propertyColumnFormulaTemplates;
162 private final boolean[][] propertyColumnUpdateable;
163 private final boolean[][] propertyColumnInsertable;
164 private final boolean[] propertyUniqueness;
165 private final boolean[] propertySelectable;
166
167 //information about lazy properties of this class
168 private final String[] lazyPropertyNames;
169 private final int[] lazyPropertyNumbers;
170 private final Type[] lazyPropertyTypes;
171 private final String[][] lazyPropertyColumnAliases;
172
173 //information about all properties in class hierarchy
174 private final String[] subclassPropertyNameClosure;
175 private final String[] subclassPropertySubclassNameClosure;
176 private final Type[] subclassPropertyTypeClosure;
177 private final String[][] subclassPropertyFormulaTemplateClosure;
178 private final String[][] subclassPropertyColumnNameClosure;
179 private final FetchMode[] subclassPropertyFetchModeClosure;
180 private final boolean[] subclassPropertyNullabilityClosure;
181 private final boolean[] propertyDefinedOnSubclass;
182 private final int[][] subclassPropertyColumnNumberClosure;
183 private final int[][] subclassPropertyFormulaNumberClosure;
184 private final CascadeStyle[] subclassPropertyCascadeStyleClosure;
185
186 //information about all columns/formulas in class hierarchy
187 private final String[] subclassColumnClosure;
188 private final boolean[] subclassColumnLazyClosure;
189 private final String[] subclassColumnAliasClosure;
190 private final boolean[] subclassColumnSelectableClosure;
191 private final String[] subclassFormulaClosure;
192 private final String[] subclassFormulaTemplateClosure;
193 private final String[] subclassFormulaAliasClosure;
194 private final boolean[] subclassFormulaLazyClosure;
195
196 // dynamic filters attached to the class-level
197 private final FilterHelper filterHelper;
198
199 private final Map uniqueKeyLoaders = new HashMap();
200 private final Map lockers = new HashMap();
201 private final Map loaders = new HashMap();
202
203 // SQL strings
204 private String sqlVersionSelectString;
205 private String sqlSnapshotSelectString;
206 private String sqlLazySelectString;
207
208 private String sqlIdentityInsertString;
209 private String sqlUpdateByRowIdString;
210 private String sqlLazyUpdateByRowIdString;
211
212 private String[] sqlDeleteStrings;
213 private String[] sqlInsertStrings;
214 private String[] sqlUpdateStrings;
215 private String[] sqlLazyUpdateStrings;
216
217 private String sqlInsertGeneratedValuesSelectString;
218 private String sqlUpdateGeneratedValuesSelectString;
219
220 //Custom SQL (would be better if these were private)
221 protected boolean[] insertCallable;
222 protected boolean[] updateCallable;
223 protected boolean[] deleteCallable;
224 protected String[] customSQLInsert;
225 protected String[] customSQLUpdate;
226 protected String[] customSQLDelete;
227 protected ExecuteUpdateResultCheckStyle[] insertResultCheckStyles;
228 protected ExecuteUpdateResultCheckStyle[] updateResultCheckStyles;
229 protected ExecuteUpdateResultCheckStyle[] deleteResultCheckStyles;
230
231 private InsertGeneratedIdentifierDelegate identityDelegate;
232
233 private boolean[] tableHasColumns;
234
235 private final String loaderName;
236
237 private UniqueEntityLoader queryLoader;
238
239 private final String temporaryIdTableName;
240 private final String temporaryIdTableDDL;
241
242 private final Map subclassPropertyAliases = new HashMap();
243 private final Map subclassPropertyColumnNames = new HashMap();
244
245 protected final BasicEntityPropertyMapping propertyMapping;
246
247 protected void addDiscriminatorToInsert(Insert insert) {}
248
249 protected void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {}
250
251 protected abstract int[] getSubclassColumnTableNumberClosure();
252
253 protected abstract int[] getSubclassFormulaTableNumberClosure();
254
255 public abstract String getSubclassTableName(int j);
256
257 protected abstract String[] getSubclassTableKeyColumns(int j);
258
259 protected abstract boolean isClassOrSuperclassTable(int j);
260
261 protected abstract int getSubclassTableSpan();
262
263 protected abstract int getTableSpan();
264
265 protected abstract boolean isTableCascadeDeleteEnabled(int j);
266
267 protected abstract String getTableName(int j);
268
269 protected abstract String[] getKeyColumns(int j);
270
271 protected abstract boolean isPropertyOfTable(int property, int j);
272
273 protected abstract int[] getPropertyTableNumbersInSelect();
274
275 protected abstract int[] getPropertyTableNumbers();
276
277 protected abstract int getSubclassPropertyTableNumber(int i);
278
279 protected abstract String filterFragment(String alias) throws MappingException;
280
281 private static final String DISCRIMINATOR_ALIAS = "clazz_";
282
283 public String getDiscriminatorColumnName() {
284 return DISCRIMINATOR_ALIAS;
285 }
286
287 protected String getDiscriminatorAlias() {
288 return DISCRIMINATOR_ALIAS;
289 }
290
291 protected String getDiscriminatorFormulaTemplate() {
292 return null;
293 }
294
295 protected boolean isInverseTable(int j) {
296 return false;
297 }
298
299 protected boolean isNullableTable(int j) {
300 return false;
301 }
302
303 protected boolean isNullableSubclassTable(int j) {
304 return false;
305 }
306
307 protected boolean isInverseSubclassTable(int j) {
308 return false;
309 }
310
311 public boolean isSubclassEntityName(String entityName) {
312 return entityMetamodel.getSubclassEntityNames().contains(entityName);
313 }
314
315 private boolean[] getTableHasColumns() {
316 return tableHasColumns;
317 }
318
319 public String[] getRootTableKeyColumnNames() {
320 return rootTableKeyColumnNames;
321 }
322
323 protected String[] getSQLUpdateByRowIdStrings() {
324 if ( sqlUpdateByRowIdString == null ) {
325 throw new AssertionFailure( "no update by row id" );
326 }
327 String[] result = new String[getTableSpan() + 1];
328 result[0] = sqlUpdateByRowIdString;
329 System.arraycopy( sqlUpdateStrings, 0, result, 1, getTableSpan() );
330 return result;
331 }
332
333 protected String[] getSQLLazyUpdateByRowIdStrings() {
334 if ( sqlLazyUpdateByRowIdString == null ) {
335 throw new AssertionFailure( "no update by row id" );
336 }
337 String[] result = new String[getTableSpan()];
338 result[0] = sqlLazyUpdateByRowIdString;
339 for ( int i = 1; i < getTableSpan(); i++ ) {
340 result[i] = sqlLazyUpdateStrings[i];
341 }
342 return result;
343 }
344
345 protected String getSQLSnapshotSelectString() {
346 return sqlSnapshotSelectString;
347 }
348
349 protected String getSQLLazySelectString() {
350 return sqlLazySelectString;
351 }
352
353 protected String[] getSQLDeleteStrings() {
354 return sqlDeleteStrings;
355 }
356
357 protected String[] getSQLInsertStrings() {
358 return sqlInsertStrings;
359 }
360
361 protected String[] getSQLUpdateStrings() {
362 return sqlUpdateStrings;
363 }
364
365 protected String[] getSQLLazyUpdateStrings() {
366 return sqlLazyUpdateStrings;
367 }
368
369 /**
370 * The query that inserts a row, letting the database generate an id
371 *
372 * @return The IDENTITY-based insertion query.
373 */
374 protected String getSQLIdentityInsertString() {
375 return sqlIdentityInsertString;
376 }
377
378 protected String getVersionSelectString() {
379 return sqlVersionSelectString;
380 }
381
382 protected boolean isInsertCallable(int j) {
383 return insertCallable[j];
384 }
385
386 protected boolean isUpdateCallable(int j) {
387 return updateCallable[j];
388 }
389
390 protected boolean isDeleteCallable(int j) {
391 return deleteCallable[j];
392 }
393
394 protected boolean isSubclassPropertyDeferred(String propertyName, String entityName) {
395 return false;
396 }
397
398 protected boolean isSubclassTableSequentialSelect(int j) {
399 return false;
400 }
401
402 public boolean hasSequentialSelect() {
403 return false;
404 }
405
406 /**
407 * Decide which tables need to be updated.
408 * <p/>
409 * The return here is an array of boolean values with each index corresponding
410 * to a given table in the scope of this persister.
411 *
412 * @param dirtyProperties The indices of all the entity properties considered dirty.
413 * @param hasDirtyCollection Whether any collections owned by the entity which were considered dirty.
414 *
415 * @return Array of booleans indicating which table require updating.
416 */
417 protected boolean[] getTableUpdateNeeded(final int[] dirtyProperties, boolean hasDirtyCollection) {
418
419 if ( dirtyProperties == null ) {
420 return getTableHasColumns(); // for objects that came in via update()
421 }
422 else {
423 boolean[] updateability = getPropertyUpdateability();
424 int[] propertyTableNumbers = getPropertyTableNumbers();
425 boolean[] tableUpdateNeeded = new boolean[ getTableSpan() ];
426 for ( int i = 0; i < dirtyProperties.length; i++ ) {
427 int property = dirtyProperties[i];
428 int table = propertyTableNumbers[property];
429 tableUpdateNeeded[table] = tableUpdateNeeded[table] ||
430 ( getPropertyColumnSpan(property) > 0 && updateability[property] );
431 }
432 if ( isVersioned() ) {
433 tableUpdateNeeded[0] = tableUpdateNeeded[0] ||
434 Versioning.isVersionIncrementRequired( dirtyProperties, hasDirtyCollection, getPropertyVersionability() );
435 }
436 return tableUpdateNeeded;
437 }
438 }
439
440 public boolean hasRowId() {
441 return rowIdName != null;
442 }
443
444 public AbstractEntityPersister(
445 final PersistentClass persistentClass,
446 final EntityRegionAccessStrategy cacheAccessStrategy,
447 final SessionFactoryImplementor factory) throws HibernateException {
448
449 // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
450 this.factory = factory;
451 this.cacheAccessStrategy = cacheAccessStrategy;
452 isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable();
453 this.cacheEntryStructure = factory.getSettings().isStructuredCacheEntriesEnabled() ?
454 (CacheEntryStructure) new StructuredCacheEntry(this) :
455 (CacheEntryStructure) new UnstructuredCacheEntry();
456
457 this.entityMetamodel = new EntityMetamodel( persistentClass, factory );
458
459 if ( persistentClass.hasPojoRepresentation() ) {
460 //TODO: this is currently specific to pojos, but need to be available for all entity-modes
461 Iterator iter = persistentClass.getSubclassIterator();
462 while ( iter.hasNext() ) {
463 PersistentClass pc = ( PersistentClass ) iter.next();
464 entityNameBySubclass.put( pc.getMappedClass(), pc.getEntityName() );
465 }
466 }
467 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
468
469 int batch = persistentClass.getBatchSize();
470 if ( batch == -1 ) {
471 batch = factory.getSettings().getDefaultBatchFetchSize();
472 }
473 batchSize = batch;
474 hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections();
475
476 propertyMapping = new BasicEntityPropertyMapping( this );
477
478 // IDENTIFIER
479
480 identifierColumnSpan = persistentClass.getIdentifier().getColumnSpan();
481 rootTableKeyColumnNames = new String[identifierColumnSpan];
482 identifierAliases = new String[identifierColumnSpan];
483
484 rowIdName = persistentClass.getRootTable().getRowId();
485
486 loaderName = persistentClass.getLoaderName();
487
488 Iterator iter = persistentClass.getIdentifier().getColumnIterator();
489 int i = 0;
490 while ( iter.hasNext() ) {
491 Column col = ( Column ) iter.next();
492 rootTableKeyColumnNames[i] = col.getQuotedName( factory.getDialect() );
493 identifierAliases[i] = col.getAlias( factory.getDialect(), persistentClass.getRootTable() );
494 i++;
495 }
496
497 // VERSION
498
499 if ( persistentClass.isVersioned() ) {
500 versionColumnName = ( ( Column ) persistentClass.getVersion().getColumnIterator().next() ).getQuotedName( factory.getDialect() );
501 }
502 else {
503 versionColumnName = null;
504 }
505
506 //WHERE STRING
507
508 sqlWhereString = StringHelper.isNotEmpty( persistentClass.getWhere() ) ? "( " + persistentClass.getWhere() + ") " : null;
509 sqlWhereStringTemplate = sqlWhereString == null ?
510 null :
511 Template.renderWhereStringTemplate( sqlWhereString, factory.getDialect(), factory.getSqlFunctionRegistry() );
512
513 // PROPERTIES
514
515 final boolean lazyAvailable = isInstrumented(EntityMode.POJO);
516
517 int hydrateSpan = entityMetamodel.getPropertySpan();
518 propertyColumnSpans = new int[hydrateSpan];
519 propertySubclassNames = new String[hydrateSpan];
520 propertyColumnAliases = new String[hydrateSpan][];
521 propertyColumnNames = new String[hydrateSpan][];
522 propertyColumnFormulaTemplates = new String[hydrateSpan][];
523 propertyUniqueness = new boolean[hydrateSpan];
524 propertySelectable = new boolean[hydrateSpan];
525 propertyColumnUpdateable = new boolean[hydrateSpan][];
526 propertyColumnInsertable = new boolean[hydrateSpan][];
527 HashSet thisClassProperties = new HashSet();
528
529 lazyProperties = new HashSet();
530 ArrayList lazyNames = new ArrayList();
531 ArrayList lazyNumbers = new ArrayList();
532 ArrayList lazyTypes = new ArrayList();
533 ArrayList lazyColAliases = new ArrayList();
534
535 iter = persistentClass.getPropertyClosureIterator();
536 i = 0;
537 boolean foundFormula = false;
538 while ( iter.hasNext() ) {
539 Property prop = ( Property ) iter.next();
540 thisClassProperties.add( prop );
541
542 int span = prop.getColumnSpan();
543 propertyColumnSpans[i] = span;
544 propertySubclassNames[i] = prop.getPersistentClass().getEntityName();
545 String[] colNames = new String[span];
546 String[] colAliases = new String[span];
547 String[] templates = new String[span];
548 Iterator colIter = prop.getColumnIterator();
549 int k = 0;
550 while ( colIter.hasNext() ) {
551 Selectable thing = ( Selectable ) colIter.next();
552 colAliases[k] = thing.getAlias( factory.getDialect() , prop.getValue().getTable() );
553 if ( thing.isFormula() ) {
554 foundFormula = true;
555 templates[k] = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
556 }
557 else {
558 colNames[k] = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
559 }
560 k++;
561 }
562 propertyColumnNames[i] = colNames;
563 propertyColumnFormulaTemplates[i] = templates;
564 propertyColumnAliases[i] = colAliases;
565
566 if ( lazyAvailable && prop.isLazy() ) {
567 lazyProperties.add( prop.getName() );
568 lazyNames.add( prop.getName() );
569 lazyNumbers.add( new Integer( i ) );
570 lazyTypes.add( prop.getValue().getType() );
571 lazyColAliases.add( colAliases );
572 }
573
574 propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
575 propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
576
577 propertySelectable[i] = prop.isSelectable();
578
579 propertyUniqueness[i] = prop.getValue().isAlternateUniqueKey();
580
581 i++;
582
583 }
584 hasFormulaProperties = foundFormula;
585 lazyPropertyColumnAliases = ArrayHelper.to2DStringArray( lazyColAliases );
586 lazyPropertyNames = ArrayHelper.toStringArray( lazyNames );
587 lazyPropertyNumbers = ArrayHelper.toIntArray( lazyNumbers );
588 lazyPropertyTypes = ArrayHelper.toTypeArray( lazyTypes );
589
590 // SUBCLASS PROPERTY CLOSURE
591
592 ArrayList columns = new ArrayList();
593 ArrayList columnsLazy = new ArrayList();
594 ArrayList aliases = new ArrayList();
595 ArrayList formulas = new ArrayList();
596 ArrayList formulaAliases = new ArrayList();
597 ArrayList formulaTemplates = new ArrayList();
598 ArrayList formulasLazy = new ArrayList();
599 ArrayList types = new ArrayList();
600 ArrayList names = new ArrayList();
601 ArrayList classes = new ArrayList();
602 ArrayList templates = new ArrayList();
603 ArrayList propColumns = new ArrayList();
604 ArrayList joinedFetchesList = new ArrayList();
605 ArrayList cascades = new ArrayList();
606 ArrayList definedBySubclass = new ArrayList();
607 ArrayList propColumnNumbers = new ArrayList();
608 ArrayList propFormulaNumbers = new ArrayList();
609 ArrayList columnSelectables = new ArrayList();
610 ArrayList propNullables = new ArrayList();
611
612 iter = persistentClass.getSubclassPropertyClosureIterator();
613 while ( iter.hasNext() ) {
614 Property prop = ( Property ) iter.next();
615 names.add( prop.getName() );
616 classes.add( prop.getPersistentClass().getEntityName() );
617 boolean isDefinedBySubclass = !thisClassProperties.contains( prop );
618 definedBySubclass.add( Boolean.valueOf( isDefinedBySubclass ) );
619 propNullables.add( Boolean.valueOf( prop.isOptional() || isDefinedBySubclass ) ); //TODO: is this completely correct?
620 types.add( prop.getType() );
621
622 Iterator colIter = prop.getColumnIterator();
623 String[] cols = new String[prop.getColumnSpan()];
624 String[] forms = new String[prop.getColumnSpan()];
625 int[] colnos = new int[prop.getColumnSpan()];
626 int[] formnos = new int[prop.getColumnSpan()];
627 int l = 0;
628 Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable );
629 while ( colIter.hasNext() ) {
630 Selectable thing = ( Selectable ) colIter.next();
631 if ( thing.isFormula() ) {
632 String template = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
633 formnos[l] = formulaTemplates.size();
634 colnos[l] = -1;
635 formulaTemplates.add( template );
636 forms[l] = template;
637 formulas.add( thing.getText( factory.getDialect() ) );
638 formulaAliases.add( thing.getAlias( factory.getDialect() ) );
639 formulasLazy.add( lazy );
640 }
641 else {
642 String colName = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
643 colnos[l] = columns.size(); //before add :-)
644 formnos[l] = -1;
645 columns.add( colName );
646 cols[l] = colName;
647 aliases.add( thing.getAlias( factory.getDialect(), prop.getValue().getTable() ) );
648 columnsLazy.add( lazy );
649 columnSelectables.add( Boolean.valueOf( prop.isSelectable() ) );
650 }
651 l++;
652 }
653 propColumns.add( cols );
654 templates.add( forms );
655 propColumnNumbers.add( colnos );
656 propFormulaNumbers.add( formnos );
657
658 joinedFetchesList.add( prop.getValue().getFetchMode() );
659 cascades.add( prop.getCascadeStyle() );
660 }
661 subclassColumnClosure = ArrayHelper.toStringArray( columns );
662 subclassColumnAliasClosure = ArrayHelper.toStringArray( aliases );
663 subclassColumnLazyClosure = ArrayHelper.toBooleanArray( columnsLazy );
664 subclassColumnSelectableClosure = ArrayHelper.toBooleanArray( columnSelectables );
665
666 subclassFormulaClosure = ArrayHelper.toStringArray( formulas );
667 subclassFormulaTemplateClosure = ArrayHelper.toStringArray( formulaTemplates );
668 subclassFormulaAliasClosure = ArrayHelper.toStringArray( formulaAliases );
669 subclassFormulaLazyClosure = ArrayHelper.toBooleanArray( formulasLazy );
670
671 subclassPropertyNameClosure = ArrayHelper.toStringArray( names );
672 subclassPropertySubclassNameClosure = ArrayHelper.toStringArray( classes );
673 subclassPropertyTypeClosure = ArrayHelper.toTypeArray( types );
674 subclassPropertyNullabilityClosure = ArrayHelper.toBooleanArray( propNullables );
675 subclassPropertyFormulaTemplateClosure = ArrayHelper.to2DStringArray( templates );
676 subclassPropertyColumnNameClosure = ArrayHelper.to2DStringArray( propColumns );
677 subclassPropertyColumnNumberClosure = ArrayHelper.to2DIntArray( propColumnNumbers );
678 subclassPropertyFormulaNumberClosure = ArrayHelper.to2DIntArray( propFormulaNumbers );
679
680 subclassPropertyCascadeStyleClosure = new CascadeStyle[cascades.size()];
681 iter = cascades.iterator();
682 int j = 0;
683 while ( iter.hasNext() ) {
684 subclassPropertyCascadeStyleClosure[j++] = ( CascadeStyle ) iter.next();
685 }
686 subclassPropertyFetchModeClosure = new FetchMode[joinedFetchesList.size()];
687 iter = joinedFetchesList.iterator();
688 j = 0;
689 while ( iter.hasNext() ) {
690 subclassPropertyFetchModeClosure[j++] = ( FetchMode ) iter.next();
691 }
692
693 propertyDefinedOnSubclass = new boolean[definedBySubclass.size()];
694 iter = definedBySubclass.iterator();
695 j = 0;
696 while ( iter.hasNext() ) {
697 propertyDefinedOnSubclass[j++] = ( ( Boolean ) iter.next() ).booleanValue();
698 }
699
700 // Handle any filters applied to the class level
701 filterHelper = new FilterHelper( persistentClass.getFilterMap(), factory.getDialect(), factory.getSqlFunctionRegistry() );
702
703 temporaryIdTableName = persistentClass.getTemporaryIdTableName();
704 temporaryIdTableDDL = persistentClass.getTemporaryIdTableDDL();
705 }
706
707 protected String generateLazySelectString() {
708
709 if ( !entityMetamodel.hasLazyProperties() ) {
710 return null;
711 }
712
713 HashSet tableNumbers = new HashSet();
714 ArrayList columnNumbers = new ArrayList();
715 ArrayList formulaNumbers = new ArrayList();
716 for ( int i = 0; i < lazyPropertyNames.length; i++ ) {
717 // all this only really needs to consider properties
718 // of this class, not its subclasses, but since we
719 // are reusing code used for sequential selects, we
720 // use the subclass closure
721 int propertyNumber = getSubclassPropertyIndex( lazyPropertyNames[i] );
722
723 int tableNumber = getSubclassPropertyTableNumber( propertyNumber );
724 tableNumbers.add( new Integer( tableNumber ) );
725
726 int[] colNumbers = subclassPropertyColumnNumberClosure[propertyNumber];
727 for ( int j = 0; j < colNumbers.length; j++ ) {
728 if ( colNumbers[j]!=-1 ) {
729 columnNumbers.add( new Integer( colNumbers[j] ) );
730 }
731 }
732 int[] formNumbers = subclassPropertyFormulaNumberClosure[propertyNumber];
733 for ( int j = 0; j < formNumbers.length; j++ ) {
734 if ( formNumbers[j]!=-1 ) {
735 formulaNumbers.add( new Integer( formNumbers[j] ) );
736 }
737 }
738 }
739
740 if ( columnNumbers.size()==0 && formulaNumbers.size()==0 ) {
741 // only one-to-one is lazy fetched
742 return null;
743 }
744
745 return renderSelect( ArrayHelper.toIntArray( tableNumbers ),
746 ArrayHelper.toIntArray( columnNumbers ),
747 ArrayHelper.toIntArray( formulaNumbers ) );
748
749 }
750
751 public Object initializeLazyProperty(String fieldName, Object entity, SessionImplementor session)
752 throws HibernateException {
753
754 final Serializable id = session.getContextEntityIdentifier( entity );
755
756 final EntityEntry entry = session.getPersistenceContext().getEntry( entity );
757 if ( entry == null ) {
758 throw new HibernateException( "entity is not associated with the session: " + id );
759 }
760
761 if ( log.isTraceEnabled() ) {
762 log.trace(
763 "initializing lazy properties of: " +
764 MessageHelper.infoString( this, id, getFactory() ) +
765 ", field access: " + fieldName
766 );
767 }
768
769 if ( hasCache() ) {
770 CacheKey cacheKey = new CacheKey(id, getIdentifierType(), getEntityName(), session.getEntityMode(), getFactory() );
771 Object ce = getCacheAccessStrategy().get( cacheKey, session.getTimestamp() );
772 if (ce!=null) {
773 CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure(ce, factory);
774 if ( !cacheEntry.areLazyPropertiesUnfetched() ) {
775 //note early exit here:
776 return initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry );
777 }
778 }
779 }
780
781 return initializeLazyPropertiesFromDatastore( fieldName, entity, session, id, entry );
782
783 }
784
785 private Object initializeLazyPropertiesFromDatastore(
786 final String fieldName,
787 final Object entity,
788 final SessionImplementor session,
789 final Serializable id,
790 final EntityEntry entry) {
791
792 if ( !hasLazyProperties() ) {
793 throw new AssertionFailure("no lazy properties");
794 }
795
796 log.trace("initializing lazy properties from datastore");
797
798 try {
799
800 Object result = null;
801 PreparedStatement ps = null;
802 try {
803 final String lazySelect = getSQLLazySelectString();
804 ResultSet rs = null;
805 try {
806 if ( lazySelect != null ) {
807 // null sql means that the only lazy properties
808 // are shared PK one-to-one associations which are
809 // handled differently in the Type#nullSafeGet code...
810 ps = session.getBatcher().prepareSelectStatement(lazySelect);
811 getIdentifierType().nullSafeSet( ps, id, 1, session );
812 rs = ps.executeQuery();
813 rs.next();
814 }
815 final Object[] snapshot = entry.getLoadedState();
816 for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
817 Object propValue = lazyPropertyTypes[j].nullSafeGet( rs, lazyPropertyColumnAliases[j], session, entity );
818 if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
819 result = propValue;
820 }
821 }
822 }
823 finally {
824 if ( rs != null ) {
825 rs.close();
826 }
827 }
828 }
829 finally {
830 if ( ps != null ) {
831 session.getBatcher().closeStatement( ps );
832 }
833 }
834
835 log.trace( "done initializing lazy properties" );
836
837 return result;
838
839 }
840 catch ( SQLException sqle ) {
841 throw JDBCExceptionHelper.convert(
842 getFactory().getSQLExceptionConverter(),
843 sqle,
844 "could not initialize lazy properties: " +
845 MessageHelper.infoString( this, id, getFactory() ),
846 getSQLLazySelectString()
847 );
848 }
849 }
850
851 private Object initializeLazyPropertiesFromCache(
852 final String fieldName,
853 final Object entity,
854 final SessionImplementor session,
855 final EntityEntry entry,
856 final CacheEntry cacheEntry
857 ) {
858
859 log.trace("initializing lazy properties from second-level cache");
860
861 Object result = null;
862 Serializable[] disassembledValues = cacheEntry.getDisassembledState();
863 final Object[] snapshot = entry.getLoadedState();
864 for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
865 final Object propValue = lazyPropertyTypes[j].assemble(
866 disassembledValues[ lazyPropertyNumbers[j] ],
867 session,
868 entity
869 );
870 if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
871 result = propValue;
872 }
873 }
874
875 log.trace( "done initializing lazy properties" );
876
877 return result;
878 }
879
880 private boolean initializeLazyProperty(
881 final String fieldName,
882 final Object entity,
883 final SessionImplementor session,
884 final Object[] snapshot,
885 final int j,
886 final Object propValue) {
887 setPropertyValue( entity, lazyPropertyNumbers[j], propValue, session.getEntityMode() );
888 if (snapshot != null) {
889 // object have been loaded with setReadOnly(true); HHH-2236
890 snapshot[ lazyPropertyNumbers[j] ] = lazyPropertyTypes[j].deepCopy( propValue, session.getEntityMode(), factory );
891 }
892 return fieldName.equals( lazyPropertyNames[j] );
893 }
894
895 public boolean isBatchable() {
896 return optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_NONE ||
897 ( !isVersioned() && optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_VERSION ) ||
898 getFactory().getSettings().isJdbcBatchVersionedData();
899 }
900
901 public Serializable[] getQuerySpaces() {
902 return getPropertySpaces();
903 }
904
905 protected Set getLazyProperties() {
906 return lazyProperties;
907 }
908
909 public boolean isBatchLoadable() {
910 return batchSize > 1;
911 }
912
913 public String[] getIdentifierColumnNames() {
914 return rootTableKeyColumnNames;
915 }
916
917 protected int getIdentifierColumnSpan() {
918 return identifierColumnSpan;
919 }
920
921 protected String[] getIdentifierAliases() {
922 return identifierAliases;
923 }
924
925 public String getVersionColumnName() {
926 return versionColumnName;
927 }
928
929 protected String getVersionedTableName() {
930 return getTableName( 0 );
931 }
932
933 protected boolean[] getSubclassColumnLazyiness() {
934 return subclassColumnLazyClosure;
935 }
936
937 protected boolean[] getSubclassFormulaLazyiness() {
938 return subclassFormulaLazyClosure;
939 }
940
941 /**
942 * We can't immediately add to the cache if we have formulas
943 * which must be evaluated, or if we have the possibility of
944 * two concurrent updates to the same item being merged on
945 * the database. This can happen if (a) the item is not
946 * versioned and either (b) we have dynamic update enabled
947 * or (c) we have multiple tables holding the state of the
948 * item.
949 */
950 public boolean isCacheInvalidationRequired() {
951 return hasFormulaProperties() ||
952 ( !isVersioned() && ( entityMetamodel.isDynamicUpdate() || getTableSpan() > 1 ) );
953 }
954
955 public boolean isLazyPropertiesCacheable() {
956 return isLazyPropertiesCacheable;
957 }
958
959 public String selectFragment(String alias, String suffix) {
960 return identifierSelectFragment( alias, suffix ) +
961 propertySelectFragment( alias, suffix, false );
962 }
963
964 public String[] getIdentifierAliases(String suffix) {
965 // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
966 // was toUnqotedAliasStrings( getIdentiferColumnNames() ) before - now tried
967 // to remove that unqoting and missing aliases..
968 return new Alias( suffix ).toAliasStrings( getIdentifierAliases() );
969 }
970
971 public String[] getPropertyAliases(String suffix, int i) {
972 // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
973 return new Alias( suffix ).toUnquotedAliasStrings( propertyColumnAliases[i] );
974 }
975
976 public String getDiscriminatorAlias(String suffix) {
977 // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
978 // was toUnqotedAliasStrings( getdiscriminatorColumnName() ) before - now tried
979 // to remove that unqoting and missing aliases..
980 return entityMetamodel.hasSubclasses() ?
981 new Alias( suffix ).toAliasString( getDiscriminatorAlias() ) :
982 null;
983 }
984
985 public String identifierSelectFragment(String name, String suffix) {
986 return new SelectFragment()
987 .setSuffix( suffix )
988 .addColumns( name, getIdentifierColumnNames(), getIdentifierAliases() )
989 .toFragmentString()
990 .substring( 2 ); //strip leading ", "
991 }
992
993
994 public String propertySelectFragment(String name, String suffix, boolean allProperties) {
995
996 SelectFragment select = new SelectFragment()
997 .setSuffix( suffix )
998 .setUsedAliases( getIdentifierAliases() );
999
1000 int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
1001 String[] columnAliases = getSubclassColumnAliasClosure();
1002 String[] columns = getSubclassColumnClosure();
1003 for ( int i = 0; i < getSubclassColumnClosure().length; i++ ) {
1004 boolean selectable = ( allProperties || !subclassColumnLazyClosure[i] ) &&
1005 !isSubclassTableSequentialSelect( columnTableNumbers[i] ) &&
1006 subclassColumnSelectableClosure[i];
1007 if ( selectable ) {
1008 String subalias = generateTableAlias( name, columnTableNumbers[i] );
1009 select.addColumn( subalias, columns[i], columnAliases[i] );
1010 }
1011 }
1012
1013 int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
1014 String[] formulaTemplates = getSubclassFormulaTemplateClosure();
1015 String[] formulaAliases = getSubclassFormulaAliasClosure();
1016 for ( int i = 0; i < getSubclassFormulaTemplateClosure().length; i++ ) {
1017 boolean selectable = ( allProperties || !subclassFormulaLazyClosure[i] )
1018 && !isSubclassTableSequentialSelect( formulaTableNumbers[i] );
1019 if ( selectable ) {
1020 String subalias = generateTableAlias( name, formulaTableNumbers[i] );
1021 select.addFormula( subalias, formulaTemplates[i], formulaAliases[i] );
1022 }
1023 }
1024
1025 if ( entityMetamodel.hasSubclasses() ) {
1026 addDiscriminatorToSelect( select, name, suffix );
1027 }
1028
1029 if ( hasRowId() ) {
1030 select.addColumn( name, rowIdName, ROWID_ALIAS );
1031 }
1032
1033 return select.toFragmentString();
1034 }
1035
1036 public Object[] getDatabaseSnapshot(Serializable id, SessionImplementor session)
1037 throws HibernateException {
1038
1039 if ( log.isTraceEnabled() ) {
1040 log.trace( "Getting current persistent state for: " + MessageHelper.infoString( this, id, getFactory() ) );
1041 }
1042
1043 try {
1044 PreparedStatement ps = session.getBatcher().prepareSelectStatement( getSQLSnapshotSelectString() );
1045 try {
1046 getIdentifierType().nullSafeSet( ps, id, 1, session );
1047 //if ( isVersioned() ) getVersionType().nullSafeSet( ps, version, getIdentifierColumnSpan()+1, session );
1048 ResultSet rs = ps.executeQuery();
1049 try {
1050 //if there is no resulting row, return null
1051 if ( !rs.next() ) {
1052 return null;
1053 }
1054
1055 //otherwise return the "hydrated" state (ie. associations are not resolved)
1056 Type[] types = getPropertyTypes();
1057 Object[] values = new Object[types.length];
1058 boolean[] includeProperty = getPropertyUpdateability();
1059 for ( int i = 0; i < types.length; i++ ) {
1060 if ( includeProperty[i] ) {
1061 values[i] = types[i].hydrate( rs, getPropertyAliases( "", i ), session, null ); //null owner ok??
1062 }
1063 }
1064 return values;
1065 }
1066 finally {
1067 rs.close();
1068 }
1069 }
1070 finally {
1071 session.getBatcher().closeStatement( ps );
1072 }
1073 }
1074 catch ( SQLException sqle ) {
1075 throw JDBCExceptionHelper.convert(
1076 getFactory().getSQLExceptionConverter(),
1077 sqle,
1078 "could not retrieve snapshot: " +
1079 MessageHelper.infoString( this, id, getFactory() ),
1080 getSQLSnapshotSelectString()
1081 );
1082 }
1083
1084 }
1085
1086 /**
1087 * Generate the SQL that selects the version number by id
1088 */
1089 protected String generateSelectVersionString() {
1090 SimpleSelect select = new SimpleSelect( getFactory().getDialect() )
1091 .setTableName( getVersionedTableName() );
1092 if ( isVersioned() ) {
1093 select.addColumn( versionColumnName );
1094 }
1095 else {
1096 select.addColumns( rootTableKeyColumnNames );
1097 }
1098 if ( getFactory().getSettings().isCommentsEnabled() ) {
1099 select.setComment( "get version " + getEntityName() );
1100 }
1101 return select.addCondition( rootTableKeyColumnNames, "=?" ).toStatementString();
1102 }
1103
1104 protected String generateInsertGeneratedValuesSelectString() {
1105 return generateGeneratedValuesSelectString( getPropertyInsertGenerationInclusions() );
1106 }
1107
1108 protected String generateUpdateGeneratedValuesSelectString() {
1109 return generateGeneratedValuesSelectString( getPropertyUpdateGenerationInclusions() );
1110 }
1111
1112 private String generateGeneratedValuesSelectString(ValueInclusion[] inclusions) {
1113 Select select = new Select( getFactory().getDialect() );
1114
1115 if ( getFactory().getSettings().isCommentsEnabled() ) {
1116 select.setComment( "get generated state " + getEntityName() );
1117 }
1118
1119 String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
1120
1121 // Here we render the select column list based on the properties defined as being generated.
1122 // For partial component generation, we currently just re-select the whole component
1123 // rather than trying to handle the individual generated portions.
1124 String selectClause = concretePropertySelectFragment( getRootAlias(), inclusions );
1125 selectClause = selectClause.substring( 2 );
1126
1127 String fromClause = fromTableFragment( getRootAlias() ) +
1128 fromJoinFragment( getRootAlias(), true, false );
1129
1130 String whereClause = new StringBuffer()
1131 .append( StringHelper.join( "=? and ", aliasedIdColumns ) )
1132 .append( "=?" )
1133 .append( whereJoinFragment( getRootAlias(), true, false ) )
1134 .toString();
1135
1136 return select.setSelectClause( selectClause )
1137 .setFromClause( fromClause )
1138 .setOuterJoins( "", "" )
1139 .setWhereClause( whereClause )
1140 .toStatementString();
1141 }
1142
1143 protected static interface InclusionChecker {
1144 public boolean includeProperty(int propertyNumber);
1145 }
1146
1147 protected String concretePropertySelectFragment(String alias, final ValueInclusion[] inclusions) {
1148 return concretePropertySelectFragment(
1149 alias,
1150 new InclusionChecker() {
1151 // TODO : currently we really do not handle ValueInclusion.PARTIAL...
1152 // ValueInclusion.PARTIAL would indicate parts of a component need to
1153 // be included in the select; currently we then just render the entire
1154 // component into the select clause in that case.
1155 public boolean includeProperty(int propertyNumber) {
1156 return inclusions[propertyNumber] != ValueInclusion.NONE;
1157 }
1158 }
1159 );
1160 }
1161
1162 protected String concretePropertySelectFragment(String alias, final boolean[] includeProperty) {
1163 return concretePropertySelectFragment(
1164 alias,
1165 new InclusionChecker() {
1166 public boolean includeProperty(int propertyNumber) {
1167 return includeProperty[propertyNumber];
1168 }
1169 }
1170 );
1171 }
1172
1173 protected String concretePropertySelectFragment(String alias, InclusionChecker inclusionChecker) {
1174 int propertyCount = getPropertyNames().length;
1175 int[] propertyTableNumbers = getPropertyTableNumbersInSelect();
1176 SelectFragment frag = new SelectFragment();
1177 for ( int i = 0; i < propertyCount; i++ ) {
1178 if ( inclusionChecker.includeProperty( i ) ) {
1179 frag.addColumns(
1180 generateTableAlias( alias, propertyTableNumbers[i] ),
1181 propertyColumnNames[i],
1182 propertyColumnAliases[i]
1183 );
1184 frag.addFormulas(
1185 generateTableAlias( alias, propertyTableNumbers[i] ),
1186 propertyColumnFormulaTemplates[i],
1187 propertyColumnAliases[i]
1188 );
1189 }
1190 }
1191 return frag.toFragmentString();
1192 }
1193
1194 protected String generateSnapshotSelectString() {
1195
1196 //TODO: should we use SELECT .. FOR UPDATE?
1197
1198 Select select = new Select( getFactory().getDialect() );
1199
1200 if ( getFactory().getSettings().isCommentsEnabled() ) {
1201 select.setComment( "get current state " + getEntityName() );
1202 }
1203
1204 String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
1205 String selectClause = StringHelper.join( ", ", aliasedIdColumns ) +
1206 concretePropertySelectFragment( getRootAlias(), getPropertyUpdateability() );
1207
1208 String fromClause = fromTableFragment( getRootAlias() ) +
1209 fromJoinFragment( getRootAlias(), true, false );
1210
1211 String whereClause = new StringBuffer()
1212 .append( StringHelper.join( "=? and ",
1213 aliasedIdColumns ) )
1214 .append( "=?" )
1215 .append( whereJoinFragment( getRootAlias(), true, false ) )
1216 .toString();
1217
1218 /*if ( isVersioned() ) {
1219 where.append(" and ")
1220 .append( getVersionColumnName() )
1221 .append("=?");
1222 }*/
1223
1224 return select.setSelectClause( selectClause )
1225 .setFromClause( fromClause )
1226 .setOuterJoins( "", "" )
1227 .setWhereClause( whereClause )
1228 .toStatementString();
1229 }
1230
1231 public Object forceVersionIncrement(Serializable id, Object currentVersion, SessionImplementor session) {
1232 if ( !isVersioned() ) {
1233 throw new AssertionFailure( "cannot force version increment on non-versioned entity" );
1234 }
1235
1236 if ( isVersionPropertyGenerated() ) {
1237 // the difficulty here is exactly what do we update in order to
1238 // force the version to be incremented in the db...
1239 throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties" );
1240 }
1241
1242 Object nextVersion = getVersionType().next( currentVersion, session );
1243 if ( log.isTraceEnabled() ) {
1244 log.trace(
1245 "Forcing version increment [" + MessageHelper.infoString( this, id, getFactory() ) +
1246 "; " + getVersionType().toLoggableString( currentVersion, getFactory() ) +
1247 " -> " + getVersionType().toLoggableString( nextVersion, getFactory() ) + "]"
1248 );
1249 }
1250
1251 // todo : cache this sql...
1252 String versionIncrementString = generateVersionIncrementUpdateString();
1253 PreparedStatement st = null;
1254 try {
1255 try {
1256 st = session.getBatcher().prepareStatement( versionIncrementString );
1257 getVersionType().nullSafeSet( st, nextVersion, 1, session );
1258 getIdentifierType().nullSafeSet( st, id, 2, session );
1259 getVersionType().nullSafeSet( st, currentVersion, 2 + getIdentifierColumnSpan(), session );
1260 int rows = st.executeUpdate();
1261 if ( rows != 1 ) {
1262 throw new StaleObjectStateException( getEntityName(), id );
1263 }
1264 }
1265 finally {
1266 session.getBatcher().closeStatement( st );
1267 }
1268 }
1269 catch ( SQLException sqle ) {
1270 throw JDBCExceptionHelper.convert(
1271 getFactory().getSQLExceptionConverter(),
1272 sqle,
1273 "could not retrieve version: " +
1274 MessageHelper.infoString( this, id, getFactory() ),
1275 getVersionSelectString()
1276 );
1277 }
1278
1279 return nextVersion;
1280 }
1281
1282 private String generateVersionIncrementUpdateString() {
1283 Update update = new Update( getFactory().getDialect() );
1284 update.setTableName( getTableName( 0 ) );
1285 if ( getFactory().getSettings().isCommentsEnabled() ) {
1286 update.setComment( "forced version increment" );
1287 }
1288 update.addColumn( getVersionColumnName() );
1289 update.setPrimaryKeyColumnNames( getIdentifierColumnNames() );
1290 update.setVersionColumnName( getVersionColumnName() );
1291 return update.toStatementString();
1292 }
1293
1294 /**
1295 * Retrieve the version number
1296 */
1297 public Object getCurrentVersion(Serializable id, SessionImplementor session) throws HibernateException {
1298
1299 if ( log.isTraceEnabled() ) {
1300 log.trace( "Getting version: " + MessageHelper.infoString( this, id, getFactory() ) );
1301 }
1302
1303 try {
1304
1305 PreparedStatement st = session.getBatcher().prepareSelectStatement( getVersionSelectString() );
1306 try {
1307 getIdentifierType().nullSafeSet( st, id, 1, session );
1308
1309 ResultSet rs = st.executeQuery();
1310 try {
1311 if ( !rs.next() ) {
1312 return null;
1313 }
1314 if ( !isVersioned() ) {
1315 return this;
1316 }
1317 return getVersionType().nullSafeGet( rs, getVersionColumnName(), session, null );
1318 }
1319 finally {
1320 rs.close();
1321 }
1322 }
1323 finally {
1324 session.getBatcher().closeStatement( st );
1325 }
1326
1327 }
1328 catch ( SQLException sqle ) {
1329 throw JDBCExceptionHelper.convert(
1330 getFactory().getSQLExceptionConverter(),
1331 sqle,
1332 "could not retrieve version: " +
1333 MessageHelper.infoString( this, id, getFactory() ),
1334 getVersionSelectString()
1335 );
1336 }
1337
1338 }
1339
1340 protected void initLockers() {
1341 lockers.put( LockMode.READ, generateLocker( LockMode.READ ) );
1342 lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) );
1343 lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) );
1344 lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) );
1345 }
1346
1347 protected LockingStrategy generateLocker(LockMode lockMode) {
1348 return factory.getDialect().getLockingStrategy( this, lockMode );
1349 }
1350
1351 private LockingStrategy getLocker(LockMode lockMode) {
1352 return ( LockingStrategy ) lockers.get( lockMode );
1353 }
1354
1355 public void lock(
1356 Serializable id,
1357 Object version,
1358 Object object,
1359 LockMode lockMode,
1360 SessionImplementor session) throws HibernateException {
1361 getLocker( lockMode ).lock( id, version, object, session );
1362 }
1363
1364 public String getRootTableName() {
1365 return getSubclassTableName( 0 );
1366 }
1367
1368 public String getRootTableAlias(String drivingAlias) {
1369 return drivingAlias;
1370 }
1371
1372 public String[] getRootTableIdentifierColumnNames() {
1373 return getRootTableKeyColumnNames();
1374 }
1375
1376 public String[] toColumns(String alias, String propertyName) throws QueryException {
1377 return propertyMapping.toColumns( alias, propertyName );
1378 }
1379
1380 public String[] toColumns(String propertyName) throws QueryException {
1381 return propertyMapping.getColumnNames( propertyName );
1382 }
1383
1384 public Type toType(String propertyName) throws QueryException {
1385 return propertyMapping.toType( propertyName );
1386 }
1387
1388 public String[] getPropertyColumnNames(String propertyName) {
1389 return propertyMapping.getColumnNames( propertyName );
1390 }
1391
1392 /**
1393 * Warning:
1394 * When there are duplicated property names in the subclasses
1395 * of the class, this method may return the wrong table
1396 * number for the duplicated subclass property (note that
1397 * SingleTableEntityPersister defines an overloaded form
1398 * which takes the entity name.
1399 */
1400 public int getSubclassPropertyTableNumber(String propertyPath) {
1401 String rootPropertyName = StringHelper.root(propertyPath);
1402 Type type = propertyMapping.toType(rootPropertyName);
1403 if ( type.isAssociationType() ) {
1404 AssociationType assocType = ( AssociationType ) type;
1405 if ( assocType.useLHSPrimaryKey() ) {
1406 // performance op to avoid the array search
1407 return 0;
1408 }
1409 else if ( type.isCollectionType() ) {
1410 // properly handle property-ref-based associations
1411 rootPropertyName = assocType.getLHSPropertyName();
1412 }
1413 }
1414 //Enable for HHH-440, which we don't like:
1415 /*if ( type.isComponentType() && !propertyName.equals(rootPropertyName) ) {
1416 String unrooted = StringHelper.unroot(propertyName);
1417 int idx = ArrayHelper.indexOf( getSubclassColumnClosure(), unrooted );
1418 if ( idx != -1 ) {
1419 return getSubclassColumnTableNumberClosure()[idx];
1420 }
1421 }*/
1422 int index = ArrayHelper.indexOf( getSubclassPropertyNameClosure(), rootPropertyName); //TODO: optimize this better!
1423 return index==-1 ? 0 : getSubclassPropertyTableNumber(index);
1424 }
1425
1426 public Declarer getSubclassPropertyDeclarer(String propertyPath) {
1427 int tableIndex = getSubclassPropertyTableNumber( propertyPath );
1428 if ( tableIndex == 0 ) {
1429 return Declarer.CLASS;
1430 }
1431 else if ( isClassOrSuperclassTable( tableIndex ) ) {
1432 return Declarer.SUPERCLASS;
1433 }
1434 else {
1435 return Declarer.SUBCLASS;
1436 }
1437 }
1438
1439 protected String generateTableAlias(String rootAlias, int tableNumber) {
1440 if ( tableNumber == 0 ) {
1441 return rootAlias;
1442 }
1443 StringBuffer buf = new StringBuffer().append( rootAlias );
1444 if ( !rootAlias.endsWith( "_" ) ) {
1445 buf.append( '_' );
1446 }
1447 return buf.append( tableNumber ).append( '_' ).toString();
1448 }
1449
1450 public String[] toColumns(String name, final int i) {
1451 final String alias = generateTableAlias( name, getSubclassPropertyTableNumber( i ) );
1452 String[] cols = getSubclassPropertyColumnNames( i );
1453 String[] templates = getSubclassPropertyFormulaTemplateClosure()[i];
1454 String[] result = new String[cols.length];
1455 for ( int j = 0; j < cols.length; j++ ) {
1456 if ( cols[j] == null ) {
1457 result[j] = StringHelper.replace( templates[j], Template.TEMPLATE, alias );
1458 }
1459 else {
1460 result[j] = StringHelper.qualify( alias, cols[j] );
1461 }
1462 }
1463 return result;
1464 }
1465
1466 private int getSubclassPropertyIndex(String propertyName) {
1467 return ArrayHelper.indexOf(subclassPropertyNameClosure, propertyName);
1468 }
1469
1470 protected String[] getPropertySubclassNames() {
1471 return propertySubclassNames;
1472 }
1473
1474 public String[] getPropertyColumnNames(int i) {
1475 return propertyColumnNames[i];
1476 }
1477
1478 protected int getPropertyColumnSpan(int i) {
1479 return propertyColumnSpans[i];
1480 }
1481
1482 protected boolean hasFormulaProperties() {
1483 return hasFormulaProperties;
1484 }
1485
1486 public FetchMode getFetchMode(int i) {
1487 return subclassPropertyFetchModeClosure[i];
1488 }
1489
1490 public CascadeStyle getCascadeStyle(int i) {
1491 return subclassPropertyCascadeStyleClosure[i];
1492 }
1493
1494 public Type getSubclassPropertyType(int i) {
1495 return subclassPropertyTypeClosure[i];
1496 }
1497
1498 public String getSubclassPropertyName(int i) {
1499 return subclassPropertyNameClosure[i];
1500 }
1501
1502 public int countSubclassProperties() {
1503 return subclassPropertyTypeClosure.length;
1504 }
1505
1506 public String[] getSubclassPropertyColumnNames(int i) {
1507 return subclassPropertyColumnNameClosure[i];
1508 }
1509
1510 public boolean isDefinedOnSubclass(int i) {
1511 return propertyDefinedOnSubclass[i];
1512 }
1513
1514 protected String[][] getSubclassPropertyFormulaTemplateClosure() {
1515 return subclassPropertyFormulaTemplateClosure;
1516 }
1517
1518 protected Type[] getSubclassPropertyTypeClosure() {
1519 return subclassPropertyTypeClosure;
1520 }
1521
1522 protected String[][] getSubclassPropertyColumnNameClosure() {
1523 return subclassPropertyColumnNameClosure;
1524 }
1525
1526 protected String[] getSubclassPropertyNameClosure() {
1527 return subclassPropertyNameClosure;
1528 }
1529
1530 protected String[] getSubclassPropertySubclassNameClosure() {
1531 return subclassPropertySubclassNameClosure;
1532 }
1533
1534 protected String[] getSubclassColumnClosure() {
1535 return subclassColumnClosure;
1536 }
1537
1538 protected String[] getSubclassColumnAliasClosure() {
1539 return subclassColumnAliasClosure;
1540 }
1541
1542 protected String[] getSubclassFormulaClosure() {
1543 return subclassFormulaClosure;
1544 }
1545
1546 protected String[] getSubclassFormulaTemplateClosure() {
1547 return subclassFormulaTemplateClosure;
1548 }
1549
1550 protected String[] getSubclassFormulaAliasClosure() {
1551 return subclassFormulaAliasClosure;
1552 }
1553
1554 public String[] getSubclassPropertyColumnAliases(String propertyName, String suffix) {
1555 String rawAliases[] = ( String[] ) subclassPropertyAliases.get( propertyName );
1556
1557 if ( rawAliases == null ) {
1558 return null;
1559 }
1560
1561 String result[] = new String[rawAliases.length];
1562 for ( int i = 0; i < rawAliases.length; i++ ) {
1563 result[i] = new Alias( suffix ).toUnquotedAliasString( rawAliases[i] );
1564 }
1565 return result;
1566 }
1567
1568 public String[] getSubclassPropertyColumnNames(String propertyName) {
1569 //TODO: should we allow suffixes on these ?
1570 return ( String[] ) subclassPropertyColumnNames.get( propertyName );
1571 }
1572
1573
1574
1575 //This is really ugly, but necessary:
1576 /**
1577 * Must be called by subclasses, at the end of their constructors
1578 */
1579 protected void initSubclassPropertyAliasesMap(PersistentClass model) throws MappingException {
1580
1581 // ALIASES
1582 internalInitSubclassPropertyAliasesMap( null, model.getSubclassPropertyClosureIterator() );
1583
1584 // aliases for identifier ( alias.id ); skip if the entity defines a non-id property named 'id'
1585 if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
1586 subclassPropertyAliases.put( ENTITY_ID, getIdentifierAliases() );
1587 subclassPropertyColumnNames.put( ENTITY_ID, getIdentifierColumnNames() );
1588 }
1589
1590 // aliases named identifier ( alias.idname )
1591 if ( hasIdentifierProperty() ) {
1592 subclassPropertyAliases.put( getIdentifierPropertyName(), getIdentifierAliases() );
1593 subclassPropertyColumnNames.put( getIdentifierPropertyName(), getIdentifierColumnNames() );
1594 }
1595
1596 // aliases for composite-id's
1597 if ( getIdentifierType().isComponentType() ) {
1598 // Fetch embedded identifiers propertynames from the "virtual" identifier component
1599 AbstractComponentType componentId = ( AbstractComponentType ) getIdentifierType();
1600 String[] idPropertyNames = componentId.getPropertyNames();
1601 String[] idAliases = getIdentifierAliases();
1602 String[] idColumnNames = getIdentifierColumnNames();
1603
1604 for ( int i = 0; i < idPropertyNames.length; i++ ) {
1605 if ( entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
1606 subclassPropertyAliases.put(
1607 ENTITY_ID + "." + idPropertyNames[i],
1608 new String[] { idAliases[i] }
1609 );
1610 subclassPropertyColumnNames.put(
1611 ENTITY_ID + "." + getIdentifierPropertyName() + "." + idPropertyNames[i],
1612 new String[] { idColumnNames[i] }
1613 );
1614 }
1615 // if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) {
1616 if ( hasIdentifierProperty() ) {
1617 subclassPropertyAliases.put(
1618 getIdentifierPropertyName() + "." + idPropertyNames[i],
1619 new String[] { idAliases[i] }
1620 );
1621 subclassPropertyColumnNames.put(
1622 getIdentifierPropertyName() + "." + idPropertyNames[i],
1623 new String[] { idColumnNames[i] }
1624 );
1625 }
1626 else {
1627 // embedded composite ids ( alias.idname1, alias.idname2 )
1628 subclassPropertyAliases.put( idPropertyNames[i], new String[] { idAliases[i] } );
1629 subclassPropertyColumnNames.put( idPropertyNames[i], new String[] { idColumnNames[i] } );
1630 }
1631 }
1632 }
1633
1634 if ( entityMetamodel.isPolymorphic() ) {
1635 subclassPropertyAliases.put( ENTITY_CLASS, new String[] { getDiscriminatorAlias() } );
1636 subclassPropertyColumnNames.put( ENTITY_CLASS, new String[] { getDiscriminatorColumnName() } );
1637 }
1638
1639 }
1640
1641 private void internalInitSubclassPropertyAliasesMap(String path, Iterator propertyIterator) {
1642 while ( propertyIterator.hasNext() ) {
1643
1644 Property prop = ( Property ) propertyIterator.next();
1645 String propname = path == null ? prop.getName() : path + "." + prop.getName();
1646 if ( prop.isComposite() ) {
1647 Component component = ( Component ) prop.getValue();
1648 Iterator compProps = component.getPropertyIterator();
1649 internalInitSubclassPropertyAliasesMap( propname, compProps );
1650 }
1651 else {
1652 String[] aliases = new String[prop.getColumnSpan()];
1653 String[] cols = new String[prop.getColumnSpan()];
1654 Iterator colIter = prop.getColumnIterator();
1655 int l = 0;
1656 while ( colIter.hasNext() ) {
1657 Selectable thing = ( Selectable ) colIter.next();
1658 aliases[l] = thing.getAlias( getFactory().getDialect(), prop.getValue().getTable() );
1659 cols[l] = thing.getText( getFactory().getDialect() ); // TODO: skip formulas?
1660 l++;
1661 }
1662
1663 subclassPropertyAliases.put( propname, aliases );
1664 subclassPropertyColumnNames.put( propname, cols );
1665 }
1666 }
1667
1668 }
1669
1670 public Object loadByUniqueKey(String propertyName, Object uniqueKey, SessionImplementor session)
1671 throws HibernateException {
1672 return getAppropriateUniqueKeyLoader( propertyName, session.getEnabledFilters() )
1673 .loadByUniqueKey( session, uniqueKey );
1674 }
1675
1676 private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, Map enabledFilters) {
1677
1678 final boolean useStaticLoader = ( enabledFilters == null || enabledFilters.isEmpty() )
1679 && propertyName.indexOf('.')<0; //ugly little workaround for fact that createUniqueKeyLoaders() does not handle component properties
1680
1681 if ( useStaticLoader ) {
1682 return (EntityLoader) uniqueKeyLoaders.get( propertyName );
1683 }
1684 else {
1685 return createUniqueKeyLoader(
1686 propertyMapping.toType(propertyName),
1687 propertyMapping.toColumns(propertyName),
1688 enabledFilters
1689 );
1690 }
1691 }
1692
1693 public int getPropertyIndex(String propertyName) {
1694 return entityMetamodel.getPropertyIndex(propertyName);
1695 }
1696
1697 protected void createUniqueKeyLoaders() throws MappingException {
1698 Type[] propertyTypes = getPropertyTypes();
1699 String[] propertyNames = getPropertyNames();
1700 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
1701 if ( propertyUniqueness[i] ) {
1702 //don't need filters for the static loaders
1703 uniqueKeyLoaders.put(
1704 propertyNames[i],
1705 createUniqueKeyLoader(
1706 propertyTypes[i],
1707 getPropertyColumnNames( i ),
1708 CollectionHelper.EMPTY_MAP
1709 )
1710 );
1711 //TODO: create uk loaders for component properties
1712 }
1713 }
1714 }
1715
1716 private EntityLoader createUniqueKeyLoader(Type uniqueKeyType, String[] columns, Map enabledFilters) {
1717 if ( uniqueKeyType.isEntityType() ) {
1718 String className = ( ( EntityType ) uniqueKeyType ).getAssociatedEntityName();
1719 uniqueKeyType = getFactory().getEntityPersister( className ).getIdentifierType();
1720 }
1721
1722 return new EntityLoader( this, columns, uniqueKeyType, 1, LockMode.NONE, getFactory(), enabledFilters );
1723 }
1724
1725 protected String getSQLWhereString(String alias) {
1726 return StringHelper.replace( sqlWhereStringTemplate, Template.TEMPLATE, alias );
1727 }
1728
1729 protected boolean hasWhere() {
1730 return sqlWhereString != null;
1731 }
1732
1733 private void initOrdinaryPropertyPaths(Mapping mapping) throws MappingException {
1734 for ( int i = 0; i < getSubclassPropertyNameClosure().length; i++ ) {
1735 propertyMapping.initPropertyPaths( getSubclassPropertyNameClosure()[i],
1736 getSubclassPropertyTypeClosure()[i],
1737 getSubclassPropertyColumnNameClosure()[i],
1738 getSubclassPropertyFormulaTemplateClosure()[i],
1739 mapping );
1740 }
1741 }
1742
1743 private void initIdentifierPropertyPaths(Mapping mapping) throws MappingException {
1744 String idProp = getIdentifierPropertyName();
1745 if ( idProp != null ) {
1746 propertyMapping.initPropertyPaths( idProp, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
1747 }
1748 if ( entityMetamodel.getIdentifierProperty().isEmbedded() ) {
1749 propertyMapping.initPropertyPaths( null, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
1750 }
1751 if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
1752 propertyMapping.initPropertyPaths( ENTITY_ID, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
1753 }
1754 }
1755
1756 private void initDiscriminatorPropertyPath(Mapping mapping) throws MappingException {
1757 propertyMapping.initPropertyPaths( ENTITY_CLASS,
1758 getDiscriminatorType(),
1759 new String[]{getDiscriminatorColumnName()},
1760 new String[]{getDiscriminatorFormulaTemplate()},
1761 getFactory() );
1762 }
1763
1764 protected void initPropertyPaths(Mapping mapping) throws MappingException {
1765 initOrdinaryPropertyPaths(mapping);
1766 initOrdinaryPropertyPaths(mapping); //do two passes, for collection property-ref!
1767 initIdentifierPropertyPaths(mapping);
1768 if ( entityMetamodel.isPolymorphic() ) {
1769 initDiscriminatorPropertyPath( mapping );
1770 }
1771 }
1772
1773 protected UniqueEntityLoader createEntityLoader(LockMode lockMode, Map enabledFilters) throws MappingException {
1774 //TODO: disable batch loading if lockMode > READ?
1775 return BatchingEntityLoader.createBatchingEntityLoader( this, batchSize, lockMode, getFactory(), enabledFilters );
1776 }
1777
1778 protected UniqueEntityLoader createEntityLoader(LockMode lockMode) throws MappingException {
1779 return createEntityLoader( lockMode, CollectionHelper.EMPTY_MAP );
1780 }
1781
1782 protected boolean check(int rows, Serializable id, int tableNumber, Expectation expectation, PreparedStatement statement) throws HibernateException {
1783 try {
1784 expectation.verifyOutcome( rows, statement, -1 );
1785 }
1786 catch( StaleStateException e ) {
1787 if ( !isNullableTable( tableNumber ) ) {
1788 if ( getFactory().getStatistics().isStatisticsEnabled() ) {
1789 getFactory().getStatisticsImplementor()
1790 .optimisticFailure( getEntityName() );
1791 }
1792 throw new StaleObjectStateException( getEntityName(), id );
1793 }
1794 return false;
1795 }
1796 catch( TooManyRowsAffectedException e ) {
1797 throw new HibernateException(
1798 "Duplicate identifier in table for: " +
1799 MessageHelper.infoString( this, id, getFactory() )
1800 );
1801 }
1802 catch ( Throwable t ) {
1803 return false;
1804 }
1805 return true;
1806 }
1807
1808 protected String generateUpdateString(boolean[] includeProperty, int j, boolean useRowId) {
1809 return generateUpdateString( includeProperty, j, null, useRowId );
1810 }
1811
1812 /**
1813 * Generate the SQL that updates a row by id (and version)
1814 */
1815 protected String generateUpdateString(final boolean[] includeProperty,
1816 final int j,
1817 final Object[] oldFields,
1818 final boolean useRowId) {
1819
1820 Update update = new Update( getFactory().getDialect() ).setTableName( getTableName( j ) );
1821
1822 // select the correct row by either pk or rowid
1823 if ( useRowId ) {
1824 update.setPrimaryKeyColumnNames( new String[]{rowIdName} ); //TODO: eventually, rowIdName[j]
1825 }
1826 else {
1827 update.setPrimaryKeyColumnNames( getKeyColumns( j ) );
1828 }
1829
1830 boolean hasColumns = false;
1831 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
1832 if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
1833 // this is a property of the table, which we are updating
1834 update.addColumns( getPropertyColumnNames(i), propertyColumnUpdateable[i] );
1835 hasColumns = hasColumns || getPropertyColumnSpan( i ) > 0;
1836 }
1837 }
1838
1839 if ( j == 0 && isVersioned() && entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_VERSION ) {
1840 // this is the root (versioned) table, and we are using version-based
1841 // optimistic locking; if we are not updating the version, also don't
1842 // check it (unless this is a "generated" version column)!
1843 if ( checkVersion( includeProperty ) ) {
1844 update.setVersionColumnName( getVersionColumnName() );
1845 hasColumns = true;
1846 }
1847 }
1848 else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
1849 // we are using "all" or "dirty" property-based optimistic locking
1850
1851 boolean[] includeInWhere = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
1852 getPropertyUpdateability() : //optimistic-lock="all", include all updatable properties
1853 includeProperty; //optimistic-lock="dirty", include all properties we are updating this time
1854
1855 boolean[] versionability = getPropertyVersionability();
1856 Type[] types = getPropertyTypes();
1857 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
1858 boolean include = includeInWhere[i] &&
1859 isPropertyOfTable( i, j ) &&
1860 versionability[i];
1861 if ( include ) {
1862 // this property belongs to the table, and it is not specifically
1863 // excluded from optimistic locking by optimistic-lock="false"
1864 String[] propertyColumnNames = getPropertyColumnNames( i );
1865 boolean[] propertyNullness = types[i].toColumnNullness( oldFields[i], getFactory() );
1866 for ( int k=0; k<propertyNullness.length; k++ ) {
1867 if ( propertyNullness[k] ) {
1868 update.addWhereColumn( propertyColumnNames[k] );
1869 }
1870 else {
1871 update.addWhereColumn( propertyColumnNames[k], " is null" );
1872 }
1873 }
1874 }
1875 }
1876
1877 }
1878
1879 if ( getFactory().getSettings().isCommentsEnabled() ) {
1880 update.setComment( "update " + getEntityName() );
1881 }
1882
1883 return hasColumns ? update.toStatementString() : null;
1884 }
1885
1886 private boolean checkVersion(final boolean[] includeProperty) {
1887 return includeProperty[ getVersionProperty() ] ||
1888 entityMetamodel.getPropertyUpdateGenerationInclusions()[ getVersionProperty() ] != ValueInclusion.NONE;
1889 }
1890
1891 protected String generateInsertString(boolean[] includeProperty, int j) {
1892 return generateInsertString( false, includeProperty, j );
1893 }
1894
1895 protected String generateInsertString(boolean identityInsert, boolean[] includeProperty) {
1896 return generateInsertString( identityInsert, includeProperty, 0 );
1897 }
1898
1899 /**
1900 * Generate the SQL that inserts a row
1901 */
1902 protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) {
1903
1904 // todo : remove the identityInsert param and variations;
1905 // identity-insert strings are now generated from generateIdentityInsertString()
1906
1907 Insert insert = new Insert( getFactory().getDialect() )
1908 .setTableName( getTableName( j ) );
1909
1910 // add normal properties
1911 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
1912 if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
1913 // this property belongs on the table and is to be inserted
1914 insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i] );
1915 }
1916 }
1917
1918 // add the discriminator
1919 if ( j == 0 ) {
1920 addDiscriminatorToInsert( insert );
1921 }
1922
1923 // add the primary key
1924 if ( j == 0 && identityInsert ) {
1925 insert.addIdentityColumn( getKeyColumns( 0 )[0] );
1926 }
1927 else {
1928 insert.addColumns( getKeyColumns( j ) );
1929 }
1930
1931 if ( getFactory().getSettings().isCommentsEnabled() ) {
1932 insert.setComment( "insert " + getEntityName() );
1933 }
1934
1935 String result = insert.toStatementString();
1936
1937 // append the SQL to return the generated identifier
1938 if ( j == 0 && identityInsert && useInsertSelectIdentity() ) { //TODO: suck into Insert
1939 result = getFactory().getDialect().appendIdentitySelectToInsert( result );
1940 }
1941
1942 return result;
1943 }
1944
1945 /**
1946 * Used to generate an insery statement against the root table in the
1947 * case of identifier generation strategies where the insert statement
1948 * executions actually generates the identifier value.
1949 *
1950 * @param includeProperty indices of the properties to include in the
1951 * insert statement.
1952 * @return The insert SQL statement string
1953 */
1954 protected String generateIdentityInsertString(boolean[] includeProperty) {
1955 Insert insert = identityDelegate.prepareIdentifierGeneratingInsert();
1956 insert.setTableName( getTableName( 0 ) );
1957
1958 // add normal properties
1959 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
1960 if ( includeProperty[i] && isPropertyOfTable( i, 0 ) ) {
1961 // this property belongs on the table and is to be inserted
1962 insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i] );
1963 }
1964 }
1965
1966 // add the discriminator
1967 addDiscriminatorToInsert( insert );
1968
1969 // delegate already handles PK columns
1970
1971 if ( getFactory().getSettings().isCommentsEnabled() ) {
1972 insert.setComment( "insert " + getEntityName() );
1973 }
1974
1975 return insert.toStatementString();
1976 }
1977
1978 /**
1979 * Generate the SQL that deletes a row by id (and version)
1980 */
1981 protected String generateDeleteString(int j) {
1982 Delete delete = new Delete()
1983 .setTableName( getTableName( j ) )
1984 .setPrimaryKeyColumnNames( getKeyColumns( j ) );
1985 if ( j == 0 ) {
1986 delete.setVersionColumnName( getVersionColumnName() );
1987 }
1988 if ( getFactory().getSettings().isCommentsEnabled() ) {
1989 delete.setComment( "delete " + getEntityName() );
1990 }
1991 return delete.toStatementString();
1992 }
1993
1994 protected int dehydrate(
1995 Serializable id,
1996 Object[] fields,
1997 boolean[] includeProperty,
1998 boolean[][] includeColumns,
1999 int j,
2000 PreparedStatement st,
2001 SessionImplementor session) throws HibernateException, SQLException {
2002 return dehydrate( id, fields, null, includeProperty, includeColumns, j, st, session, 1 );
2003 }
2004
2005 /**
2006 * Marshall the fields of a persistent instance to a prepared statement
2007 */
2008 protected int dehydrate(
2009 final Serializable id,
2010 final Object[] fields,
2011 final Object rowId,
2012 final boolean[] includeProperty,
2013 final boolean[][] includeColumns,
2014 final int j,
2015 final PreparedStatement ps,
2016 final SessionImplementor session,
2017 int index) throws SQLException, HibernateException {
2018
2019 if ( log.isTraceEnabled() ) {
2020 log.trace( "Dehydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
2021 }
2022
2023 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
2024 if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
2025 getPropertyTypes()[i].nullSafeSet( ps, fields[i], index, includeColumns[i], session );
2026 //index += getPropertyColumnSpan( i );
2027 index += ArrayHelper.countTrue( includeColumns[i] ); //TODO: this is kinda slow...
2028 }
2029 }
2030
2031 if ( rowId != null ) {
2032 ps.setObject( index, rowId );
2033 index += 1;
2034 }
2035 else if ( id != null ) {
2036 getIdentifierType().nullSafeSet( ps, id, index, session );
2037 index += getIdentifierColumnSpan();
2038 }
2039
2040 return index;
2041
2042 }
2043
2044 /**
2045 * Unmarshall the fields of a persistent instance from a result set,
2046 * without resolving associations or collections. Question: should
2047 * this really be here, or should it be sent back to Loader?
2048 */
2049 public Object[] hydrate(
2050 final ResultSet rs,
2051 final Serializable id,
2052 final Object object,
2053 final Loadable rootLoadable,
2054 final String[][] suffixedPropertyColumns,
2055 final boolean allProperties,
2056 final SessionImplementor session) throws SQLException, HibernateException {
2057
2058 if ( log.isTraceEnabled() ) {
2059 log.trace( "Hydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
2060 }
2061
2062 final AbstractEntityPersister rootPersister = (AbstractEntityPersister) rootLoadable;
2063
2064 final boolean hasDeferred = rootPersister.hasSequentialSelect();
2065 PreparedStatement sequentialSelect = null;
2066 ResultSet sequentialResultSet = null;
2067 boolean sequentialSelectEmpty = false;
2068 try {
2069
2070 if ( hasDeferred ) {
2071 final String sql = rootPersister.getSequentialSelect( getEntityName() );
2072 if ( sql != null ) {
2073 //TODO: I am not so sure about the exception handling in this bit!
2074 sequentialSelect = session.getBatcher().prepareSelectStatement( sql );
2075 rootPersister.getIdentifierType().nullSafeSet( sequentialSelect, id, 1, session );
2076 sequentialResultSet = sequentialSelect.executeQuery();
2077 if ( !sequentialResultSet.next() ) {
2078 // TODO: Deal with the "optional" attribute in the <join> mapping;
2079 // this code assumes that optional defaults to "true" because it
2080 // doesn't actually seem to work in the fetch="join" code
2081 //
2082 // Note that actual proper handling of optional-ality here is actually
2083 // more involved than this patch assumes. Remember that we might have
2084 // multiple <join/> mappings associated with a single entity. Really
2085 // a couple of things need to happen to properly handle optional here:
2086 // 1) First and foremost, when handling multiple <join/>s, we really
2087 // should be using the entity root table as the driving table;
2088 // another option here would be to choose some non-optional joined
2089 // table to use as the driving table. In all likelihood, just using
2090 // the root table is much simplier
2091 // 2) Need to add the FK columns corresponding to each joined table
2092 // to the generated select list; these would then be used when
2093 // iterating the result set to determine whether all non-optional
2094 // data is present
2095 // My initial thoughts on the best way to deal with this would be
2096 // to introduce a new SequentialSelect abstraction that actually gets
2097 // generated in the persisters (ok, SingleTable...) and utilized here.
2098 // It would encapsulated all this required optional-ality checking...
2099 sequentialSelectEmpty = true;
2100 }
2101 }
2102 }
2103
2104 final String[] propNames = getPropertyNames();
2105 final Type[] types = getPropertyTypes();
2106 final Object[] values = new Object[types.length];
2107 final boolean[] laziness = getPropertyLaziness();
2108 final String[] propSubclassNames = getSubclassPropertySubclassNameClosure();
2109
2110 for ( int i = 0; i < types.length; i++ ) {
2111 if ( !propertySelectable[i] ) {
2112 values[i] = BackrefPropertyAccessor.UNKNOWN;
2113 }
2114 else if ( allProperties || !laziness[i] ) {
2115 //decide which ResultSet to get the property value from:
2116 final boolean propertyIsDeferred = hasDeferred &&
2117 rootPersister.isSubclassPropertyDeferred( propNames[i], propSubclassNames[i] );
2118 if ( propertyIsDeferred && sequentialSelectEmpty ) {
2119 values[i] = null;
2120 }
2121 else {
2122 final ResultSet propertyResultSet = propertyIsDeferred ? sequentialResultSet : rs;
2123 final String[] cols = propertyIsDeferred ? propertyColumnAliases[i] : suffixedPropertyColumns[i];
2124 values[i] = types[i].hydrate( propertyResultSet, cols, session, object );
2125 }
2126 }
2127 else {
2128 values[i] = LazyPropertyInitializer.UNFETCHED_PROPERTY;
2129 }
2130 }
2131
2132 if ( sequentialResultSet != null ) {
2133 sequentialResultSet.close();
2134 }
2135
2136 return values;
2137
2138 }
2139 finally {
2140 if ( sequentialSelect != null ) {
2141 session.getBatcher().closeStatement( sequentialSelect );
2142 }
2143 }
2144 }
2145
2146 protected boolean useInsertSelectIdentity() {
2147 return !useGetGeneratedKeys() && getFactory().getDialect().supportsInsertSelectIdentity();
2148 }
2149
2150 protected boolean useGetGeneratedKeys() {
2151 return getFactory().getSettings().isGetGeneratedKeysEnabled();
2152 }
2153
2154 protected String getSequentialSelect(String entityName) {
2155 throw new UnsupportedOperationException("no sequential selects");
2156 }
2157
2158 /**
2159 * Perform an SQL INSERT, and then retrieve a generated identifier.
2160 * <p/>
2161 * This form is used for PostInsertIdentifierGenerator-style ids (IDENTITY,
2162 * select, etc).
2163 */
2164 protected Serializable insert(
2165 final Object[] fields,
2166 final boolean[] notNull,
2167 String sql,
2168 final Object object,
2169 final SessionImplementor session) throws HibernateException {
2170
2171 if ( log.isTraceEnabled() ) {
2172 log.trace( "Inserting entity: " + getEntityName() + " (native id)" );
2173 if ( isVersioned() ) {
2174 log.trace( "Version: " + Versioning.getVersion( fields, this ) );
2175 }
2176 }
2177
2178 Binder binder = new Binder() {
2179 public void bindValues(PreparedStatement ps) throws SQLException {
2180 dehydrate( null, fields, notNull, propertyColumnInsertable, 0, ps, session );
2181 }
2182 public Object getEntity() {
2183 return object;
2184 }
2185 };
2186 return identityDelegate.performInsert( sql, session, binder );
2187 }
2188
2189 public String getIdentitySelectString() {
2190 //TODO: cache this in an instvar
2191 return getFactory().getDialect().getIdentitySelectString(
2192 getTableName(0),
2193 getKeyColumns(0)[0],
2194 getIdentifierType().sqlTypes( getFactory() )[0]
2195 );
2196 }
2197
2198 public String getSelectByUniqueKeyString(String propertyName) {
2199 return new SimpleSelect( getFactory().getDialect() )
2200 .setTableName( getTableName(0) )
2201 .addColumns( getKeyColumns(0) )
2202 .addCondition( getPropertyColumnNames(propertyName), "=?" )
2203 .toStatementString();
2204 }
2205
2206 /**
2207 * Perform an SQL INSERT.
2208 * <p/>
2209 * This for is used for all non-root tables as well as the root table
2210 * in cases where the identifier value is known before the insert occurs.
2211 */
2212 protected void insert(
2213 final Serializable id,
2214 final Object[] fields,
2215 final boolean[] notNull,
2216 final int j,
2217 final String sql,
2218 final Object object,
2219 final SessionImplementor session) throws HibernateException {
2220
2221 if ( isInverseTable( j ) ) {
2222 return;
2223 }
2224
2225 //note: it is conceptually possible that a UserType could map null to
2226 // a non-null value, so the following is arguable:
2227 if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
2228 return;
2229 }
2230
2231 if ( log.isTraceEnabled() ) {
2232 log.trace( "Inserting entity: " + MessageHelper.infoString( this, id, getFactory() ) );
2233 if ( j == 0 && isVersioned() ) {
2234 log.trace( "Version: " + Versioning.getVersion( fields, this ) );
2235 }
2236 }
2237
2238 Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] );
2239 boolean callable = isInsertCallable( j );
2240 // we can't batch joined inserts, *especially* not if it is an identity insert;
2241 // nor can we batch statements where the expectation is based on an output param
2242 final boolean useBatch = j == 0 && expectation.canBeBatched();
2243 try {
2244
2245 // Render the SQL query
2246 final PreparedStatement insert;
2247 if ( useBatch ) {
2248 if ( callable ) {
2249 insert = session.getBatcher().prepareBatchCallableStatement( sql );
2250 }
2251 else {
2252 insert = session.getBatcher().prepareBatchStatement( sql );
2253 }
2254 }
2255 else {
2256 if ( callable ) {
2257 insert = session.getBatcher().prepareCallableStatement( sql );
2258 }
2259 else {
2260 insert = session.getBatcher().prepareStatement( sql );
2261 }
2262 }
2263
2264 try {
2265 int index = 1;
2266 index += expectation.prepare( insert );
2267
2268 // Write the values of fields onto the prepared statement - we MUST use the state at the time the
2269 // insert was issued (cos of foreign key constraints). Not necessarily the object's current state
2270
2271 dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index );
2272
2273 if ( useBatch ) {
2274 // TODO : shouldnt inserts be Expectations.NONE?
2275 session.getBatcher().addToBatch( expectation );
2276 }
2277 else {
2278 expectation.verifyOutcome( insert.executeUpdate(), insert, -1 );
2279 }
2280
2281 }
2282 catch ( SQLException sqle ) {
2283 if ( useBatch ) {
2284 session.getBatcher().abortBatch( sqle );
2285 }
2286 throw sqle;
2287 }
2288 finally {
2289 if ( !useBatch ) {
2290 session.getBatcher().closeStatement( insert );
2291 }
2292 }
2293 }
2294 catch ( SQLException sqle ) {
2295 throw JDBCExceptionHelper.convert(
2296 getFactory().getSQLExceptionConverter(),
2297 sqle,
2298 "could not insert: " + MessageHelper.infoString( this ),
2299 sql
2300 );
2301 }
2302
2303 }
2304
2305 /**
2306 * Perform an SQL UPDATE or SQL INSERT
2307 */
2308 protected void updateOrInsert(
2309 final Serializable id,
2310 final Object[] fields,
2311 final Object[] oldFields,
2312 final Object rowId,
2313 final boolean[] includeProperty,
2314 final int j,
2315 final Object oldVersion,
2316 final Object object,
2317 final String sql,
2318 final SessionImplementor session) throws HibernateException {
2319
2320 if ( !isInverseTable( j ) ) {
2321
2322 final boolean isRowToUpdate;
2323 if ( isNullableTable( j ) && oldFields != null && isAllNull( oldFields, j ) ) {
2324 //don't bother trying to update, we know there is no row there yet
2325 isRowToUpdate = false;
2326 }
2327 else if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
2328 //if all fields are null, we might need to delete existing row
2329 isRowToUpdate = true;
2330 delete( id, oldVersion, j, object, getSQLDeleteStrings()[j], session, null );
2331 }
2332 else {
2333 //there is probably a row there, so try to update
2334 //if no rows were updated, we will find out
2335 isRowToUpdate = update( id, fields, oldFields, rowId, includeProperty, j, oldVersion, object, sql, session );
2336 }
2337
2338 if ( !isRowToUpdate && !isAllNull( fields, j ) ) {
2339 // assume that the row was not there since it previously had only null
2340 // values, so do an INSERT instead
2341 //TODO: does not respect dynamic-insert
2342 insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
2343 }
2344
2345 }
2346
2347 }
2348
2349 protected boolean update(
2350 final Serializable id,
2351 final Object[] fields,
2352 final Object[] oldFields,
2353 final Object rowId,
2354 final boolean[] includeProperty,
2355 final int j,
2356 final Object oldVersion,
2357 final Object object,
2358 final String sql,
2359 final SessionImplementor session) throws HibernateException {
2360
2361 final boolean useVersion = j == 0 && isVersioned();
2362 final Expectation expectation = Expectations.appropriateExpectation( updateResultCheckStyles[j] );
2363 final boolean callable = isUpdateCallable( j );
2364 final boolean useBatch = j == 0 && expectation.canBeBatched() && isBatchable(); //note: updates to joined tables can't be batched...
2365
2366 if ( log.isTraceEnabled() ) {
2367 log.trace( "Updating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
2368 if ( useVersion ) {
2369 log.trace( "Existing version: " + oldVersion + " -> New version: " + fields[getVersionProperty()] );
2370 }
2371 }
2372
2373 try {
2374
2375 int index = 1; // starting index
2376 final PreparedStatement update;
2377 if ( useBatch ) {
2378 if ( callable ) {
2379 update = session.getBatcher().prepareBatchCallableStatement( sql );
2380 }
2381 else {
2382 update = session.getBatcher().prepareBatchStatement( sql );
2383 }
2384 }
2385 else {
2386 if ( callable ) {
2387 update = session.getBatcher().prepareCallableStatement( sql );
2388 }
2389 else {
2390 update = session.getBatcher().prepareStatement( sql );
2391 }
2392 }
2393
2394 try {
2395
2396 index+= expectation.prepare( update );
2397
2398 //Now write the values of fields onto the prepared statement
2399 index = dehydrate( id, fields, rowId, includeProperty, propertyColumnUpdateable, j, update, session, index );
2400
2401 // Write any appropriate versioning conditional parameters
2402 if ( useVersion && Versioning.OPTIMISTIC_LOCK_VERSION == entityMetamodel.getOptimisticLockMode() ) {
2403 if ( checkVersion( includeProperty ) ) {
2404 getVersionType().nullSafeSet( update, oldVersion, index, session );
2405 }
2406 }
2407 else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
2408 boolean[] versionability = getPropertyVersionability(); //TODO: is this really necessary????
2409 boolean[] includeOldField = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
2410 getPropertyUpdateability() : includeProperty;
2411 Type[] types = getPropertyTypes();
2412 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
2413 boolean include = includeOldField[i] &&
2414 isPropertyOfTable( i, j ) &&
2415 versionability[i]; //TODO: is this really necessary????
2416 if ( include ) {
2417 boolean[] settable = types[i].toColumnNullness( oldFields[i], getFactory() );
2418 types[i].nullSafeSet(
2419 update,
2420 oldFields[i],
2421 index,
2422 settable,
2423 session
2424 );
2425 index += ArrayHelper.countTrue(settable);
2426 }
2427 }
2428 }
2429
2430 if ( useBatch ) {
2431 session.getBatcher().addToBatch( expectation );
2432 return true;
2433 }
2434 else {
2435 return check( update.executeUpdate(), id, j, expectation, update );
2436 }
2437
2438 }
2439 catch ( SQLException sqle ) {
2440 if ( useBatch ) {
2441 session.getBatcher().abortBatch( sqle );
2442 }
2443 throw sqle;
2444 }
2445 finally {
2446 if ( !useBatch ) {
2447 session.getBatcher().closeStatement( update );
2448 }
2449 }
2450
2451 }
2452 catch ( SQLException sqle ) {
2453 throw JDBCExceptionHelper.convert(
2454 getFactory().getSQLExceptionConverter(),
2455 sqle,
2456 "could not update: " + MessageHelper.infoString( this, id, getFactory() ),
2457 sql
2458 );
2459 }
2460 }
2461
2462 /**
2463 * Perform an SQL DELETE
2464 */
2465 protected void delete(
2466 final Serializable id,
2467 final Object version,
2468 final int j,
2469 final Object object,
2470 final String sql,
2471 final SessionImplementor session,
2472 final Object[] loadedState) throws HibernateException {
2473
2474 if ( isInverseTable( j ) ) {
2475 return;
2476 }
2477
2478 final boolean useVersion = j == 0 && isVersioned();
2479 final boolean callable = isDeleteCallable( j );
2480 final Expectation expectation = Expectations.appropriateExpectation( deleteResultCheckStyles[j] );
2481 final boolean useBatch = j == 0 && isBatchable() && expectation.canBeBatched();
2482
2483 if ( log.isTraceEnabled() ) {
2484 log.trace( "Deleting entity: " + MessageHelper.infoString( this, id, getFactory() ) );
2485 if ( useVersion ) {
2486 log.trace( "Version: " + version );
2487 }
2488 }
2489
2490 if ( isTableCascadeDeleteEnabled( j ) ) {
2491 if ( log.isTraceEnabled() ) {
2492 log.trace( "delete handled by foreign key constraint: " + getTableName( j ) );
2493 }
2494 return; //EARLY EXIT!
2495 }
2496
2497 try {
2498
2499 //Render the SQL query
2500 PreparedStatement delete;
2501 int index = 1;
2502 if ( useBatch ) {
2503 if ( callable ) {
2504 delete = session.getBatcher().prepareBatchCallableStatement( sql );
2505 }
2506 else {
2507 delete = session.getBatcher().prepareBatchStatement( sql );
2508 }
2509 }
2510 else {
2511 if ( callable ) {
2512 delete = session.getBatcher().prepareCallableStatement( sql );
2513 }
2514 else {
2515 delete = session.getBatcher().prepareStatement( sql );
2516 }
2517 }
2518
2519 try {
2520
2521 index += expectation.prepare( delete );
2522
2523 // Do the key. The key is immutable so we can use the _current_ object state - not necessarily
2524 // the state at the time the delete was issued
2525 getIdentifierType().nullSafeSet( delete, id, index, session );
2526 index += getIdentifierColumnSpan();
2527
2528 // We should use the _current_ object state (ie. after any updates that occurred during flush)
2529
2530 if ( useVersion ) {
2531 getVersionType().nullSafeSet( delete, version, index, session );
2532 }
2533 else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && loadedState != null ) {
2534 boolean[] versionability = getPropertyVersionability();
2535 Type[] types = getPropertyTypes();
2536 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
2537 if ( isPropertyOfTable( i, j ) && versionability[i] ) {
2538 // this property belongs to the table and it is not specifically
2539 // excluded from optimistic locking by optimistic-lock="false"
2540 boolean[] settable = types[i].toColumnNullness( loadedState[i], getFactory() );
2541 types[i].nullSafeSet( delete, loadedState[i], index, settable, session );
2542 index += ArrayHelper.countTrue( settable );
2543 }
2544 }
2545 }
2546
2547 if ( useBatch ) {
2548 session.getBatcher().addToBatch( expectation );
2549 }
2550 else {
2551 check( delete.executeUpdate(), id, j, expectation, delete );
2552 }
2553
2554 }
2555 catch ( SQLException sqle ) {
2556 if ( useBatch ) {
2557 session.getBatcher().abortBatch( sqle );
2558 }
2559 throw sqle;
2560 }
2561 finally {
2562 if ( !useBatch ) {
2563 session.getBatcher().closeStatement( delete );
2564 }
2565 }
2566
2567 }
2568 catch ( SQLException sqle ) {
2569 throw JDBCExceptionHelper.convert(
2570 getFactory().getSQLExceptionConverter(),
2571 sqle,
2572 "could not delete: " +
2573 MessageHelper.infoString( this, id, getFactory() ),
2574 sql
2575 );
2576
2577 }
2578
2579 }
2580
2581 private String[] getUpdateStrings(boolean byRowId, boolean lazy) {
2582 if ( byRowId ) {
2583 return lazy ? getSQLLazyUpdateByRowIdStrings() : getSQLUpdateByRowIdStrings();
2584 }
2585 else {
2586 return lazy ? getSQLLazyUpdateStrings() : getSQLUpdateStrings();
2587 }
2588 }
2589
2590 /**
2591 * Update an object
2592 */
2593 public void update(
2594 final Serializable id,
2595 final Object[] fields,
2596 final int[] dirtyFields,
2597 final boolean hasDirtyCollection,
2598 final Object[] oldFields,
2599 final Object oldVersion,
2600 final Object object,
2601 final Object rowId,
2602 final SessionImplementor session) throws HibernateException {
2603
2604 //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update
2605 // oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields)
2606
2607 final boolean[] tableUpdateNeeded = getTableUpdateNeeded( dirtyFields, hasDirtyCollection );
2608 final int span = getTableSpan();
2609
2610 final boolean[] propsToUpdate;
2611 final String[] updateStrings;
2612 if ( entityMetamodel.isDynamicUpdate() && dirtyFields != null ) {
2613 // For the case of dynamic-update="true", we need to generate the UPDATE SQL
2614 propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
2615 // don't need to check laziness (dirty checking algorithm handles that)
2616 updateStrings = new String[span];
2617 for ( int j = 0; j < span; j++ ) {
2618 updateStrings[j] = tableUpdateNeeded[j] ?
2619 generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) :
2620 null;
2621 }
2622 }
2623 else {
2624 // For the case of dynamic-update="false", or no snapshot, we use the static SQL
2625 updateStrings = getUpdateStrings(
2626 rowId != null,
2627 hasUninitializedLazyProperties( object, session.getEntityMode() )
2628 );
2629 propsToUpdate = getPropertyUpdateability( object, session.getEntityMode() );
2630 }
2631
2632 for ( int j = 0; j < span; j++ ) {
2633 // Now update only the tables with dirty properties (and the table with the version number)
2634 if ( tableUpdateNeeded[j] ) {
2635 updateOrInsert(
2636 id,
2637 fields,
2638 oldFields,
2639 j == 0 ? rowId : null,
2640 propsToUpdate,
2641 j,
2642 oldVersion,
2643 object,
2644 updateStrings[j],
2645 session
2646 );
2647 }
2648 }
2649 }
2650
2651 public Serializable insert(Object[] fields, Object object, SessionImplementor session)
2652 throws HibernateException {
2653
2654 final int span = getTableSpan();
2655 final Serializable id;
2656 if ( entityMetamodel.isDynamicInsert() ) {
2657 // For the case of dynamic-insert="true", we need to generate the INSERT SQL
2658 boolean[] notNull = getPropertiesToInsert( fields );
2659 id = insert( fields, notNull, generateInsertString( true, notNull ), object, session );
2660 for ( int j = 1; j < span; j++ ) {
2661 insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
2662 }
2663 }
2664 else {
2665 // For the case of dynamic-insert="false", use the static SQL
2666 id = insert( fields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
2667 for ( int j = 1; j < span; j++ ) {
2668 insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
2669 }
2670 }
2671 return id;
2672 }
2673
2674 public void insert(Serializable id, Object[] fields, Object object, SessionImplementor session)
2675 throws HibernateException {
2676
2677 final int span = getTableSpan();
2678 if ( entityMetamodel.isDynamicInsert() ) {
2679 // For the case of dynamic-insert="true", we need to generate the INSERT SQL
2680 boolean[] notNull = getPropertiesToInsert( fields );
2681 for ( int j = 0; j < span; j++ ) {
2682 insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
2683 }
2684 }
2685 else {
2686 // For the case of dynamic-insert="false", use the static SQL
2687 for ( int j = 0; j < span; j++ ) {
2688 insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
2689 }
2690 }
2691 }
2692
2693 /**
2694 * Delete an object
2695 */
2696 public void delete(Serializable id, Object version, Object object, SessionImplementor session)
2697 throws HibernateException {
2698 final int span = getTableSpan();
2699 boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned() && entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION;
2700 Object[] loadedState = null;
2701 if ( isImpliedOptimisticLocking ) {
2702 // need to treat this as if it where optimistic-lock="all" (dirty does *not* make sense);
2703 // first we need to locate the "loaded" state
2704 //
2705 // Note, it potentially could be a proxy, so perform the location the safe way...
2706 EntityKey key = new EntityKey( id, this, session.getEntityMode() );
2707 Object entity = session.getPersistenceContext().getEntity( key );
2708 if ( entity != null ) {
2709 EntityEntry entry = session.getPersistenceContext().getEntry( entity );
2710 loadedState = entry.getLoadedState();
2711 }
2712 }
2713
2714 final String[] deleteStrings;
2715 if ( isImpliedOptimisticLocking && loadedState != null ) {
2716 // we need to utilize dynamic delete statements
2717 deleteStrings = generateSQLDeletStrings( loadedState );
2718 }
2719 else {
2720 // otherwise, utilize the static delete statements
2721 deleteStrings = getSQLDeleteStrings();
2722 }
2723
2724 for ( int j = span - 1; j >= 0; j-- ) {
2725 delete( id, version, j, object, deleteStrings[j], session, loadedState );
2726 }
2727
2728 }
2729
2730 private String[] generateSQLDeletStrings(Object[] loadedState) {
2731 int span = getTableSpan();
2732 String[] deleteStrings = new String[span];
2733 for ( int j = span - 1; j >= 0; j-- ) {
2734 Delete delete = new Delete()
2735 .setTableName( getTableName( j ) )
2736 .setPrimaryKeyColumnNames( getKeyColumns( j ) );
2737 if ( getFactory().getSettings().isCommentsEnabled() ) {
2738 delete.setComment( "delete " + getEntityName() + " [" + j + "]" );
2739 }
2740
2741 boolean[] versionability = getPropertyVersionability();
2742 Type[] types = getPropertyTypes();
2743 for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
2744 if ( isPropertyOfTable( i, j ) && versionability[i] ) {
2745 // this property belongs to the table and it is not specifically
2746 // excluded from optimistic locking by optimistic-lock="false"
2747 String[] propertyColumnNames = getPropertyColumnNames( i );
2748 boolean[] propertyNullness = types[i].toColumnNullness( loadedState[i], getFactory() );
2749 for ( int k = 0; k < propertyNullness.length; k++ ) {
2750 if ( propertyNullness[k] ) {
2751 delete.addWhereFragment( propertyColumnNames[k] + " = ?" );
2752 }
2753 else {
2754 delete.addWhereFragment( propertyColumnNames[k] + " is null" );
2755 }
2756 }
2757 }
2758 }
2759 deleteStrings[j] = delete.toStatementString();
2760 }
2761 return deleteStrings;
2762 }
2763
2764 protected void logStaticSQL() {
2765 if ( log.isDebugEnabled() ) {
2766 log.debug( "Static SQL for entity: " + getEntityName() );
2767 if ( sqlLazySelectString != null ) {
2768 log.debug( " Lazy select: " + sqlLazySelectString );
2769 }
2770 if ( sqlVersionSelectString != null ) {
2771 log.debug( " Version select: " + sqlVersionSelectString );
2772 }
2773 if ( sqlSnapshotSelectString != null ) {
2774 log.debug( " Snapshot select: " + sqlSnapshotSelectString );
2775 }
2776 for ( int j = 0; j < getTableSpan(); j++ ) {
2777 log.debug( " Insert " + j + ": " + getSQLInsertStrings()[j] );
2778 log.debug( " Update " + j + ": " + getSQLUpdateStrings()[j] );
2779 log.debug( " Delete " + j + ": " + getSQLDeleteStrings()[j] );
2780
2781 }
2782 if ( sqlIdentityInsertString != null ) {
2783 log.debug( " Identity insert: " + sqlIdentityInsertString );
2784 }
2785 if ( sqlUpdateByRowIdString != null ) {
2786 log.debug( " Update by row id (all fields): " + sqlUpdateByRowIdString );
2787 }
2788 if ( sqlLazyUpdateByRowIdString != null ) {
2789 log.debug( " Update by row id (non-lazy fields): " + sqlLazyUpdateByRowIdString );
2790 }
2791 if ( sqlInsertGeneratedValuesSelectString != null ) {
2792 log.debug( "Insert-generated property select: " + sqlInsertGeneratedValuesSelectString );
2793 }
2794 if ( sqlUpdateGeneratedValuesSelectString != null ) {
2795 log.debug( "Update-generated property select: " + sqlUpdateGeneratedValuesSelectString );
2796 }
2797 }
2798 }
2799
2800 public String filterFragment(String alias, Map enabledFilters) throws MappingException {
2801 final StringBuffer sessionFilterFragment = new StringBuffer();
2802 filterHelper.render( sessionFilterFragment, generateFilterConditionAlias( alias ), enabledFilters );
2803
2804 return sessionFilterFragment.append( filterFragment( alias ) ).toString();
2805 }
2806
2807 public String generateFilterConditionAlias(String rootAlias) {
2808 return rootAlias;
2809 }
2810
2811 public String oneToManyFilterFragment(String alias) throws MappingException {
2812 return "";
2813 }
2814
2815 public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
2816 return getSubclassTableSpan() == 1 ?
2817 "" : //just a performance opt!
2818 createJoin( alias, innerJoin, includeSubclasses ).toFromFragmentString();
2819 }
2820
2821 public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
2822 return getSubclassTableSpan() == 1 ?
2823 "" : //just a performance opt!
2824 createJoin( alias, innerJoin, includeSubclasses ).toWhereFragmentString();
2825 }
2826
2827 protected boolean isSubclassTableLazy(int j) {
2828 return false;
2829 }
2830
2831 protected JoinFragment createJoin(String name, boolean innerJoin, boolean includeSubclasses) {
2832 final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() ); //all joins join to the pk of the driving table
2833 final JoinFragment join = getFactory().getDialect().createOuterJoinFragment();
2834 final int tableSpan = getSubclassTableSpan();
2835 for ( int j = 1; j < tableSpan; j++ ) { //notice that we skip the first table; it is the driving table!
2836 final boolean joinIsIncluded = isClassOrSuperclassTable( j ) ||
2837 ( includeSubclasses && !isSubclassTableSequentialSelect( j ) && !isSubclassTableLazy( j ) );
2838 if ( joinIsIncluded ) {
2839 join.addJoin( getSubclassTableName( j ),
2840 generateTableAlias( name, j ),
2841 idCols,
2842 getSubclassTableKeyColumns( j ),
2843 innerJoin && isClassOrSuperclassTable( j ) && !isInverseTable( j ) && !isNullableTable( j ) ?
2844 JoinFragment.INNER_JOIN : //we can inner join to superclass tables (the row MUST be there)
2845 JoinFragment.LEFT_OUTER_JOIN //we can never inner join to subclass tables
2846 );
2847 }
2848 }
2849 return join;
2850 }
2851
2852 protected JoinFragment createJoin(int[] tableNumbers, String drivingAlias) {
2853 final String[] keyCols = StringHelper.qualify( drivingAlias, getSubclassTableKeyColumns( tableNumbers[0] ) );
2854 final JoinFragment jf = getFactory().getDialect().createOuterJoinFragment();
2855 for ( int i = 1; i < tableNumbers.length; i++ ) { //skip the driving table
2856 final int j = tableNumbers[i];
2857 jf.addJoin( getSubclassTableName( j ),
2858 generateTableAlias( getRootAlias(), j ),
2859 keyCols,
2860 getSubclassTableKeyColumns( j ),
2861 isInverseSubclassTable( j ) || isNullableSubclassTable( j ) ?
2862 JoinFragment.LEFT_OUTER_JOIN :
2863 JoinFragment.INNER_JOIN );
2864 }
2865 return jf;
2866 }
2867
2868 protected SelectFragment createSelect(final int[] subclassColumnNumbers,
2869 final int[] subclassFormulaNumbers) {
2870
2871 SelectFragment selectFragment = new SelectFragment();
2872
2873 int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
2874 String[] columnAliases = getSubclassColumnAliasClosure();
2875 String[] columns = getSubclassColumnClosure();
2876 for ( int i = 0; i < subclassColumnNumbers.length; i++ ) {
2877 if ( subclassColumnSelectableClosure[i] ) {
2878 int columnNumber = subclassColumnNumbers[i];
2879 final String subalias = generateTableAlias( getRootAlias(), columnTableNumbers[columnNumber] );
2880 selectFragment.addColumn( subalias, columns[columnNumber], columnAliases[columnNumber] );
2881 }
2882 }
2883
2884 int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
2885 String[] formulaTemplates = getSubclassFormulaTemplateClosure();
2886 String[] formulaAliases = getSubclassFormulaAliasClosure();
2887 for ( int i = 0; i < subclassFormulaNumbers.length; i++ ) {
2888 int formulaNumber = subclassFormulaNumbers[i];
2889 final String subalias = generateTableAlias( getRootAlias(), formulaTableNumbers[formulaNumber] );
2890 selectFragment.addFormula( subalias, formulaTemplates[formulaNumber], formulaAliases[formulaNumber] );
2891 }
2892
2893 return selectFragment;
2894 }
2895
2896 protected String createFrom(int tableNumber, String alias) {
2897 return getSubclassTableName( tableNumber ) + ' ' + alias;
2898 }
2899
2900 protected String createWhereByKey(int tableNumber, String alias) {
2901 //TODO: move to .sql package, and refactor with similar things!
2902 return StringHelper.join( "=? and ",
2903 StringHelper.qualify( alias, getSubclassTableKeyColumns( tableNumber ) ) ) + "=?";
2904 }
2905
2906 protected String renderSelect(
2907 final int[] tableNumbers,
2908 final int[] columnNumbers,
2909 final int[] formulaNumbers) {
2910
2911 Arrays.sort( tableNumbers ); //get 'em in the right order (not that it really matters)
2912
2913 //render the where and from parts
2914 int drivingTable = tableNumbers[0];
2915 final String drivingAlias = generateTableAlias( getRootAlias(), drivingTable ); //we *could* regerate this inside each called method!
2916 final String where = createWhereByKey( drivingTable, drivingAlias );
2917 final String from = createFrom( drivingTable, drivingAlias );
2918
2919 //now render the joins
2920 JoinFragment jf = createJoin( tableNumbers, drivingAlias );
2921
2922 //now render the select clause
2923 SelectFragment selectFragment = createSelect( columnNumbers, formulaNumbers );
2924
2925 //now tie it all together
2926 Select select = new Select( getFactory().getDialect() );
2927 select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) );
2928 select.setFromClause( from );
2929 select.setWhereClause( where );
2930 select.setOuterJoins( jf.toFromFragmentString(), jf.toWhereFragmentString() );
2931 if ( getFactory().getSettings().isCommentsEnabled() ) {
2932 select.setComment( "sequential select " + getEntityName() );
2933 }
2934 return select.toStatementString();
2935 }
2936
2937 private String getRootAlias() {
2938 return StringHelper.generateAlias( getEntityName() );
2939 }
2940
2941 protected void postConstruct(Mapping mapping) throws MappingException {
2942 initPropertyPaths(mapping);
2943
2944 //insert/update/delete SQL
2945 final int joinSpan = getTableSpan();
2946 sqlDeleteStrings = new String[joinSpan];
2947 sqlInsertStrings = new String[joinSpan];
2948 sqlUpdateStrings = new String[joinSpan];
2949 sqlLazyUpdateStrings = new String[joinSpan];
2950
2951 sqlUpdateByRowIdString = rowIdName == null ?
2952 null :
2953 generateUpdateString( getPropertyUpdateability(), 0, true );
2954 sqlLazyUpdateByRowIdString = rowIdName == null ?
2955 null :
2956 generateUpdateString( getNonLazyPropertyUpdateability(), 0, true );
2957
2958 for ( int j = 0; j < joinSpan; j++ ) {
2959 sqlInsertStrings[j] = customSQLInsert[j] == null ?
2960 generateInsertString( getPropertyInsertability(), j ) :
2961 customSQLInsert[j];
2962 sqlUpdateStrings[j] = customSQLUpdate[j] == null ?
2963 generateUpdateString( getPropertyUpdateability(), j, false ) :
2964 customSQLUpdate[j];
2965 sqlLazyUpdateStrings[j] = customSQLUpdate[j] == null ?
2966 generateUpdateString( getNonLazyPropertyUpdateability(), j, false ) :
2967 customSQLUpdate[j];
2968 sqlDeleteStrings[j] = customSQLDelete[j] == null ?
2969 generateDeleteString( j ) :
2970 customSQLDelete[j];
2971 }
2972
2973 tableHasColumns = new boolean[joinSpan];
2974 for ( int j = 0; j < joinSpan; j++ ) {
2975 tableHasColumns[j] = sqlUpdateStrings[j] != null;
2976 }
2977
2978 //select SQL
2979 sqlSnapshotSelectString = generateSnapshotSelectString();
2980 sqlLazySelectString = generateLazySelectString();
2981 sqlVersionSelectString = generateSelectVersionString();
2982 if ( hasInsertGeneratedProperties() ) {
2983 sqlInsertGeneratedValuesSelectString = generateInsertGeneratedValuesSelectString();
2984 }
2985 if ( hasUpdateGeneratedProperties() ) {
2986 sqlUpdateGeneratedValuesSelectString = generateUpdateGeneratedValuesSelectString();
2987 }
2988 if ( isIdentifierAssignedByInsert() ) {
2989 identityDelegate = ( ( PostInsertIdentifierGenerator ) getIdentifierGenerator() )
2990 .getInsertGeneratedIdentifierDelegate( this, getFactory().getDialect(), useGetGeneratedKeys() );
2991 sqlIdentityInsertString = customSQLInsert[0] == null
2992 ? generateIdentityInsertString( getPropertyInsertability() )
2993 : customSQLInsert[0];
2994 }
2995 else {
2996 sqlIdentityInsertString = null;
2997 }
2998
2999 logStaticSQL();
3000
3001 }
3002
3003 public void postInstantiate() throws MappingException {
3004
3005 createLoaders();
3006 createUniqueKeyLoaders();
3007 createQueryLoader();
3008
3009 }
3010
3011 private void createLoaders() {
3012 loaders.put( LockMode.NONE, createEntityLoader( LockMode.NONE ) );
3013
3014 UniqueEntityLoader readLoader = createEntityLoader( LockMode.READ );
3015 loaders.put( LockMode.READ, readLoader );
3016
3017 //TODO: inexact, what we really need to know is: are any outer joins used?
3018 boolean disableForUpdate = getSubclassTableSpan() > 1 &&
3019 hasSubclasses() &&
3020 !getFactory().getDialect().supportsOuterJoinForUpdate();
3021
3022 loaders.put(
3023 LockMode.UPGRADE,
3024 disableForUpdate ?
3025 readLoader :
3026 createEntityLoader( LockMode.UPGRADE )
3027 );
3028 loaders.put(
3029 LockMode.UPGRADE_NOWAIT,
3030 disableForUpdate ?
3031 readLoader :
3032 createEntityLoader( LockMode.UPGRADE_NOWAIT )
3033 );
3034 loaders.put(
3035 LockMode.FORCE,
3036 disableForUpdate ?
3037 readLoader :
3038 createEntityLoader( LockMode.FORCE )
3039 );
3040
3041 loaders.put(
3042 "merge",
3043 new CascadeEntityLoader( this, CascadingAction.MERGE, getFactory() )
3044 );
3045 loaders.put(
3046 "refresh",
3047 new CascadeEntityLoader( this, CascadingAction.REFRESH, getFactory() )
3048 );
3049 }
3050
3051 protected void createQueryLoader() {
3052 if ( loaderName != null ) {
3053 queryLoader = new NamedQueryLoader( loaderName, this );
3054 }
3055 }
3056
3057 /**
3058 * Load an instance using either the <tt>forUpdateLoader</tt> or the outer joining <tt>loader</tt>,
3059 * depending upon the value of the <tt>lock</tt> parameter
3060 */
3061 public Object load(Serializable id, Object optionalObject, LockMode lockMode, SessionImplementor session)
3062 throws HibernateException {
3063
3064 if ( log.isTraceEnabled() ) {
3065 log.trace(
3066 "Fetching entity: " +
3067 MessageHelper.infoString( this, id, getFactory() )
3068 );
3069 }
3070
3071 final UniqueEntityLoader loader = getAppropriateLoader( lockMode, session );
3072 return loader.load( id, optionalObject, session );
3073 }
3074
3075 private UniqueEntityLoader getAppropriateLoader(LockMode lockMode, SessionImplementor session) {
3076 final Map enabledFilters = session.getEnabledFilters();
3077 if ( queryLoader != null ) {
3078 return queryLoader;
3079 }
3080 else if ( enabledFilters == null || enabledFilters.isEmpty() ) {
3081 if ( session.getFetchProfile()!=null && LockMode.UPGRADE.greaterThan(lockMode) ) {
3082 return (UniqueEntityLoader) loaders.get( session.getFetchProfile() );
3083 }
3084 else {
3085 return (UniqueEntityLoader) loaders.get( lockMode );
3086 }
3087 }
3088 else {
3089 return createEntityLoader( lockMode, enabledFilters );
3090 }
3091 }
3092
3093 private boolean isAllNull(Object[] array, int tableNumber) {
3094 for ( int i = 0; i < array.length; i++ ) {
3095 if ( isPropertyOfTable( i, tableNumber ) && array[i] != null ) {
3096 return false;
3097 }
3098 }
3099 return true;
3100 }
3101
3102 public boolean isSubclassPropertyNullable(int i) {
3103 return subclassPropertyNullabilityClosure[i];
3104 }
3105
3106 /**
3107 * Transform the array of property indexes to an array of booleans,
3108 * true when the property is dirty
3109 */
3110 protected final boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection) {
3111 final boolean[] propsToUpdate = new boolean[ entityMetamodel.getPropertySpan() ];
3112 final boolean[] updateability = getPropertyUpdateability(); //no need to check laziness, dirty checking handles that
3113 for ( int j = 0; j < dirtyProperties.length; j++ ) {
3114 int property = dirtyProperties[j];
3115 if ( updateability[property] ) {
3116 propsToUpdate[property] = true;
3117 }
3118 }
3119 if ( isVersioned() ) {
3120 propsToUpdate[ getVersionProperty() ] =
3121 Versioning.isVersionIncrementRequired( dirtyProperties, hasDirtyCollection, getPropertyVersionability() );
3122 }
3123 return propsToUpdate;
3124 }
3125
3126 /**
3127 * Transform the array of property indexes to an array of booleans,
3128 * true when the property is insertable and non-null
3129 */
3130 protected boolean[] getPropertiesToInsert(Object[] fields) {
3131 boolean[] notNull = new boolean[fields.length];
3132 boolean[] insertable = getPropertyInsertability();
3133 for ( int i = 0; i < fields.length; i++ ) {
3134 notNull[i] = insertable[i] && fields[i] != null;
3135 }
3136 return notNull;
3137 }
3138
3139 /**
3140 * Locate the property-indices of all properties considered to be dirty.
3141 *
3142 * @param currentState The current state of the entity (the state to be checked).
3143 * @param previousState The previous state of the entity (the state to be checked against).
3144 * @param entity The entity for which we are checking state dirtiness.
3145 * @param session The session in which the check is ccurring.
3146 * @return <tt>null</tt> or the indices of the dirty properties
3147 * @throws HibernateException
3148 */
3149 public int[] findDirty(Object[] currentState, Object[] previousState, Object entity, SessionImplementor session)
3150 throws HibernateException {
3151 int[] props = TypeFactory.findDirty(
3152 entityMetamodel.getProperties(),
3153 currentState,
3154 previousState,
3155 propertyColumnUpdateable,
3156 hasUninitializedLazyProperties( entity, session.getEntityMode() ),
3157 session
3158 );
3159 if ( props == null ) {
3160 return null;
3161 }
3162 else {
3163 logDirtyProperties( props );
3164 return props;
3165 }
3166 }
3167
3168 /**
3169 * Locate the property-indices of all properties considered to be dirty.
3170 *
3171 * @param old The old state of the entity.
3172 * @param current The current state of the entity.
3173 * @param entity The entity for which we are checking state modification.
3174 * @param session The session in which the check is ccurring.
3175 * @return <tt>null</tt> or the indices of the modified properties
3176 * @throws HibernateException
3177 */
3178 public int[] findModified(Object[] old, Object[] current, Object entity, SessionImplementor session)
3179 throws HibernateException {
3180 int[] props = TypeFactory.findModified(
3181 entityMetamodel.getProperties(),
3182 current,
3183 old,
3184 propertyColumnUpdateable,
3185 hasUninitializedLazyProperties( entity, session.getEntityMode() ),
3186 session
3187 );
3188 if ( props == null ) {
3189 return null;
3190 }
3191 else {
3192 logDirtyProperties( props );
3193 return props;
3194 }
3195 }
3196
3197 /**
3198 * Which properties appear in the SQL update?
3199 * (Initialized, updateable ones!)
3200 */
3201 protected boolean[] getPropertyUpdateability(Object entity, EntityMode entityMode) {
3202 return hasUninitializedLazyProperties( entity, entityMode ) ?
3203 getNonLazyPropertyUpdateability() :
3204 getPropertyUpdateability();
3205 }
3206
3207 private void logDirtyProperties(int[] props) {
3208 if ( log.isTraceEnabled() ) {
3209 for ( int i = 0; i < props.length; i++ ) {
3210 String propertyName = entityMetamodel.getProperties()[ props[i] ].getName();
3211 log.trace( StringHelper.qualify( getEntityName(), propertyName ) + " is dirty" );
3212 }
3213 }
3214 }
3215
3216 protected EntityTuplizer getTuplizer(SessionImplementor session) {
3217 return getTuplizer( session.getEntityMode() );
3218 }
3219
3220 protected EntityTuplizer getTuplizer(EntityMode entityMode) {
3221 return entityMetamodel.getTuplizer( entityMode );
3222 }
3223
3224 public SessionFactoryImplementor getFactory() {
3225 return factory;
3226 }
3227
3228 public EntityMetamodel getEntityMetamodel() {
3229 return entityMetamodel;
3230 }
3231
3232 public boolean hasCache() {
3233 return cacheAccessStrategy != null;
3234 }
3235
3236 public EntityRegionAccessStrategy getCacheAccessStrategy() {
3237 return cacheAccessStrategy;
3238 }
3239
3240 public CacheEntryStructure getCacheEntryStructure() {
3241 return cacheEntryStructure;
3242 }
3243
3244 public Comparator getVersionComparator() {
3245 return isVersioned() ? getVersionType().getComparator() : null;
3246 }
3247
3248 // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3249 public final String getEntityName() {
3250 return entityMetamodel.getName();
3251 }
3252
3253 public EntityType getEntityType() {
3254 return entityMetamodel.getEntityType();
3255 }
3256
3257 private String getSubclassEntityName(Class clazz) {
3258 return ( String ) entityNameBySubclass.get( clazz );
3259 }
3260
3261 public boolean isPolymorphic() {
3262 return entityMetamodel.isPolymorphic();
3263 }
3264
3265 public boolean isInherited() {
3266 return entityMetamodel.isInherited();
3267 }
3268
3269 public boolean hasCascades() {
3270 return entityMetamodel.hasCascades();
3271 }
3272
3273 public boolean hasIdentifierProperty() {
3274 return !entityMetamodel.getIdentifierProperty().isVirtual();
3275 }
3276
3277 public VersionType getVersionType() {
3278 return ( VersionType ) locateVersionType();
3279 }
3280
3281 private Type locateVersionType() {
3282 return entityMetamodel.getVersionProperty() == null ?
3283 null :
3284 entityMetamodel.getVersionProperty().getType();
3285 }
3286
3287 public int getVersionProperty() {
3288 return entityMetamodel.getVersionPropertyIndex();
3289 }
3290
3291 public boolean isVersioned() {
3292 return entityMetamodel.isVersioned();
3293 }
3294
3295 public boolean isIdentifierAssignedByInsert() {
3296 return entityMetamodel.getIdentifierProperty().isIdentifierAssignedByInsert();
3297 }
3298
3299 public boolean hasLazyProperties() {
3300 return entityMetamodel.hasLazyProperties();
3301 }
3302
3303 // public boolean hasUninitializedLazyProperties(Object entity) {
3304 // if ( hasLazyProperties() ) {
3305 // InterceptFieldCallback callback = ( ( InterceptFieldEnabled ) entity ).getInterceptFieldCallback();
3306 // return callback != null && !( ( FieldInterceptor ) callback ).isInitialized();
3307 // }
3308 // else {
3309 // return false;
3310 // }
3311 // }
3312
3313 public void afterReassociate(Object entity, SessionImplementor session) {
3314 //if ( hasLazyProperties() ) {
3315 if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
3316 FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( entity );
3317 if ( interceptor != null ) {
3318 interceptor.setSession( session );
3319 }
3320 else {
3321 FieldInterceptor fieldInterceptor = FieldInterceptionHelper.injectFieldInterceptor(
3322 entity,
3323 getEntityName(),
3324 null,
3325 session
3326 );
3327 fieldInterceptor.dirty();
3328 }
3329 }
3330 }
3331
3332 public Boolean isTransient(Object entity, SessionImplementor session) throws HibernateException {
3333 final Serializable id;
3334 if ( canExtractIdOutOfEntity() ) {
3335 id = getIdentifier( entity, session.getEntityMode() );
3336 }
3337 else {
3338 id = null;
3339 }
3340 // we *always* assume an instance with a null
3341 // identifier or no identifier property is unsaved!
3342 if ( id == null ) {
3343 return Boolean.TRUE;
3344 }
3345
3346 // check the version unsaved-value, if appropriate
3347 final Object version = getVersion( entity, session.getEntityMode() );
3348 if ( isVersioned() ) {
3349 // let this take precedence if defined, since it works for
3350 // assigned identifiers
3351 Boolean result = entityMetamodel.getVersionProperty()
3352 .getUnsavedValue().isUnsaved( version );
3353 if ( result != null ) {
3354 return result;
3355 }
3356 }
3357
3358 // check the id unsaved-value
3359 Boolean result = entityMetamodel.getIdentifierProperty()
3360 .getUnsavedValue().isUnsaved( id );
3361 if ( result != null ) {
3362 return result;
3363 }
3364
3365 // check to see if it is in the second-level cache
3366 if ( hasCache() ) {
3367 CacheKey ck = new CacheKey(
3368 id,
3369 getIdentifierType(),
3370 getRootEntityName(),
3371 session.getEntityMode(),
3372 session.getFactory()
3373 );
3374 if ( getCacheAccessStrategy().get( ck, session.getTimestamp() ) != null ) {
3375 return Boolean.FALSE;
3376 }
3377 }
3378
3379 return null;
3380 }
3381
3382 public boolean hasCollections() {
3383 return entityMetamodel.hasCollections();
3384 }
3385
3386 public boolean hasMutableProperties() {
3387 return entityMetamodel.hasMutableProperties();
3388 }
3389
3390 public boolean isMutable() {
3391 return entityMetamodel.isMutable();
3392 }
3393
3394 public boolean isAbstract() {
3395 return entityMetamodel.isAbstract();
3396 }
3397
3398 public boolean hasSubclasses() {
3399 return entityMetamodel.hasSubclasses();
3400 }
3401
3402 public boolean hasProxy() {
3403 return entityMetamodel.isLazy();
3404 }
3405
3406 public IdentifierGenerator getIdentifierGenerator() throws HibernateException {
3407 return entityMetamodel.getIdentifierProperty().getIdentifierGenerator();
3408 }
3409
3410 public String getRootEntityName() {
3411 return entityMetamodel.getRootName();
3412 }
3413
3414 public ClassMetadata getClassMetadata() {
3415 return this;
3416 }
3417
3418 public String getMappedSuperclass() {
3419 return entityMetamodel.getSuperclass();
3420 }
3421
3422 public boolean isExplicitPolymorphism() {
3423 return entityMetamodel.isExplicitPolymorphism();
3424 }
3425
3426 protected boolean useDynamicUpdate() {
3427 return entityMetamodel.isDynamicUpdate();
3428 }
3429
3430 protected boolean useDynamicInsert() {
3431 return entityMetamodel.isDynamicInsert();
3432 }
3433
3434 protected boolean hasEmbeddedCompositeIdentifier() {
3435 return entityMetamodel.getIdentifierProperty().isEmbedded();
3436 }
3437
3438 public boolean canExtractIdOutOfEntity() {
3439 return hasIdentifierProperty() || hasEmbeddedCompositeIdentifier() || hasIdentifierMapper();
3440 }
3441
3442 private boolean hasIdentifierMapper() {
3443 return entityMetamodel.getIdentifierProperty().hasIdentifierMapper();
3444 }
3445
3446 public String[] getKeyColumnNames() {
3447 return getIdentifierColumnNames();
3448 }
3449
3450 public String getName() {
3451 return getEntityName();
3452 }
3453
3454 public boolean isCollection() {
3455 return false;
3456 }
3457
3458 public boolean consumesEntityAlias() {
3459 return true;
3460 }
3461
3462 public boolean consumesCollectionAlias() {
3463 return false;
3464 }
3465
3466 public Type getPropertyType(String propertyName) throws MappingException {
3467 return propertyMapping.toType(propertyName);
3468 }
3469
3470 public Type getType() {
3471 return entityMetamodel.getEntityType();
3472 }
3473
3474 public boolean isSelectBeforeUpdateRequired() {
3475 return entityMetamodel.isSelectBeforeUpdate();
3476 }
3477
3478 protected final int optimisticLockMode() {
3479 return entityMetamodel.getOptimisticLockMode();
3480 }
3481
3482 public Object createProxy(Serializable id, SessionImplementor session) throws HibernateException {
3483 return entityMetamodel.getTuplizer( session.getEntityMode() )
3484 .createProxy( id, session );
3485 }
3486
3487 public String toString() {
3488 return StringHelper.unqualify( getClass().getName() ) +
3489 '(' + entityMetamodel.getName() + ')';
3490 }
3491
3492 public final String selectFragment(
3493 Joinable rhs,
3494 String rhsAlias,
3495 String lhsAlias,
3496 String entitySuffix,
3497 String collectionSuffix,
3498 boolean includeCollectionColumns) {
3499 return selectFragment( lhsAlias, entitySuffix );
3500 }
3501
3502 public boolean isInstrumented(EntityMode entityMode) {
3503 EntityTuplizer tuplizer = entityMetamodel.getTuplizerOrNull(entityMode);
3504 return tuplizer!=null && tuplizer.isInstrumented();
3505 }
3506
3507 public boolean hasInsertGeneratedProperties() {
3508 return entityMetamodel.hasInsertGeneratedValues();
3509 }
3510
3511 public boolean hasUpdateGeneratedProperties() {
3512 return entityMetamodel.hasUpdateGeneratedValues();
3513 }
3514
3515 public boolean isVersionPropertyGenerated() {
3516 return isVersioned() && ( getPropertyUpdateGenerationInclusions() [ getVersionProperty() ] != ValueInclusion.NONE );
3517 }
3518
3519 public boolean isVersionPropertyInsertable() {
3520 return isVersioned() && getPropertyInsertability() [ getVersionProperty() ];
3521 }
3522
3523 public void afterInitialize(Object entity, boolean lazyPropertiesAreUnfetched, SessionImplementor session) {
3524 getTuplizer( session ).afterInitialize( entity, lazyPropertiesAreUnfetched, session );
3525 }
3526
3527 public String[] getPropertyNames() {
3528 return entityMetamodel.getPropertyNames();
3529 }
3530
3531 public Type[] getPropertyTypes() {
3532 return entityMetamodel.getPropertyTypes();
3533 }
3534
3535 public boolean[] getPropertyLaziness() {
3536 return entityMetamodel.getPropertyLaziness();
3537 }
3538
3539 public boolean[] getPropertyUpdateability() {
3540 return entityMetamodel.getPropertyUpdateability();
3541 }
3542
3543 public boolean[] getPropertyCheckability() {
3544 return entityMetamodel.getPropertyCheckability();
3545 }
3546
3547 public boolean[] getNonLazyPropertyUpdateability() {
3548 return entityMetamodel.getNonlazyPropertyUpdateability();
3549 }
3550
3551 public boolean[] getPropertyInsertability() {
3552 return entityMetamodel.getPropertyInsertability();
3553 }
3554
3555 public ValueInclusion[] getPropertyInsertGenerationInclusions() {
3556 return entityMetamodel.getPropertyInsertGenerationInclusions();
3557 }
3558
3559 public ValueInclusion[] getPropertyUpdateGenerationInclusions() {
3560 return entityMetamodel.getPropertyUpdateGenerationInclusions();
3561 }
3562
3563 public boolean[] getPropertyNullability() {
3564 return entityMetamodel.getPropertyNullability();
3565 }
3566
3567 public boolean[] getPropertyVersionability() {
3568 return entityMetamodel.getPropertyVersionability();
3569 }
3570
3571 public CascadeStyle[] getPropertyCascadeStyles() {
3572 return entityMetamodel.getCascadeStyles();
3573 }
3574
3575 public final Class getMappedClass(EntityMode entityMode) {
3576 Tuplizer tup = entityMetamodel.getTuplizerOrNull(entityMode);
3577 return tup==null ? null : tup.getMappedClass();
3578 }
3579
3580 public boolean implementsLifecycle(EntityMode entityMode) {
3581 return getTuplizer( entityMode ).isLifecycleImplementor();
3582 }
3583
3584 public boolean implementsValidatable(EntityMode entityMode) {
3585 return getTuplizer( entityMode ).isValidatableImplementor();
3586 }
3587
3588 public Class getConcreteProxyClass(EntityMode entityMode) {
3589 return getTuplizer( entityMode ).getConcreteProxyClass();
3590 }
3591
3592 public void setPropertyValues(Object object, Object[] values, EntityMode entityMode)
3593 throws HibernateException {
3594 getTuplizer( entityMode ).setPropertyValues( object, values );
3595 }
3596
3597 public void setPropertyValue(Object object, int i, Object value, EntityMode entityMode)
3598 throws HibernateException {
3599 getTuplizer( entityMode ).setPropertyValue( object, i, value );
3600 }
3601
3602 public Object[] getPropertyValues(Object object, EntityMode entityMode)
3603 throws HibernateException {
3604 return getTuplizer( entityMode ).getPropertyValues( object );
3605 }
3606
3607 public Object getPropertyValue(Object object, int i, EntityMode entityMode)
3608 throws HibernateException {
3609 return getTuplizer( entityMode ).getPropertyValue( object , i );
3610 }
3611
3612 public Object getPropertyValue(Object object, String propertyName, EntityMode entityMode)
3613 throws HibernateException {
3614 return getTuplizer( entityMode ).getPropertyValue( object, propertyName );
3615 }
3616
3617 public Serializable getIdentifier(Object object, EntityMode entityMode)
3618 throws HibernateException {
3619 return getTuplizer( entityMode ).getIdentifier( object );
3620 }
3621
3622 public void setIdentifier(Object object, Serializable id, EntityMode entityMode)
3623 throws HibernateException {
3624 getTuplizer( entityMode ).setIdentifier( object, id );
3625 }
3626
3627 public Object getVersion(Object object, EntityMode entityMode)
3628 throws HibernateException {
3629 return getTuplizer( entityMode ).getVersion( object );
3630 }
3631
3632 public Object instantiate(Serializable id, EntityMode entityMode)
3633 throws HibernateException {
3634 return getTuplizer( entityMode ).instantiate( id );
3635 }
3636
3637 public boolean isInstance(Object object, EntityMode entityMode) {
3638 return getTuplizer( entityMode ).isInstance( object );
3639 }
3640
3641 public boolean hasUninitializedLazyProperties(Object object, EntityMode entityMode) {
3642 return getTuplizer( entityMode ).hasUninitializedLazyProperties( object );
3643 }
3644
3645 public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion, EntityMode entityMode) {
3646 getTuplizer( entityMode ).resetIdentifier( entity, currentId, currentVersion );
3647 }
3648
3649 public EntityPersister getSubclassEntityPersister(Object instance, SessionFactoryImplementor factory, EntityMode entityMode) {
3650 if ( !hasSubclasses() ) {
3651 return this;
3652 }
3653 else {
3654 // TODO : really need a way to do something like :
3655 // getTuplizer(entityMode).determineConcreteSubclassEntityName(instance)
3656 Class clazz = instance.getClass();
3657 if ( clazz == getMappedClass( entityMode ) ) {
3658 return this;
3659 }
3660 else {
3661 String subclassEntityName = getSubclassEntityName( clazz );
3662 if ( subclassEntityName == null ) {
3663 throw new HibernateException(
3664 "instance not of expected entity type: " + clazz.getName() +
3665 " is not a: " + getEntityName()
3666 );
3667 }
3668 else {
3669 return factory.getEntityPersister( subclassEntityName );
3670 }
3671 }
3672 }
3673 }
3674
3675 public EntityMode guessEntityMode(Object object) {
3676 return entityMetamodel.guessEntityMode(object);
3677 }
3678
3679 public boolean isMultiTable() {
3680 return false;
3681 }
3682
3683 public String getTemporaryIdTableName() {
3684 return temporaryIdTableName;
3685 }
3686
3687 public String getTemporaryIdTableDDL() {
3688 return temporaryIdTableDDL;
3689 }
3690
3691 protected int getPropertySpan() {
3692 return entityMetamodel.getPropertySpan();
3693 }
3694
3695 public Object[] getPropertyValuesToInsert(Object object, Map mergeMap, SessionImplementor session) throws HibernateException {
3696 return getTuplizer( session.getEntityMode() ).getPropertyValuesToInsert( object, mergeMap, session );
3697 }
3698
3699 public void processInsertGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
3700 if ( !hasInsertGeneratedProperties() ) {
3701 throw new AssertionFailure("no insert-generated properties");
3702 }
3703 processGeneratedProperties( id, entity, state, session, sqlInsertGeneratedValuesSelectString, getPropertyInsertGenerationInclusions() );
3704 }
3705
3706 public void processUpdateGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
3707 if ( !hasUpdateGeneratedProperties() ) {
3708 throw new AssertionFailure("no update-generated properties");
3709 }
3710 processGeneratedProperties( id, entity, state, session, sqlUpdateGeneratedValuesSelectString, getPropertyUpdateGenerationInclusions() );
3711 }
3712
3713 private void processGeneratedProperties(
3714 Serializable id,
3715 Object entity,
3716 Object[] state,
3717 SessionImplementor session,
3718 String selectionSQL,
3719 ValueInclusion[] includeds) {
3720
3721 session.getBatcher().executeBatch(); //force immediate execution of the insert
3722
3723 try {
3724 PreparedStatement ps = session.getBatcher().prepareSelectStatement( selectionSQL );
3725 try {
3726 getIdentifierType().nullSafeSet( ps, id, 1, session );
3727 ResultSet rs = ps.executeQuery();
3728 try {
3729 if ( !rs.next() ) {
3730 throw new HibernateException(
3731 "Unable to locate row for retrieval of generated properties: " +
3732 MessageHelper.infoString( this, id, getFactory() )
3733 );
3734 }
3735 for ( int i = 0; i < getPropertySpan(); i++ ) {
3736 if ( includeds[i] != ValueInclusion.NONE ) {
3737 Object hydratedState = getPropertyTypes()[i].hydrate( rs, getPropertyAliases( "", i ), session, entity );
3738 state[i] = getPropertyTypes()[i].resolve( hydratedState, session, entity );
3739 setPropertyValue( entity, i, state[i], session.getEntityMode() );
3740 }
3741 }
3742 }
3743 finally {
3744 if ( rs != null ) {
3745 rs.close();
3746 }
3747 }
3748 }
3749 finally {
3750 session.getBatcher().closeStatement( ps );
3751 }
3752 }
3753 catch( SQLException sqle ) {
3754 throw JDBCExceptionHelper.convert(
3755 getFactory().getSQLExceptionConverter(),
3756 sqle,
3757 "unable to select generated column values",
3758 selectionSQL
3759 );
3760 }
3761
3762 }
3763
3764 public String getIdentifierPropertyName() {
3765 return entityMetamodel.getIdentifierProperty().getName();
3766 }
3767
3768 public Type getIdentifierType() {
3769 return entityMetamodel.getIdentifierProperty().getType();
3770 }
3771
3772 public boolean hasSubselectLoadableCollections() {
3773 return hasSubselectLoadableCollections;
3774 }
3775
3776 public int[] getNaturalIdentifierProperties() {
3777 return entityMetamodel.getNaturalIdentifierProperties();
3778 }
3779
3780 public Object[] getNaturalIdentifierSnapshot(Serializable id, SessionImplementor session) throws HibernateException {
3781 if ( !hasNaturalIdentifier() ) {
3782 throw new MappingException( "persistent class did not define a natural-id : " + MessageHelper.infoString( this ) );
3783 }
3784 if ( log.isTraceEnabled() ) {
3785 log.trace( "Getting current natural-id snapshot state for: " + MessageHelper.infoString( this, id, getFactory() ) );
3786 }
3787
3788 int[] naturalIdPropertyIndexes = getNaturalIdentifierProperties();
3789 int naturalIdPropertyCount = naturalIdPropertyIndexes.length;
3790 boolean[] naturalIdMarkers = new boolean[ getPropertySpan() ];
3791 Type[] extractionTypes = new Type[ naturalIdPropertyCount ];
3792 for ( int i = 0; i < naturalIdPropertyCount; i++ ) {
3793 extractionTypes[i] = getPropertyTypes()[ naturalIdPropertyIndexes[i] ];
3794 naturalIdMarkers[ naturalIdPropertyIndexes[i] ] = true;
3795 }
3796
3797 ///////////////////////////////////////////////////////////////////////
3798 // TODO : look at perhaps caching this...
3799 Select select = new Select( getFactory().getDialect() );
3800 if ( getFactory().getSettings().isCommentsEnabled() ) {
3801 select.setComment( "get current natural-id state " + getEntityName() );
3802 }
3803 select.setSelectClause( concretePropertySelectFragmentSansLeadingComma( getRootAlias(), naturalIdMarkers ) );
3804 select.setFromClause( fromTableFragment( getRootAlias() ) + fromJoinFragment( getRootAlias(), true, false ) );
3805
3806 String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
3807 String whereClause = new StringBuffer()
3808 .append( StringHelper.join( "=? and ",
3809 aliasedIdColumns ) )
3810 .append( "=?" )
3811 .append( whereJoinFragment( getRootAlias(), true, false ) )
3812 .toString();
3813
3814 String sql = select.setOuterJoins( "", "" )
3815 .setWhereClause( whereClause )
3816 .toStatementString();
3817 ///////////////////////////////////////////////////////////////////////
3818
3819 Object[] snapshot = new Object[ naturalIdPropertyCount ];
3820 try {
3821 PreparedStatement ps = session.getBatcher().prepareSelectStatement( sql );
3822 try {
3823 getIdentifierType().nullSafeSet( ps, id, 1, session );
3824 ResultSet rs = ps.executeQuery();
3825 try {
3826 //if there is no resulting row, return null
3827 if ( !rs.next() ) {
3828 return null;
3829 }
3830
3831 for ( int i = 0; i < naturalIdPropertyCount; i++ ) {
3832 snapshot[i] = extractionTypes[i].hydrate( rs, getPropertyAliases( "", naturalIdPropertyIndexes[i] ), session, null );
3833 }
3834 return snapshot;
3835 }
3836 finally {
3837 rs.close();
3838 }
3839 }
3840 finally {
3841 session.getBatcher().closeStatement( ps );
3842 }
3843 }
3844 catch ( SQLException sqle ) {
3845 throw JDBCExceptionHelper.convert(
3846 getFactory().getSQLExceptionConverter(),
3847 sqle,
3848 "could not retrieve snapshot: " +
3849 MessageHelper.infoString( this, id, getFactory() ),
3850 sql
3851 );
3852 }
3853 }
3854
3855 protected String concretePropertySelectFragmentSansLeadingComma(String alias, boolean[] include) {
3856 String concretePropertySelectFragment = concretePropertySelectFragment( alias, include );
3857 int firstComma = concretePropertySelectFragment.indexOf( ", " );
3858 if ( firstComma == 0 ) {
3859 concretePropertySelectFragment = concretePropertySelectFragment.substring( 2 );
3860 }
3861 return concretePropertySelectFragment;
3862 }
3863 public boolean hasNaturalIdentifier() {
3864 return entityMetamodel.hasNaturalIdentifier();
3865 }
3866
3867 public void setPropertyValue(Object object, String propertyName, Object value, EntityMode entityMode)
3868 throws HibernateException {
3869 getTuplizer( entityMode ).setPropertyValue( object, propertyName, value );
3870 }
3871 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3872
3873 }