Skip to content

jdoc, comments, and doc #10142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions documentation/src/main/asciidoc/introduction/Interacting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,19 @@ The two most useful heuristics are:
1. If the entity has a <<generated-identifiers,generated identifier>>, 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 <<version-attributes,version>>, 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

Expand Down
51 changes: 30 additions & 21 deletions hibernate-core/src/main/java/org/hibernate/Interceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ default void onCollectionRemove(Object collection, Object key) {
*/
default void onCollectionUpdate(Object collection, Object key) {
}

/**
* Called before a flush.
*
Expand All @@ -223,32 +224,37 @@ default void onCollectionUpdate(Object collection, Object key) {
default void preFlush(Iterator<Object> 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<Object> 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:
* <ul>
* <li>{@code Boolean.TRUE} - the entity is transient
* <li>{@code Boolean.FALSE} - the entity is detached
* <li>{@code null} - Hibernate uses the {@code unsaved-value} mapping and other heuristics to
* determine if the object is unsaved
* <li>{@code Boolean.TRUE} if the entity is transient,
* <li>{@code Boolean.FALSE} if the entity is detached, or
* <li>{@code null} to signal that the usual heuristics should be used to determine
* if the instance is transient
* </ul>
* 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
* <ul>
* <li>an array of property indices - the entity is dirty
* <li>an empty array - the entity is not dirty
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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
* <p>
* {@inheritDoc}
*/
@Override
public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException {
final EntityKey key = session.generateEntityKey( id, persister );
Expand Down Expand Up @@ -1193,14 +1187,13 @@ public void initializeNonLazyCollections() throws HibernateException {
public void initializeNonLazyCollections(Consumer<PersistentCollection<?>> 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 ) );
}
}
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
*
Expand All @@ -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.
Expand All @@ -133,13 +121,16 @@ default boolean hasLoadContext() {
/**
* Retrieve the cached database snapshot for the requested entity key.
* <p>
* This differs from {@link #getDatabaseSnapshot} in two important respects:<ol>
* <li>no snapshot is obtained from the database if not already cached</li>
* <li>an entry of {@link #NO_ROW} here is interpreted as an exception</li>
* This differs from {@link #getDatabaseSnapshot} in two important ways:
* <ol>
* <li>no snapshot is obtained from the database if not already cached,
* and
* <li>an entry of {@link #NO_ROW} here results in an exception.
* </ol>
*
* @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);

Expand Down Expand Up @@ -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.
* <p>
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

Expand Down