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: 
> ...
132
demandé sur hennr 2008-11-13 18:15:23

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

171
répondu Thiago H. de Paula Figueiredo 2016-07-04 09:50:21

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.

29
répondu billjamesdev 2015-11-03 15:47:56

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.

15
répondu toolkit 2010-03-09 21:15:02

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;

9
répondu Anthony 2009-12-21 16:07:59

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>();


}
8
répondu 2008-12-24 11:38:39

Nous pouvons également utiliser ceci.

@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
6
répondu Jaimin Patel 2017-08-08 16:58:49

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.

5
répondu Inverce 2017-05-11 13:02:06

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.

4
répondu Jonck van der Kogel 2018-05-21 11:43:35

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.

1
répondu powers meat 2012-04-25 12:34:47

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 {
}
1
répondu Zia 2017-05-29 07:36:26