¿Cómo persistir una propiedad de tipo List en JPA?

¿Cuál es la forma más inteligente de obtener una entidad con un campo de tipo Lista persistida?

Command.java

package persistlistofstring; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.EntityManager; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Persistence; @Entity public class Command implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) Long id; @Basic List arguments = new ArrayList(); public static void main(String[] args) { Command command = new Command(); EntityManager em = Persistence .createEntityManagerFactory("pu") .createEntityManager(); em.getTransaction().begin(); em.persist(command); em.getTransaction().commit(); em.close(); System.out.println("Persisted with id=" + command.id); } } 

Este código produce:

 > Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named pu: Provider named oracle.toplink.essentials.PersistenceProvider threw unexpected exception at create EntityManagerFactory: > oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException > Local Exception Stack: > Exception [TOPLINK-30005] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException > Exception Description: An exception was thrown while searching for persistence archives with ClassLoader: sun.misc.Launcher$AppClassLoader@11b86e7 > Internal Exception: javax.persistence.PersistenceException: Exception [TOPLINK-28018] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.EntityManagerSetupException > Exception Description: predeploy for PersistenceUnit [pu] failed. > Internal Exception: Exception [TOPLINK-7155] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.ValidationException > Exception Description: The type [interface java.util.List] for the attribute [arguments] on the entity class [class persistlistofstring.Command] is not a valid type for a serialized mapping. The attribute type must implement the Serializable interface. > at oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(PersistenceUnitLoadingException.java:143) > at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.createEntityManagerFactory(EntityManagerFactoryProvider.java:169) > at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:110) > at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83) > at persistlistofstring.Command.main(Command.java:30) > Caused by: > ... 

Utilice alguna implementación de JPA 2: agrega una anotación @ElementCollection, similar a la de Hibernate, que hace exactamente lo que necesita. Hay un ejemplo aquí .

Editar

Como se menciona en los comentarios a continuación, la implementación correcta de JPA 2 es

 javax.persistence.ElementCollection @ElementCollection Map collection; 

Ver: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html

Esta respuesta se realizó antes de las implementaciones JPA2, si está utilizando JPA2, consulte la respuesta ElementCollection anterior:

Las listas de objetos dentro de un objeto modelo generalmente se consideran relaciones “OneToMany” con otro objeto. Sin embargo, una Cadena no es (por sí misma) un cliente permitido de una relación de Uno a Muchos, ya que no tiene una ID.

Por lo tanto, debe convertir su lista de cadenas a una lista de objetos JPA de clase argumental que contengan una ID y una cadena. Podría utilizar potencialmente la Cadena como ID, lo que ahorraría un poco de espacio en su tabla tanto al eliminar el campo ID como al consolidar filas donde las Cadenas son iguales, pero perdería la capacidad de ordenar los argumentos nuevamente en su orden original. (ya que no almacenó ninguna información de pedido).

Alternativamente, podría convertir su lista en @Transient y agregar otro campo (argStorage) a su clase que sea VARCHAR () o CLOB. Luego deberá agregar 3 funciones: 2 de ellas son iguales y debe convertir su lista de Cadenas en una sola Cadena (en argStorage) delimitada de manera tal que pueda separarlas fácilmente. Anota estas dos funciones (que cada una haga lo mismo) con @PrePersist y @PreUpdate. Finalmente, agregue la tercera función que divide el argStorage en la lista de cadenas nuevamente y anótelo @PostLoad. Esto mantendrá su CLOB actualizado con las cadenas cada vez que vaya a almacenar el Comando, y mantendrá el campo argStorage actualizado antes de almacenarlo en el DB.

Todavía sugiero hacer el primer caso. Es una buena práctica para relaciones reales después.

Según Java Persistence con Hibernate

mapeo de colecciones de tipos de valores con anotaciones […]. En el momento de escribir esto, no es parte del estándar de Java Persistence

Si estuviera usando Hibernate, podría hacer algo como:

 @org.hibernate.annotations.CollectionOfElements( targetElement = java.lang.String.class ) @JoinTable( name = "foo", joinColumns = @JoinColumn(name = "foo_id") ) @org.hibernate.annotations.IndexColumn( name = "POSITION", base = 1 ) @Column(name = "baz", nullable = false) private List arguments = new ArrayList(); 

Actualización: Nota, esto ahora está disponible en JPA2.

Tuve el mismo problema, así que invertí la posible solución, pero al final decidí implementar mi ‘;’ lista separada de String.

así que tengo

 // a ; separated list of arguments String arguments; public List getArguments() { return Arrays.asList(arguments.split(";")); } 

De esta manera, la lista es fácilmente legible / editable en la tabla de la base de datos;

Cuando uso la implementación Hibernate de JPA, descubrí que simplemente declarar el tipo como ArrayList en lugar de List permite que hibernate almacene la lista de datos.

Claramente, esto tiene una serie de desventajas en comparación con la creación de una lista de objetos Entity. Sin carga diferida, sin capacidad para hacer referencia a las entidades en la lista desde otros objetos, tal vez más dificultad en la construcción de consultas a la base de datos. Sin embargo, cuando se trata de listas de tipos bastante primitivos que siempre querrá buscar ansiosamente junto con la entidad, entonces este enfoque me parece bien.

 @Entity public class Command implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) Long id; ArrayList arguments = new ArrayList(); } 

También podemos usar esto.

 @Column(name="arguments") @ElementCollection(targetClass=String.class) private List arguments; 

Ok, sé que es un poco tarde. Pero para esas almas valientes que verán esto a medida que pase el tiempo.

Como está escrito en la documentación :

@Basic: el tipo más simple de asignación a una columna de base de datos. La anotación Básica se puede aplicar a una propiedad persistente o variable de instancia de cualquiera de los siguientes tipos: tipos primitivos de Java, […], enumeraciones y cualquier otro tipo que implemente java.io.Serializable.

La parte importante es el tipo que implementa Serializable

Así que, con mucho, la solución más simple y fácil de usar es simplemente usar ArrayList en lugar de List (o cualquier contenedor serializable):

 @Basic ArrayList lovedColors; @Basic ArrayList catNames; 

Sin embargo, recuerde que esto utilizará la serialización del sistema, por lo que vendrá con algún precio, como por ejemplo:

  • si el modelo de objeto serializado cambiara, es posible que no pueda restaurar los datos

  • Se agrega una pequeña sobrecarga por cada elemento almacenado.

En breve

es bastante simple almacenar banderas o pocos elementos, pero no lo recomendaría para almacenar datos que puedan crecer a gran escala.

Perdón por revivir un hilo viejo, pero si alguien está buscando una solución alternativa donde almacene sus listas de cadenas como un solo campo en su base de datos, así es como lo resolví. Crea un convertidor como este:

 import java.util.Arrays; import java.util.List; import javax.persistence.AttributeConverter; import javax.persistence.Converter; @Converter public class StringListConverter implements AttributeConverter, String> { private static final String SPLIT_CHAR = ";"; @Override public String convertToDatabaseColumn(List stringList) { return String.join(SPLIT_CHAR, stringList); } @Override public List convertToEntityAttribute(String string) { return Arrays.asList(string.split(SPLIT_CHAR)); } } 

Ahora úsalo en tus Entidades así:

 @Convert(converter = StringListConverter.class) private List yourList; 

En la base de datos, su lista se almacenará como foo; bar; foobar y en su objeto Java obtendrá una lista con esas cadenas.

Espero que esto sea útil para alguien.

Mi solución para este problema fue separar la clave principal con la clave externa. Si está utilizando eclipse e hizo los cambios anteriores, recuerde actualizar el explorador de la base de datos. Luego recrea las entidades de las tablas.

La respuesta de Thiago es correcta, al agregar una muestra más específica a la pregunta, @ElementCollection creará una nueva tabla en su base de datos, pero sin mapear dos tablas, significa que la colección no es una colección de entidades, sino una colección de tipos simples (cadenas, etc. .) o una colección de elementos incrustables (clase anotada con @Embeddable ).

Aquí está la muestra para persistir la lista de String

 @ElementCollection private Collection options = new ArrayList(); 

Aquí está la muestra para persistir en la lista de objetos personalizados

 @Embedded @ElementCollection private Collection carList = new ArrayList(); 

Para este caso, necesitamos hacer una clase Embebible

 @Embeddable public class Car { }