proposed API for instantiation of detached collections + proxies

this is useful for object-graph deserialization
This commit is contained in:
Gavin King 2021-11-26 12:42:20 +01:00 committed by Steve Ebersole
parent 6fbb90aa54
commit 863802cfd0
7 changed files with 360 additions and 2 deletions

View File

@ -6,18 +6,38 @@
*/
package org.hibernate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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 org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.collection.internal.PersistentList;
import org.hibernate.collection.internal.PersistentMap;
import org.hibernate.collection.internal.PersistentSet;
import org.hibernate.collection.internal.PersistentSortedMap;
import org.hibernate.collection.internal.PersistentSortedSet;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.HibernateIterator;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
@ -239,4 +259,158 @@ public final class Hibernate {
public static <T> T unproxy(T proxy, Class<T> 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
*/
public static <E> E createDetachedProxy(SessionFactory sessionFactory, Class<E> entityClass, Object id) {
EntityPersister persister =
sessionFactory.unwrap(SessionFactoryImplementor.class).getMetamodel()
.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 <C> the type of collection, for example, <tt>List&lt;User&gt;</tt>
*/
public static final class CollectionInterface<C> {
private final Supplier<C> detached;
private final Supplier<C> created;
private CollectionInterface(Supplier<C> detached, Supplier<C> 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 <U> the element type
*/
public static <U> CollectionInterface<Collection<U>> bag() {
return new CollectionInterface<>(PersistentBag::new, ArrayList::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent sets
* of a given element type.
*
* @param <U> the element type
*/
public static <U> CollectionInterface<Set<U>> set() {
return new CollectionInterface<>(PersistentSet::new, HashSet::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent lists
* of a given element type.
*
* @param <U> the element type
*/
public static <U> CollectionInterface<List<U>> 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 <U> the key type
* @param <V> the value type
*/
public static <U,V> CollectionInterface<Map<U,V>> map() {
return new CollectionInterface<>(PersistentMap::new, HashMap::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing sorted persistent sets
* of a given element type.
*
* @param <U> the element type
*/
public static <U> CollectionInterface<SortedSet<U>> sortedSet() {
return new CollectionInterface<>(PersistentSortedSet::new, TreeSet::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing sorted persistent maps
* of a given key and value types.
*
* @param <U> the key type
* @param <V> the value type
*
*/
public static <U,V> CollectionInterface<Map<U,V>> sortedMap() {
return new CollectionInterface<>(PersistentSortedMap::new, TreeMap::new);
}
/**
* Obtain an instance of {@link CollectionInterface} representing persistent collections
* of the given type.
*
* @param collectionClass the Java class object representing the collection type
*/
@SuppressWarnings("unchecked")
public static <C> CollectionInterface<C> collection(Class<C> collectionClass) {
if (collectionClass == List.class) {
return (CollectionInterface<C>) list();
}
else if (collectionClass == Set.class) {
return (CollectionInterface<C>) set();
}
else if (collectionClass == Map.class) {
return (CollectionInterface<C>) map();
}
if (collectionClass == SortedMap.class) {
return (CollectionInterface<C>) sortedMap();
}
else if (collectionClass == SortedSet.class) {
return (CollectionInterface<C>) sortedSet();
}
else if (collectionClass == Collection.class) {
return (CollectionInterface<C>) bag();
}
else {
throw new IllegalArgumentException("illegal collection interface type");
}
}
}

View File

@ -55,7 +55,7 @@ public class PersistentSortedMap<K,E> extends PersistentMap<K,E> implements Sort
*/
@Deprecated
public PersistentSortedMap(SessionImplementor session) {
this( (SharedSessionContractImplementor) session, (Comparator<K>) null );
this( session, (Comparator<K>) null );
}
/**

View File

@ -52,7 +52,7 @@ public class PersistentSortedSet<E> extends PersistentSet<E> implements SortedSe
*/
@Deprecated
public PersistentSortedSet(SessionImplementor session) {
this( (SharedSessionContractImplementor) session, (Comparator<E>) null );
this( session, (Comparator<E>) null );
}
/**

View File

@ -0,0 +1,101 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.collection.detached;
import org.hibernate.Hibernate;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@DomainModel(
annotatedClasses = {One.class, Many.class, Several.class}
)
@SessionFactory
public class DetachedCollectionTest {
@Test
public void testToOne(SessionFactoryScope scope) {
One one = new One();
scope.inTransaction( session -> session.persist(one) );
One two = Hibernate.createDetachedProxy( scope.getSessionFactory(), One.class, one.id );
assertEquals( one.id, two.getId() );
Many many = new Many();
many.one = two;
scope.inTransaction( session -> session.persist(many) );
scope.inTransaction( session -> {
Many m = session.find(Many.class, many.id);
assertNotNull(m.one);
assertEquals( one.id, m.one.id );
});
}
@Test
public void testOwned(SessionFactoryScope scope) {
Several several = new Several();
several.many = new HashSet<>();
Many many = new Many();
several.many.add(many);
assertNotNull(several.many);
scope.inTransaction(session -> {
session.persist(several);
session.persist(many);
});
assertNotNull(several.many);
several.many = Hibernate.<Many>set().createDetachedInstance();
assertFalse(Hibernate.isInitialized(several.many));
scope.inTransaction(session -> {
Several merged = (Several) session.merge(several);
assertNotNull(merged.many);
assertTrue(Hibernate.isInitialized(merged.many));
assertEquals(1, merged.many.size());
});
scope.inTransaction(session -> {
Several found = session.find(Several.class, several.id);
Hibernate.initialize(found.many);
assertNotNull(found.many);
assertEquals(1, found.many.size());
});
}
@Test
public void testUnowned(SessionFactoryScope scope) {
One one = new One();
one.many = new ArrayList<>();
Many many = new Many();
many.one = one;
one.many.add(many);
assertNotNull(one.many);
scope.inTransaction(session -> {
session.persist(one);
session.persist(many);
});
assertNotNull(one.many);
one.many = Hibernate.<Many>list().createDetachedInstance();
assertFalse(Hibernate.isInitialized(one.many));
scope.inTransaction(session -> {
One merged = (One) session.merge(one);
assertNotNull(merged.many);
assertTrue(Hibernate.isInitialized(merged.many));
assertEquals(1, merged.many.size());
});
scope.inTransaction(session -> {
One found = session.find(One.class, one.id);
Hibernate.initialize(found.many);
assertNotNull(found.many);
assertEquals(1, found.many.size());
});
}
}

View File

@ -0,0 +1,23 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.collection.detached;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity @Table(name="DCMany")
public class Many {
@GeneratedValue
@Id
long id;
@ManyToOne
One one;
}

View File

@ -0,0 +1,34 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.collection.detached;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.List;
@Entity @Table(name="DCOne")
public class One {
@GeneratedValue
@Id
long id;
@OneToMany(mappedBy = "one",
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
List<Many> many;
public long getId() {
return id;
}
}

View File

@ -0,0 +1,26 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.collection.detached;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.Set;
@Entity @Table(name="DCSeveral")
public class Several {
@GeneratedValue
@Id
long id;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
Set<Many> many;
}