diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index c80dd078c4..9b95a122e1 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -663,6 +663,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern The collection can also name a <>, which defines the Hibernate Type of the collection elements. +See the <> chapter for more info. + [[annotations-hibernate-columndefault]] ==== `@ColumnDefault` diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 8a332abbe5..3eeaf9096b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -626,3 +626,46 @@ include::{extrasdir}/collections-comma-delimited-collection-lifecycle-example.sq ==== See the Hibernate Integrations Guide for more details on developing custom value type mappings. + +[[collections-custom]] +==== Custom collection types + +If you wish to use other collection types than `List`, `Set` or ``Map`, like `Queue` for instance, +you have to use a custom collection type, as illustrated by the following example: + +[[collections-custom-collection-mapping-example]] +.Custom collection mapping example +==== +[source,java] +---- +include::{sourcedir}/QueueTest.java[tags=collections-custom-collection-mapping-example,indent=0] + +include::{sourcedir}/type/QueueType.java[tags=collections-custom-collection-mapping-example,indent=0] + +include::{sourcedir}/type/PersistentQueue.java[tags=collections-custom-collection-mapping-example,indent=0] +---- +==== + +[NOTE] +==== +The reason why the `Queue` interface is not used for the entity attribute is because Hibernate only allows the following types: + +- `java.util.List` +- `java.util.Set` +- `java.util.Map` +- `java.util.SortedSet` +- `java.util.SortedMap` + +However, the custom collection type can still be customized as long as the base type is one of the aformentioned persistent types. +==== + +This way, the `Phone` collection can be used as a `java.util.Queue`: + +[[collections-custom-collection-example]] +.Custom collection example +==== +[source,java] +---- +include::{sourcedir}/QueueTest.java[tags=collections-custom-collection-example,indent=0] +---- +==== diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java new file mode 100644 index 0000000000..72f909f402 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java @@ -0,0 +1,158 @@ +/* + * 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 . + */ +package org.hibernate.userguide.collections; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Queue; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.CollectionType; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * @author Vlad Mihalcea + */ +public class QueueTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( QueueTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class, + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( 1L ); + person.getPhones().add( new Phone( 1L, "landline", "028-234-9876" ) ); + person.getPhones().add( new Phone( 2L, "mobile", "072-122-9876" ) ); + entityManager.persist( person ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-custom-collection-example[] + Person person = entityManager.find( Person.class, 1L ); + Queue phones = person.getPhones(); + Phone head = phones.peek(); + assertSame(head, phones.poll()); + assertEquals( 1, phones.size() ); + //end::collections-custom-collection-example[] + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, 1L ); + person.getPhones().clear(); + } ); + } + + //tag::collections-custom-collection-mapping-example[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + @OneToMany(cascade = CascadeType.ALL) + @CollectionType( type = "org.hibernate.userguide.collections.type.QueueType") + private Collection phones = new LinkedList<>(); + + //Getters and setters are omitted for brevity + + //end::collections-custom-collection-mapping-example[] + + public Person() { + } + + public Person(Long id) { + this.id = id; + } + + public Queue getPhones() { + return (Queue) phones; + } + //tag::collections-custom-collection-mapping-example[] + } + + @Entity(name = "Phone") + public static class Phone implements Comparable { + + @Id + private Long id; + + private String type; + + @NaturalId + @Column(name = "`number`") + private String number; + + //Getters and setters are omitted for brevity + + //end::collections-custom-collection-mapping-example[] + + public Phone() { + } + + public Phone(Long id, String type, String number) { + this.id = id; + this.type = type; + this.number = number; + } + + public Long getId() { + return id; + } + + public String getType() { + return type; + } + + public String getNumber() { + return number; + } + + @Override + public int compareTo(Phone o) { + return number.compareTo( o.getNumber() ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Phone phone = (Phone) o; + return Objects.equals( number, phone.number ); + } + + @Override + public int hashCode() { + return Objects.hash( number ); + } + //tag::collections-custom-collection-mapping-example[] + } + //end::collections-custom-collection-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/type/PersistentQueue.java b/documentation/src/test/java/org/hibernate/userguide/collections/type/PersistentQueue.java new file mode 100644 index 0000000000..4ff37611e3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/type/PersistentQueue.java @@ -0,0 +1,58 @@ +/* + * 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 . + */ +package org.hibernate.userguide.collections.type; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; + +import org.hibernate.collection.internal.PersistentBag; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +//tag::collections-custom-collection-mapping-example[] +public class PersistentQueue extends PersistentBag implements Queue { + + public PersistentQueue(SharedSessionContractImplementor session) { + super( session ); + } + + public PersistentQueue(SharedSessionContractImplementor session, List list) { + super( session, list ); + } + + @Override + public boolean offer(Object o) { + return add(o); + } + + @Override + public Object remove() { + return poll(); + } + + @Override + public Object poll() { + int size = size(); + if(size > 0) { + Object first = get(0); + remove( 0 ); + return first; + } + throw new NoSuchElementException(); + } + + @Override + public Object element() { + return peek(); + } + + @Override + public Object peek() { + return size() > 0 ? get( 0 ) : null; + } +} +//end::collections-custom-collection-mapping-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/type/QueueType.java b/documentation/src/test/java/org/hibernate/userguide/collections/type/QueueType.java new file mode 100644 index 0000000000..03ff399856 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/type/QueueType.java @@ -0,0 +1,75 @@ +/* + * 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 . + */ +package org.hibernate.userguide.collections.type; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +import org.hibernate.HibernateException; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.usertype.UserCollectionType; + +//tag::collections-custom-collection-mapping-example[] +public class QueueType implements UserCollectionType { + + @Override + public PersistentCollection instantiate( + SharedSessionContractImplementor session, + CollectionPersister persister) throws HibernateException { + return new PersistentQueue( session ); + } + + @Override + public PersistentCollection wrap( + SharedSessionContractImplementor session, + Object collection) { + return new PersistentQueue( session, (List) collection ); + } + + @Override + public Iterator getElementsIterator(Object collection) { + return ( (Queue) collection ).iterator(); + } + + @Override + public boolean contains(Object collection, Object entity) { + return ( (Queue) collection ).contains( entity ); + } + + @Override + public Object indexOf(Object collection, Object entity) { + int i = ( (List) collection ).indexOf( entity ); + return ( i < 0 ) ? null : i; + } + + @Override + public Object replaceElements( + Object original, + Object target, + CollectionPersister persister, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) + throws HibernateException { + Queue result = (Queue) target; + result.clear(); + result.addAll( (Queue) original ); + return result; + } + + @Override + public Object instantiate(int anticipatedSize) { + return new LinkedList<>(); + } + +} +//end::collections-custom-collection-mapping-example[]