org.hibernate.Hibernate Maven / Gradle / Ivy
Show all versions of hibernate-core Show documentation
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Supplier;
import jakarta.persistence.metamodel.Attribute;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.collection.spi.PersistentBag;
import org.hibernate.collection.spi.PersistentList;
import org.hibernate.collection.spi.PersistentMap;
import org.hibernate.collection.spi.PersistentSet;
import org.hibernate.collection.spi.PersistentSortedMap;
import org.hibernate.collection.spi.PersistentSortedSet;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.collection.spi.LazyInitializable;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
/**
* Various utility functions for working with proxies and lazy collection references.
*
* Operations like {@link #isInitialized(Object)} and {@link #initialize(Object)} are
* of general purpose. But {@link #createDetachedProxy(SessionFactory, Class, Object)}
* and {@link CollectionInterface#createDetachedInstance()} are intended for use by
* generic code that must materialize an "amputated" graph of Hibernate entities.
* (For example, a library which deserializes entities from JSON.)
*
* Lazy fetching of a {@linkplain jakarta.persistence.OneToOne one to one} or
* {@linkplain jakarta.persistence.ManyToOne many to one} association requires special
* bytecode tricks. The tricks used depend on whether build-time bytecode enhancement
* is enabled.
*
* When bytecode enhancement is not used, an unfetched lazy association is
* represented by a proxy object which holds the identifier (foreign key) of
* the associated entity instance.
*
* - The identifier property of the proxy object is set when the proxy is instantiated.
* The program may obtain the entity identifier value of an unfetched proxy, without
* triggering lazy fetching, by calling the corresponding getter method.
* (It's even possible to set an association to reference an unfetched proxy.)
*
- A delegate entity instance is lazily fetched when any other method of the proxy
* is called. Once fetched, the proxy delegates all method invocations to the
* delegate.
*
- The proxy does not have the same concrete type as the proxied delegate, and so
* {@link #getClass(Object)} must be used in place of {@link Object#getClass()},
* and this method fetches the entity by side-effect.
*
- For a polymorphic association, the concrete type of the associated entity is
* not known until the delegate is fetched from the database, and so
* {@link #unproxy(Object, Class)}} must be used to perform typecasts, and
* {@link #getClass(Object)} must be used instead of the Java {@code instanceof}
* operator.
*
* When bytecode enhancement is used, there is no such indirection, but the
* associated entity instance is initially in an unloaded state, with only its
* identifier field set.
*
* - The identifier field of an unloaded entity instance is set when the unloaded
* instance is instantiated. The program may obtain the identifier of an unloaded
* entity, without triggering lazy loading, by accessing the field containing the
* identifier.
*
- The remaining non-lazy state of the entity instance is loaded lazily when any
* other field is accessed.
*
- Typecasts, the Java {@code instanceof} operator, and {@link Object#getClass()}
* may be used as normal.
*
* As an exception to the above rules, polymorphic associations always work
* as if bytecode enhancement was not enabled.
*
* Graphs of Hibernate entities obtained from a {@link Session} are usually in an
* amputated form, with associations and collections replaced by proxies and lazy
* collections. (That is, by instances of the internal types {@link HibernateProxy}
* and {@link PersistentCollection}.) These objects are fully serializable using
* Java serialization, but can cause discomfort when working with custom serialization
* libraries. Therefore, this class defines operations that may be used to write code
* that completely removes the amputated leaves of the graph (the proxies) during
* serialization, and rematerializes and reattaches them during deserialization. It's
* possible, in principle, to use these operations, together with reflection, or with
* the Hibernate metamodel, to write such generic code for any given serialization
* library, but the details depend on what facilities the library itself offers for
* the program to intervene in the process of serialization and of deserialization.
*
* @author Gavin King
*/
public final class Hibernate {
/**
* Cannot be instantiated.
*/
private Hibernate() {
throw new UnsupportedOperationException();
}
/**
* Force initialization of a proxy or persistent collection. In the case of a
* many-valued association, only the collection itself is initialized. It is not
* guaranteed that the associated entities held within the collection will be
* initialized.
*
* @param proxy a persistable object, proxy, persistent collection or {@code null}
* @throws HibernateException if the proxy cannot be initialized at this time,
* for example, if the {@code Session} was closed
*/
public static void initialize(Object proxy) throws HibernateException {
if ( proxy != null ) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
if ( lazyInitializer != null ) {
lazyInitializer.initialize();
}
else if ( proxy instanceof LazyInitializable lazyInitializable ) {
lazyInitializable.forceInitialization();
}
else if ( isPersistentAttributeInterceptable( proxy ) ) {
if ( getAttributeInterceptor( proxy )
instanceof EnhancementAsProxyLazinessInterceptor enhancementInterceptor ) {
enhancementInterceptor.forceInitialize( proxy, null );
}
}
}
}
/**
* Determines if the given proxy or persistent collection is initialized.
*
* This operation is equivalent to {@link jakarta.persistence.PersistenceUtil#isLoaded(Object)}.
*
* @param proxy a persistable object, proxy, persistent collection or {@code null}
* @return true if the argument is already initialized, or is not a proxy or collection
*/
public static boolean isInitialized(Object proxy) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
if ( lazyInitializer != null ) {
return !lazyInitializer.isUninitialized();
}
else if ( isPersistentAttributeInterceptable( proxy ) ) {
final boolean uninitialized =
getAttributeInterceptor( proxy )
instanceof EnhancementAsProxyLazinessInterceptor enhancementInterceptor
&& !enhancementInterceptor.isInitialized();
return !uninitialized;
}
else if ( proxy instanceof LazyInitializable lazyInitializable ) {
return lazyInitializable.wasInitialized();
}
else {
return true;
}
}
/**
* Obtain the {@linkplain Collection#size() size} of a persistent collection,
* without fetching its state from the database.
*
* @param collection a persistent collection associated with an open session
* @return the size of the collection
*
* @since 6.1.1
*/
public static int size(Collection> collection) {
return collection instanceof PersistentCollection> persistentCollection
? persistentCollection.getSize()
: collection.size();
}
/**
* Determine is the given persistent collection is {@linkplain Collection#isEmpty() empty},
* without fetching its state from the database.
*
* @param collection a persistent collection associated with an open session
* @return {@code true} if the collection is empty
*
* @since 7.0
*/
public static boolean isEmpty(Collection> collection) {
return collection instanceof PersistentCollection> persistentCollection
? persistentCollection.getSize() == 0
: collection.isEmpty();
}
/**
* Determine if the given persistent collection {@linkplain Collection#contains(Object) contains}
* the given element, without fetching its state from the database.
*
* @param collection a persistent collection associated with an open session
* @return true if the collection does contain the given element
*
* @since 6.1.1
*/
public static boolean contains(Collection super T> collection, T element) {
return collection instanceof PersistentCollection> persistentCollection
? persistentCollection.elementExists( element )
: collection.contains( element );
}
/**
* Obtain the value associated with the given key by the given persistent
* map, without fetching the state of the map from the database.
*
* @param map a persistent map associated with an open session
* @param key a key belonging to the map
* @return the value associated by the map with the given key
*
* @since 6.1.1
*/
@SuppressWarnings("unchecked")
public static V get(Map super K, V> map, K key) {
return map instanceof PersistentCollection> persistentCollection
? (V) persistentCollection.elementByIndex( key )
: map.get( key );
}
/**
* Obtain the element of the given persistent list with the given index,
* without fetching the state of the list from the database.
*
* @param list a persistent list associated with an open session
* @param key an index belonging to the list
* @return the element of the list with the given index
*
* @since 6.1.1
*/
@SuppressWarnings("unchecked")
public static T get(List list, int key) {
return list instanceof PersistentCollection> persistentCollection
? (T) persistentCollection.elementByIndex( key )
: list.get( key );
}
/**
* Get the true, underlying class of a proxied entity. This operation will
* initialize a proxy by side effect.
*
* @param proxy an entity instance or proxy
* @return the true class of the instance
*/
@SuppressWarnings("unchecked")
public static Class extends T> getClass(T proxy) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
final Class> result =
lazyInitializer != null
? lazyInitializer.getImplementation().getClass()
: proxy.getClass();
return (Class extends T>) result;
}
/**
* Get the true, underlying class of a proxied entity.
*
* Like {@link #getClass}, this operation might initialize a proxy by side effect.
* However, here the initialization is avoided if possible. If the entity type is
* defined with subclasses, the proxy will need to be initialized to properly
* determine the class.
*
* @param proxy an entity instance or proxy
* @return the true class of the instance
*
* @since 6.3
*/
@SuppressWarnings("unchecked")
public static Class extends T> getClassLazy(T proxy) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
final Class> result =
lazyInitializer != null
? lazyInitializer.getImplementationClass()
: proxy.getClass();
return (Class extends T>) result;
}
/**
* Determine if the true, underlying class of the proxied entity is assignable
* to the given class. This operation will initialize a proxy by side effect.
*
* @param proxy an entity instance or proxy
* @return {@code true} if the entity is an instance of the given class
*
* @since 6.2
*/
public static boolean isInstance(Object proxy, Class> entityClass) {
return entityClass.isInstance( proxy )
|| entityClass.isAssignableFrom( getClass( proxy ) );
}
/**
* Determines if the given attribute of the given entity instance is initialized.
*
* @param entity The entity instance or proxy
* @param attribute A persistent attribute of the entity
* @return true if the named property of the object is not listed as uninitialized;
* false otherwise
*/
public boolean isPropertyInitialized(E entity, Attribute super E, ?> attribute) {
return isPropertyInitialized( entity, attribute.getName() );
}
/**
* Determines if the property with the given name of the given entity instance is
* initialized. If the named property does not exist or is not persistent, this
* method always returns {@code true}.
*
* This operation is equivalent to {@link jakarta.persistence.PersistenceUtil#isLoaded(Object, String)}.
*
* @param proxy The entity instance or proxy
* @param attributeName the name of a persistent attribute of the object
* @return true if the named property of the object is not listed as uninitialized;
* false otherwise
*/
public static boolean isPropertyInitialized(Object proxy, String attributeName) {
final Object entity;
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
if ( lazyInitializer != null ) {
if ( lazyInitializer.isUninitialized() ) {
return false;
}
else {
entity = lazyInitializer.getImplementation();
}
}
else {
entity = proxy;
}
final boolean attributeUnloaded =
isPersistentAttributeInterceptable( entity )
&& getAttributeInterceptor( entity )
instanceof BytecodeLazyAttributeInterceptor lazyAttributeInterceptor
&& !lazyAttributeInterceptor.isAttributeLoaded( attributeName );
return !attributeUnloaded;
}
/**
* Initializes the property with the given name of the given entity instance.
*
* This operation is equivalent to {@link jakarta.persistence.PersistenceUnitUtil#load(Object, String)}.
*
* @param proxy The entity instance or proxy
* @param attributeName the name of a persistent attribute of the object
*/
public static void initializeProperty(Object proxy, String attributeName) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
final Object entity = lazyInitializer != null ? lazyInitializer.getImplementation() : proxy;
if ( isPersistentAttributeInterceptable( entity ) ) {
getAttributeInterceptor( entity ).readObject( entity, attributeName, null );
}
}
/**
* If the given object is not a proxy, return it. But, if it is a proxy, ensure
* that the proxy is initialized, and return a direct reference to its proxied
* entity object.
*
* @param proxy an object which might be a proxy for an entity
* @return a reference that is never proxied
*
* @throws LazyInitializationException if this operation is called on an
* uninitialized proxy that is not associated with an open session.
*/
public static Object unproxy(Object proxy) {
final LazyInitializer lazyInitializer = extractLazyInitializer( proxy );
return lazyInitializer != null ? lazyInitializer.getImplementation() : proxy;
}
/**
* If the given object is not a proxy, cast it to the given type, and return it.
* But, if it is a proxy, ensure that the proxy is initialized, and return a
* direct reference to its proxied entity object, after casting to the given type.
*
* @param proxy an object which might be a proxy for an entity
* @param entityClass an entity type to cast to
* @return a reference that is never proxied
*
* @throws LazyInitializationException if this operation is called on an
* uninitialized proxy that is not associated with an open session.
*/
public static T unproxy(T proxy, Class entityClass) {
return entityClass.cast( unproxy( proxy ) );
}
/**
* Obtain a detached, uninitialized reference (a proxy) for a persistent entity with
* the given identifier.
*
* The returned proxy is not associated with any session, and cannot be initialized
* by calling {@link #initialize(Object)}. It can be used to represent a reference to
* the entity when working with a detached object graph.
*
* @param sessionFactory the session factory with which the entity is associated
* @param entityClass the entity class
* @param id the id of the persistent entity instance
*
* @return a detached uninitialized proxy
*
* @since 6.0
*/
@SuppressWarnings("unchecked")
public static E createDetachedProxy(SessionFactory sessionFactory, Class entityClass, Object id) {
final EntityPersister persister =
sessionFactory.unwrap( SessionFactoryImplementor.class )
.getMappingMetamodel()
.findEntityDescriptor( entityClass );
if ( persister == null ) {
throw new UnknownEntityTypeException("unknown entity type");
}
return (E) persister.createProxy( id, null );
}
/**
* Operations for obtaining references to persistent collections of a certain type.
*
* @param the type of collection, for example, {@code List<User>}
*
* @since 6.0
*/
public static final class CollectionInterface {
private final Supplier detached;
private final Supplier created;
private CollectionInterface(Supplier detached, Supplier created) {
this.detached = detached;
this.created = created;
}
/**
* Obtain a detached, uninitialized persistent collection of the type represented
* by this object.
*
* The returned wrapper object is not associated with any session, and cannot be
* initialized by calling {@link #initialize(Object)}. It can be used to represent
* an uninitialized collection when working with a detached object graph.
*
* @return an uninitialized persistent collection
*/
public C createDetachedInstance() {
return detached.get();
}
/**
* Instantiate an empty collection of the type represented by this object.
*
* @return a newly-instantiated empty collection
*/
public C createNewInstance() {
return created.get();
}
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent bags
* of a given element type.
*
* @param the element type
*
* @since 6.0
*/
public static CollectionInterface> bag() {
return new CollectionInterface<>(PersistentBag::new, ArrayList::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent sets
* of a given element type.
*
* @param the element type
*
* @since 6.0
*/
public static CollectionInterface> set() {
return new CollectionInterface<>(PersistentSet::new, HashSet::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent lists
* of a given element type.
*
* @param the element type
*
* @since 6.0
*/
public static CollectionInterface> list() {
return new CollectionInterface<>(PersistentList::new, ArrayList::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent maps
* of a given key and value types.
*
* @param the key type
* @param the value type
*
* @since 6.0
*/
public static CollectionInterface