web-dev-qa-db-fra.com

Comment conserver une propriété de type List <String> dans JPA?

Quel est le moyen le plus intelligent d’obtenir la persistance d’une entité avec un champ de type Liste?

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

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: 
> ...
144
Andrea Francia

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, la mise en œuvre 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 l'implémentation de JPA2. Si vous utilisez JPA2, reportez-vous à la réponse ElementCollection ci-dessus:

Les listes d'objets à l'intérieur d'un objet de 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 elle-même) un client autorisé d'une relation un à plusieurs, car elle ne possède pas d'identifiant.

Ainsi, vous devriez convertir votre liste de chaînes en une liste d'objets JPA de classe argument contenant un identifiant et une chaîne. Vous pouvez potentiellement utiliser la chaîne comme identifiant, ce qui vous permettrait d'économiser un peu d'espace 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. (comme vous ne stockez aucune information de commande).

Vous pouvez également convertir votre liste en @Transient et ajouter un autre champ (argStorage) à votre classe, à savoir VARCHAR () ou CLOB. Vous devrez ensuite ajouter 3 fonctions: 2 d'entre elles sont identiques et doivent convertir votre liste de chaînes en une seule chaîne (dans argStorage) délimitée de manière à pouvoir les séparer facilement. Annotez ces deux fonctions (que chacune fasse la même chose) avec @PrePersist et @PreUpdate. Enfin, ajoutez la troisième fonction qui scinde à nouveau argStorage dans la liste des chaînes et annotez-la @PostLoad. Cela maintiendra votre CLOB à jour avec les chaînes chaque fois que vous allez stocker la commande et 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
billjamesdev

Désolé de faire revivre un ancien fil de discussion, mais si quelqu'un cherchait une solution alternative dans laquelle vous stockez vos listes de chaînes sous la forme d'un champ dans votre base de données, voici comment j'ai résolu ce problème. Créez 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 sous 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.

23

Selon Java Persistence with Hibernate

mappage de collections de types de valeur avec des annotations [...]. Au moment de la rédaction de ce document, il ne fait pas partie du standard de persistance Java.

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: Remarque, cette option est maintenant disponible dans JPA2.

15
toolkit

Lors de l'utilisation de l'implémentation Hibernate de JPA, j'ai constaté que le simple fait de déclarer le type sous la forme ArrayList au lieu de List permet à hibernate de stocker la liste de données.

Clairement, cela présente un certain nombre d'inconvénients par rapport à la création d'une liste d'objets Entity. Aucun chargement paresseux, aucune possibilité de référencer les entités de la liste à partir d'autres objets, peut-être plus de difficulté à construire des requêtes de base de données. Cependant, lorsque vous traitez avec des listes de types assez primitifs que vous voudrez toujours aller chercher avec l'entité, cette approche me semble correcte.

@Entity
public class Command implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;

    ArrayList<String> arguments = new ArrayList<String>();


}
9
pete_c

Nous pouvons également utiliser cela.

@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
9
Jaimin Patel

J'avais le même problème alors j'ai investi la solution possible donnée mais à la fin j'ai décidé de mettre en œuvre mon ';' liste séparée de String.

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/éditable dans la table de la base de données;

9
Anthony

Ok je sais que c'est un peu tard. Mais pour les âmes courageuses qui verront cela au fil du temps.

Comme écrit dans documentation :

@Basic: le type le plus simple de mappage sur 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 types primitifs, [...], énumérations et tout autre type implémentant Java.io.Serializable.

La partie importante est le type qui implémente Serializable

Donc, de loin la solution la plus simple et la plus facile à utiliser consiste simplement à utiliser ArrayList au lieu de List (ou de tout conteneur sérialisable):

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;

Rappelez-vous cependant que cela utilisera la sérialisation du système, ce qui entraînera des coûts, tels que:

  • si le modèle d'objet sérialisé change, vous ne pourrez peut-être pas restaurer les données.

  • une petite surcharge est ajoutée pour chaque élément stocké.

En bref

il est assez simple de stocker des drapeaux ou peu d’éléments, mais je ne le recommanderais pas pour stocker des données qui pourraient devenir volumineuses.

6
Inverce

La réponse de Thiago est correcte, ajouter un échantillon 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 (Strings, etc.) ou une collection d’éléments incorporables (classe annotée avec @ Embeddable).

Voici un exemple de liste de persistante de String

@ElementCollection
private Collection<String> options = new ArrayList<String>();

Voici l'exemple de liste de persistante de objet personnalisé

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

Pour ce cas, nous devons rendre la classe intégrable

@Embeddable
public class Car {
}
1
Zia

Mon correctif pour ce problème était de séparer la clé primaire avec la clé étrangère. Si vous utilisez Eclipse et avez apporté les modifications ci-dessus, n'oubliez pas d'actualiser l'explorateur de base de données. Recréez ensuite les entités à partir des tables.

1
powers meat

Il semble qu'aucune des réponses n'a exploré les paramètres les plus importants pour un mappage @ElementCollection .

Lorsque vous mappez une liste avec cette annotation et laissez JPA/Hibernate générer automatiquement les tables, les colonnes, etc., les noms générés automatiquement sont également utilisés.

Alors, analysons un exemple de base:

_@Entity
@Table(name = "sample")
public class MySample {

    @Id
    @GeneratedValue
    private Long id;

    @ElementCollection
    @CollectionTable(name = "my_list", joinColumns = @JoinColumn(name = "id"))
    @Column(name = "list")
    private List<String> list;

}
_
  1. L'annotation __ de base _@ElementCollection_ (où vous pouvez définir les préférences know fetch et targetClass)
  2. L'annotation @CollectionTable est très utile pour donner un nom à la table qui sera générée, ainsi que des définitions telles que joinColumns, foreignKey ', indexes, uniqueConstraints, etc.
  3. _@Column_ est important de définir le nom de la colonne qui stockera la valeur varchar de la liste.

La création DDL générée serait:

_-- table sample
CREATE TABLE sample (
  id bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (id)
);

-- table my_list
CREATE TABLE IF NOT EXISTS my_list (
  id bigint(20) NOT NULL,
  list varchar(255) DEFAULT NULL,
  FOREIGN KEY (id) REFERENCES sample (id)
);
_
0
bosco