proposed API for instantiation of detached collections + proxies
this is useful for object-graph deserialization
This commit is contained in:
parent
6fbb90aa54
commit
863802cfd0
|
@ -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<User></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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue