diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index 946f8c157322..a302779b9ce9 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -111,13 +111,19 @@ The two most useful heuristics are: 1. If the entity has a <>, the value of the id field is inspected: if the value currently assigned to the id field is the default value for the type of the field, then the object is transient; otherwise, the object is detached. 2. If the entity has a <>, the value of the version field is inspected: if the value currently assigned to the version field is the default value, or a negative number, then the object is transient; otherwise, the object is detached. -If the entity has neither a generated id, nor a version, Hibernate falls back to just doing something reasonable. +If the entity has neither a generated id, nor a version, Hibernate usually falls back to just doing something reasonable. +In extreme cases a `SELECT` query will be issued to determine whether a matching row exists in the database. [WARNING] These heuristics aren't perfect. -It's quite easy to confuse Hibernate by assigning a value to the id field or version field that makes a new transient instance look like it's detached. +It's quite easy to confuse Hibernate by assigning a value to the id field or version field, makeing a new transient instance look like it's detached. We therefore strongly discourage assigning values to fields annotated `@GeneratedValue` or `@Version` before passing an entity to Hibernate. +[TIP] +If the heuristics ever happen cause a real problem, you may implement your own Post-it tagging via link:{doc-javadoc-url}org/hibernate/Interceptor.html#isTransient(java.lang.Object)[`Interceptor.isTransient()`]. +The `Interceptor` interface -- along with its friend link:{doc-javadoc-url}org/hibernate/CustomEntityDirtinessStrategy.html[`CustomEntityDirtinessStrategy`] -- allows advanced users to augment the built-in handling of managed entities with custom behavior. +These interfaces are very useful if you're building your own persistence framework with Hibernate as the foundation. + [[creating-session]] === Creating a session diff --git a/hibernate-core/src/main/java/org/hibernate/Interceptor.java b/hibernate-core/src/main/java/org/hibernate/Interceptor.java index 3412a5d1be88..dad428c2dfac 100644 --- a/hibernate-core/src/main/java/org/hibernate/Interceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/Interceptor.java @@ -215,6 +215,7 @@ default void onCollectionRemove(Object collection, Object key) { */ default void onCollectionUpdate(Object collection, Object key) { } + /** * Called before a flush. * @@ -223,32 +224,37 @@ default void onCollectionUpdate(Object collection, Object key) { default void preFlush(Iterator entities) {} /** - * Called after a flush that actually ends in execution of the SQL statements required to synchronize - * in-memory state with the database. + * Called after a flush that actually ends in execution of the SQL statements + * required to synchronize in-memory state with the database. * * @param entities The entities that were flushed. */ default void postFlush(Iterator entities) {} /** - * Called to distinguish between transient and detached entities. The return value determines the - * state of the entity with respect to the current session. + * Called to distinguish between transient and detached entities. The return + * value determines the state of the entity with respect to the current session. + * This method should return: *
    - *
  • {@code Boolean.TRUE} - the entity is transient - *
  • {@code Boolean.FALSE} - the entity is detached - *
  • {@code null} - Hibernate uses the {@code unsaved-value} mapping and other heuristics to - * determine if the object is unsaved + *
  • {@code Boolean.TRUE} if the entity is transient, + *
  • {@code Boolean.FALSE} if the entity is detached, or + *
  • {@code null} to signal that the usual heuristics should be used to determine + * if the instance is transient *
+ * Heuristics used when this method returns null are based on the value of the + * {@linkplain jakarta.persistence.GeneratedValue generated} id field, or the + * {@linkplain jakarta.persistence.Version version} field, if any. * * @param entity a transient or detached entity - * @return Boolean or {@code null} to choose default behaviour + * @return {@link Boolean} or {@code null} to choose default behaviour */ default Boolean isTransient(Object entity) { return null; } /** - * Called from {@code flush()}. The return value determines whether the entity is updated + * Called from {@code flush()}. The return value determines whether the entity + * is updated *
    *
  • an array of property indices - the entity is dirty *
  • an empty array - the entity is not dirty @@ -258,11 +264,13 @@ default Boolean isTransient(Object entity) { * @param entity The entity for which to find dirty properties. * @param id The identifier of the entity * @param currentState The current entity state as taken from the entity instance - * @param previousState The state of the entity when it was last synchronized (generally when it was loaded) + * @param previousState The state of the entity when it was last synchronized + * (generally when it was loaded) * @param propertyNames The names of the entity properties. * @param types The types of the entity properties * - * @return array of dirty property indices or {@code null} to indicate Hibernate should perform default behaviour + * @return array of dirty property indices or {@code null} to indicate Hibernate + * should perform default behaviour */ default int[] findDirty( Object entity, @@ -275,9 +283,9 @@ default int[] findDirty( } /** - * Instantiate the entity. Return {@code null} to indicate that Hibernate should use - * the default constructor of the class. The identifier property of the returned instance - * should be initialized with the given identifier. + * Instantiate the entity. Return {@code null} to indicate that Hibernate should + * use the default constructor of the class. The identifier property of the + * returned instance should be initialized with the given identifier. */ default Object instantiate( String entityName, @@ -287,9 +295,9 @@ default Object instantiate( } /** - * Instantiate the entity. Return {@code null} to indicate that Hibernate should use - * the default constructor of the class. The identifier property of the returned instance - * should be initialized with the given identifier. + * Instantiate the entity. Return {@code null} to indicate that Hibernate should + * use the default constructor of the class. The identifier property of the + * returned instance should be initialized with the given identifier. */ default Object instantiate( String entityName, @@ -324,9 +332,10 @@ default Object getEntity(String entityName, Object id) { } /** - * Called when a Hibernate transaction is begun via the Hibernate {@code Transaction} - * API. Will not be called if transactions are being controlled via some other - * mechanism (CMT, for example). + * Called when a Hibernate transaction is begun via the JPA-standard + * {@link jakarta.persistence.EntityTransaction} API, or via {@link Transaction}. + * This method is not be called if transactions are being controlled via some + * other mechanism, for example, if transactions are managed by a container. * * @param tx The Hibernate transaction facade object */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 06049e9a912b..852404e3e729 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -54,7 +54,6 @@ import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.InstanceIdentityMap; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -78,6 +77,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; +import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; /** @@ -324,12 +324,6 @@ public void afterTransactionCompletion() { entityEntryContext.downgradeLocks(); } - /** - * Get the current state of the entity as known to the underlying - * database, or null if there is no corresponding row - *

    - * {@inheritDoc} - */ @Override public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { final EntityKey key = session.generateEntityKey( id, persister ); @@ -1193,14 +1187,13 @@ public void initializeNonLazyCollections() throws HibernateException { public void initializeNonLazyCollections(Consumer> initializeAction ) { if ( loadCounter == 0 ) { LOG.trace( "Initializing non-lazy collections" ); - - //do this work only at the very highest level of the load - //don't let this method be called recursively + // do this work only at the very highest level of the load + // don't let this method be called recursively loadCounter++; try { - int size; - while ( nonlazyCollections != null && ( size = nonlazyCollections.size() ) > 0 ) { - //note that each iteration of the loop may add new elements + while ( nonlazyCollections != null && !nonlazyCollections.isEmpty() ) { + final int size = nonlazyCollections.size(); + // note that each iteration of the loop may add new elements initializeAction.accept( nonlazyCollections.remove( size - 1 ) ); } } @@ -1733,14 +1726,15 @@ private Object getIndexInParent( @Override public void addNullProperty(EntityKey ownerKey, String propertyName) { if ( nullAssociations == null ) { - nullAssociations = CollectionHelper.setOfSize( INIT_COLL_SIZE ); + nullAssociations = setOfSize( INIT_COLL_SIZE ); } nullAssociations.add( new AssociationKey( ownerKey, propertyName ) ); } @Override public boolean isPropertyNull(EntityKey ownerKey, String propertyName) { - return nullAssociations != null && nullAssociations.contains( new AssociationKey( ownerKey, propertyName ) ); + return nullAssociations != null + && nullAssociations.contains( new AssociationKey( ownerKey, propertyName ) ); } private void clearNullProperties() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index fd712b699980..1293b61ed2f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -67,14 +67,6 @@ default boolean hasLoadContext() { return true; } -// /** -// * Add a collection which has no owner loaded -// * -// * @param key The collection key under which to add the collection -// * @param collection The collection to add -// */ -// void addUnownedCollection(CollectionKey key, PersistentCollection collection); - /** * Take ownership of a previously unowned collection, if one. This method returns {@code null} if no such * collection was previously added () or was previously removed. @@ -99,11 +91,6 @@ default boolean hasLoadContext() { */ void clear(); -// /** -// * @return false if we know for certain that all the entities are read-only -// */ -// boolean hasNonReadOnlyEntities(); - /** * Set the status of an entry * @@ -118,8 +105,9 @@ default boolean hasLoadContext() { void afterTransactionCompletion(); /** - * Get the current state of the entity as known to the underlying database, or null if there is no - * corresponding row + * Get the current state of the entity as known to the underlying database, + * or {@code null} if there is no corresponding row. This operation might + * result in a {@code select} query being executed against the database. * * @param id The identifier of the entity for which to grab a snapshot * @param persister The persister of the entity. @@ -133,13 +121,16 @@ default boolean hasLoadContext() { /** * Retrieve the cached database snapshot for the requested entity key. *

    - * This differs from {@link #getDatabaseSnapshot} in two important respects:

      - *
    1. no snapshot is obtained from the database if not already cached
    2. - *
    3. an entry of {@link #NO_ROW} here is interpreted as an exception
    4. + * This differs from {@link #getDatabaseSnapshot} in two important ways: + *
        + *
      1. no snapshot is obtained from the database if not already cached, + * and + *
      2. an entry of {@link #NO_ROW} here results in an exception. *
      + * * @param key The entity key for which to retrieve the cached snapshot * @return The cached snapshot - * @throws IllegalStateException if the cached snapshot was == {@link #NO_ROW}. + * @throws IllegalStateException if the cached snapshot was {@link #NO_ROW}. */ Object[] getCachedDatabaseSnapshot(EntityKey key); @@ -657,7 +648,7 @@ EntityHolder claimEntityHolderIfPossible( * if the child is contained within that collection. If so, we have found the owner; if not, we go on. *

      * Also need to account for {@code mergeMap} which acts as a local copy cache managed for the duration of a merge - * operation. It represents a map of the detached entity instances pointing to the corresponding managed instance. + * operation. It represents a map of the detached entity instances pointing to the corresponding managed instance. * * @param entityName The entity name for the entity type which would own the child * @param propertyName The name of the property on the owning entity type which would name this child association. diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index cb684d82f08b..63d48ef1b958 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -149,9 +149,9 @@ public Serializable disassemble(Object value, SessionFactoryImplementor sessionF @Override public Object assemble(Serializable oid, SharedSessionContractImplementor session, Object owner) throws HibernateException { - //this should be a call to resolve(), not resolveIdentifier(), - //because it might be a property-ref, and we did not cache the - //referenced value + // this should be a call to resolve(), not resolveIdentifier(), + // because it might be a property-ref, and we did not cache the + // referenced value return resolve( session.getContextEntityIdentifier( owner ), session, owner ); }