java - example - ¿Cómo persistir una propiedad de tipo List<String> en JPA?
jpa save arraylist (9)
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<String> arguments = new ArrayList<String>();
}
¿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<String> arguments = new ArrayList<String>();
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:
> ...
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.
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<String> options = new ArrayList<String>();
Aquí está la muestra para persistir en la lista de objetos personalizados
@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
Para este caso, necesitamos hacer una clase Embebible
@Embeddable
public class Car {
}
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.
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 documentation :
@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<Color> lovedColors;
@Basic
ArrayList<String> 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.
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<String> arguments = new ArrayList<String>();
Actualización: Nota, esto ahora está disponible en JPA2.
También podemos usar esto.
@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
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<String> getArguments() {
return Arrays.asList(arguments.split(";"));
}
De esta manera, la lista es fácilmente legible / editable en la tabla de la base de datos;
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 here .
Editar
Como se menciona en los comentarios a continuación, la implementación correcta de JPA 2 es
javax.persistence.ElementCollection
@ElementCollection
Map<Key, Value> collection;
Ver: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html