Comment conserver une propriété de type List dans JPA?
Quel est le moyen le plus intelligent d'obtenir une entité avec un champ de type List persistant?
Commande.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);
}
}
Ce code produit:
> 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:
> ...
10 réponses
Utilisez une implémentation JPA 2: elle ajoute une annotation @ElementCollection, similaire à celle D'Hibernate, qui fait exactement ce dont vous avez besoin. Il y a un exemple ici.
Modifier
Comme mentionné dans les commentaires ci-dessous, l'implémentation correcte de JPA 2 est
javax.persistence.ElementCollection
@ElementCollection
Map<Key, Value> collection;
Voir: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html
Cette réponse a été faite avant les implémentations JPA2, si vous utilisez JPA2, voir la réponse ElementCollection ci-dessus:
Les listes d'objets à l'intérieur d'un objet modèle sont généralement considérées comme des relations "OneToMany" avec un autre objet. Cependant, une chaîne n'est pas (en soi) un client admissible d'une relation un à plusieurs, car elle n'a pas D'ID.
Donc, vous devriez convertir votre liste de Chaînes en une liste d'objets JPA de classe D'arguments contenant un ID et une chaîne. Vous pourrait potentiellement utiliser la chaîne comme ID, ce qui économiserait un peu d'espace dans votre table à la fois en supprimant le champ ID et en consolidant les lignes où les chaînes sont égales, mais vous perdriez la possibilité de réorganiser les arguments dans leur ordre d'origine (car vous n'avez stocké aucune information de commande).
Alternativement, vous pouvez convertir votre liste en @Transient et Ajouter un autre champ (argStorage) à votre classe qui est soit un VARCHAR() ou un CLOB. Vous devrez ensuite ajouter 3 fonctions: 2 d'entre eux sont les mêmes et devraient convertir votre liste de Chaînes en une seule chaîne (dans argStorage) délimitée d'une manière que vous pouvez facilement les séparer. Annotez ces deux fonctions (qui font chacune la même chose) avec @PrePersist et @PreUpdate. Enfin, ajoutez la troisième fonction qui divise à nouveau l'argStorage dans la liste des chaînes et annotez-la @PostLoad. Cela gardera votre CLOB mis à jour avec les chaînes chaque fois que vous allez stocker la commande, et conservera le champ argStorage mis à jour avant de le stocker dans la base de données.
Je suggère toujours de faire le premier cas. C'est une bonne pratique pour de vraies relations plus tard.
Selon persistance Java avec Hibernate
Mappage des collections de types de valeurs avec des annotations [...]. Au moment de l'écriture, il ne fait pas partie de la norme Java Persistence
Si vous utilisiez Hibernate, vous pourriez faire quelque chose comme:
@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>();
Mise à jour: notez, ceci est maintenant disponible en JPA2.
J'ai eu le même problème donc j'ai investi la solution possible donnée mais à la fin j'ai décidé d'implémenter ma liste de chaîne séparée';'.
Donc j'ai
// a ; separated list of arguments
String arguments;
public List<String> getArguments() {
return Arrays.asList(arguments.split(";"));
}
De cette façon, la liste est facilement lisible/modifiable dans la table de base de données;
Lors de l'utilisation de L'implémentation Hibernate de JPA , j'ai constaté que le simple fait de déclarer le type comme ArrayList au lieu de List permet à hibernate de stocker la liste de données.
Cela présente clairement un certain nombre d'inconvénients par rapport à la création d'une liste d'objets D'entité. Pas de chargement paresseux, pas de possibilité de référencer les entités dans la liste à partir d'autres objets, peut-être plus de difficulté à construire des requêtes de base de données. Cependant lorsque vous avez affaire à des listes de types assez primitifs que vous voudra toujours chercher avec impatience avec l'entité, alors cette approche me semble bien.
@Entity
public class Command implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
ArrayList<String> arguments = new ArrayList<String>();
}
Nous pouvons également utiliser ceci.
@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
Ok, je sais que c'est un peu tard. Mais pour ces âmes courageuses qui verront cela comme le temps passe.
Comme écrit dans documentation:
@ Basic: Le type le plus simple de mappage à une colonne de base de données. L'annotation de base peut être appliquée à une propriété persistante ou une variable d'instance de l'un des types suivants: Java primitive types, [...], enums, et tout autre type qui implémente java. io. Serializable.
La partie importante est le type qui implémente Sérialisable
Donc, de loin la solution la plus simple et la plus facile à utiliser est simplement D'utiliser ArrayList au lieu de List (ou tout conteneur sérialisable):
@Basic
ArrayList<Color> lovedColors;
@Basic
ArrayList<String> catNames;
Rappelez-vous cependant que cela utilisera la sérialisation du système, donc il viendra avec un certain prix, tels que:
-
Si le modèle d'objet sérialisé change, il se peut que vous ne puissiez pas restaurer les données
Une petite surcharge est ajoutée pour chaque élément stocké.
En bref
C'est assez simple pour stocker des drapeaux ou quelques éléments, mais je ne le ferais pas recommandez-le pour stocker des données qui pourraient grossir.
Désolé de faire revivre un vieux thread mais si quelqu'un cherchait une solution alternative où vous stockez vos listes de chaînes comme un champ dans votre base de données, voici comment j'ai résolu cela. Créer un convertisseur comme ceci:
import java.util.Arrays;
import java.util.List;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final String SPLIT_CHAR = ";";
@Override
public String convertToDatabaseColumn(List<String> stringList) {
return String.join(SPLIT_CHAR, stringList);
}
@Override
public List<String> convertToEntityAttribute(String string) {
return Arrays.asList(string.split(SPLIT_CHAR));
}
}
Maintenant, utilisez-le sur vos entités comme ceci:
@Convert(converter = StringListConverter.class)
private List<String> yourList;
Dans la base de données, votre liste sera stockée comme foo; bar; foobar et dans votre objet Java, vous obtiendrez une liste avec ces chaînes.
J'espère que cela est utile à quelqu'un.
Mon correctif pour ce problème était de séparer la clé primaire avec la clé étrangère. Si vous utilisez eclipse et que vous avez apporté les modifications ci-dessus, n'oubliez pas d'actualiser l'Explorateur de base de données. Puis recréez les entités à partir des tables.
La réponse de Thiago est correcte, en ajoutant un exemple plus spécifique à la question, @ElementCollection créera une nouvelle table dans votre base de données, mais sans mapper deux tables, cela signifie que la collection n'est pas une collection d'entités, mais une collection de types simples (chaînes,etc.) ou une collection d'éléments intégrables (classe annotée avec @Embeddable).
Voici l'exemple à conserver liste de Chaîne
@ElementCollection
private Collection<String> options = new ArrayList<String>();
Voici la liste des exemples à conserver objet Personnalisé
@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
, Pour ce cas, nous devons faire en classe Intégrable
@Embeddable
public class Car {
}