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