JPA'da List <String> türünde bir özellik nasıl devam ettirilir?


158

Liste türünde bir alana sahip bir varlığı kalıcı hale getirmenin en akıllı yolu nedir?

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

Bu kod şunu üretir:

> 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: 
> ...

Yanıtlar:


197

Bazı JPA 2 uygulamalarını kullanın: Hazırda Bekleme uygulamasına benzer şekilde, tam olarak ihtiyacınız olanı yapan bir @ElementCollection ek açıklaması ekler. Burada bir örnek var .

Düzenle

Aşağıdaki yorumlarda belirtildiği gibi, doğru JPA 2 uygulaması

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;

Bkz. Http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html


1
Benim hatam da @ OneToMany ek açıklama eklemek oldu ... onu kaldırdıktan ve sadece @ ElementCollection ayrıldıktan sonra çalıştı
Willi Mentzel

47

Eski bir iş parçacığı canlandırmak için üzgünüm ama dize listelerinizi veritabanınızda bir alan olarak saklamak alternatif bir çözüm arıyor olmalı, işte bunu nasıl çözdü. Bunun gibi bir Dönüştürücü oluşturun:

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

Şimdi şu gibi Varlıklarınızda kullanın:

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

Veritabanında listeniz foo; bar; foobar olarak saklanır ve Java nesnenizde bu dizelerle bir liste alırsınız.

Umarım bu birileri için yararlıdır.


Sonuçları o alanın içeriğine göre filtrelemek için jpa havuzlarıyla çalışır mı?
Please_Dont_Bully_Me_SO_Lords

1
@Please_Dont_Bully_Me_SO_Lords Verileriniz veritabanında "foo; bar; foobar" olarak olacağından, bu kullanım durumu için daha az uygundur. Verileri sorgulamak istiyorsanız, muhtemelen bir ElementCollection + JoinTable durumunuza gitmek için bir yoldur.
Jonck van der Kogel

Bu ayrıca SPLIT_CHARdizenizde herhangi bir oluşum bulunamayacağı anlamına gelir .
Ezmek

@crush bu doğru. Elbette buna izin verebilirsiniz, örneğin dizenizi doğru bir şekilde sınırladıktan sonra kodlayarak. Ancak burada gönderdiğim çözüm öncelikle basit kullanım durumları içindir; daha karmaşık durumlar için muhtemelen bir ElementCollection + JoinTable ile daha iyi çalışacaksınız
Jonck van der Kogel

Lütfen kodunuzu düzeltin. Bunu 'kütüphane kodu' olarak görüyorum, bu yüzden savunma olmalı, örneğin en azından boş çekler olmalı
ZZ 5

30

Bu yanıt JPA2 öncesi uygulamalar olarak yapılmıştır, eğer JPA2 kullanıyorsanız yukarıdaki ElementCollection yanıtına bakınız:

Bir model nesnesinin içindeki nesnelerin listeleri genellikle başka bir nesneyle "OneToMany" ilişkileri olarak kabul edilir. Bununla birlikte, bir Dize, bir Kimliğe sahip olmadığı için Bire Çok ilişkisinin izin verilen bir istemcisi değildir.

Yani, gereken Argüman sınıf JPA listesine Strings listenizi dönüştürmek bir kimlik ve bir dize içeren nesnelerin. Dizeyi kimlik olarak potansiyel olarak kullanabilirsiniz; bu, tablonuzda hem kimlik alanını kaldırmadan hem de Dizelerin eşit olduğu satırları birleştirerek biraz yer tasarrufu sağlar, ancak bağımsız değişkenleri orijinal sıralarına geri sipariş etme yeteneğini kaybedersiniz. (sipariş bilgilerinizi saklamadığınız için).

Alternatif olarak, listenizi @Transient biçimine dönüştürebilir ve sınıfınıza VARCHAR () veya CLOB olan başka bir alan (argStorage) ekleyebilirsiniz. Daha sonra 3 işlev eklemeniz gerekir: Bunlardan 2'si aynıdır ve Dizeler listenizi kolayca ayırabileceğiniz bir şekilde ayrılmış tek bir String'e (argStorage'da) dönüştürmelidir. @PrePersist ve @PreUpdate ile bu iki işleve (her biri aynı şeyi yapan) açıklama ekleyin. Son olarak, argStorage öğesini ayıran üçüncü işlevi tekrar Dizeler listesine ekleyin ve @PostLoad öğesine not ekleyin. Bu, Komutu depolamaya gittiğinizde CLOB'nizi dizelerle güncel tutar ve DB'ye kaydetmeden önce argStorage alanını güncel tutar.

Hala ilk vakayı yapmanızı öneririm. Daha sonra gerçek ilişkiler için iyi bir uygulama.


ArrayList <String> 'den virgülle ayrılmış değerler içeren String'e geçiş benim için çalıştı.
Chris Dale

2
Ancak bu, söz konusu alanı sorgularken sizi çirkin ifadeler (imho) kullanmaya zorlar.
whiskeysierra

Evet, dediğim gibi ... ilk seçeneği yapın, daha iyi. Bunu yapmak için kendinizi getiremiyorsanız, seçenek 2 işe yarayabilir.
billjamesdev

15

Hazırda Bekletme ile Java Kalıcılığına Göre

değer türleri koleksiyonlarını ek açıklamalarla eşleme [...]. Yazma sırasında Java Persistence standardının bir parçası değil

Hazırda Bekletme özelliğini kullanıyorsanız, aşağıdakine benzer bir şey yapabilirsiniz:

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

Güncelleme: Not, bu artık JPA2'de mevcut.


12

Bunu da kullanabiliriz.

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

1
muhtemelen artı @ katılır.
phil294

10

Aynı problemi yaşadım ve verilen olası çözüme yatırım yaptım ama sonunda ';' ayrılmış String listesi.

bu yüzden sahibim

// a ; separated list of arguments
String arguments;

public List<String> getArguments() {
    return Arrays.asList(arguments.split(";"));
}

Bu şekilde liste veritabanı tablosunda kolayca okunabilir / düzenlenebilir;


1
Bu tamamen geçerlidir, ancak uygulamanızın büyümesini ve şema gelişimini düşünün. Gelecekte (yakın) bazen, tüzel kişilik temelli yaklaşıma geçebilirsiniz.
whiskeysierra

Katılıyorum, bu tamamen geçerli. Ancak, tamamen mantık yanı sıra kodun uygulanması gözden öneririz. Dize argumentserişim izinlerinin bir listesiyse, özel bir karaktere sahip olan a, separatorayrıcalık yükseltme saldırılarına karşı savunmasız olabilir.
Thang Pham

1
Bu gerçekten kötü bir tavsiyedir, dizeniz ;uygulamanızı bozacak içerik içerebilir .
agilob

9

JPA'nın Hazırda Bekletme uygulamasını kullanırken, türü Liste yerine ArrayList olarak bildirmenin hazırda bekletme modunun veri listesini depolamasına izin verdiğini fark ettim.

Açıkçası bu, Obje nesnelerinin bir listesini oluşturmaya kıyasla bir takım dezavantajlara sahiptir. Tembel yükleme yok, listedeki varlıklara diğer nesnelerden referans verme yeteneği yok, belki de veritabanı sorguları oluşturmada daha fazla zorluk. Ancak her zaman varlıkla birlikte hevesle getirmek isteyeceğiniz oldukça ilkel türlerin listelerini ele aldığınızda, bu yaklaşım benim için iyi görünüyor.

@Entity
public class Command implements Serializable {

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

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


}

2
Teşekkürler. Bu çalışma tüm JPA uygulamaları ile, Arraylist seri hale getirilebilir bir BLOB alanına kaydedilir. Bu yöntemle ilgili sorunlar 1) BLOB boyutu sabittir 2) dizi öğelerini arayabilir veya dizine ekleyebilir 3) yalnızca Java serileştirme formatından haberdar olan bir istemci bu öğeleri okuyabilir.
Andrea Francia

Bu yaklaşımı denerseniz , sunucu başlatma @OneToMany @ManyToOne @ElementCollectionkonusunda size bir Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElementsistisna verecektir . Çünkü hazırda bekletme, koleksiyon arabirimlerini kullanmanızı ister.
Paramvir Singh Karwal

9

Yanıtların hiçbirinin bir @ElementCollectionharitalama için en önemli ayarları keşfetmediği görülüyor .

Bu ek açıklama ile bir liste eşlediğinizde ve JPA / Hibernate'in tabloları, sütunları vb. Otomatik olarak oluşturmasına izin verdiğinizde, otomatik olarak oluşturulan adları da kullanır.

Şimdi temel bir örneği inceleyelim:

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

    @Id
    @GeneratedValue
    private Long id;

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

}
  1. Temel @ElementCollectionek açıklama (bilinen fetchve targetClasstercihleri tanımlayabileceğiniz )
  2. @CollectionTableBu oluşturulduktan olacak tabloya bir isim vermek, hem de böyle tanımlar gelince açıklama çok yararlıdır joinColumns, foreignKeys,' indexes, uniqueConstraintsvb
  3. @Columnvarcharlistenin değerini saklayacak sütunun adını tanımlamak önemlidir .

Oluşturulan DDL oluşturma şu şekildedir:

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

4
Bu çözümü beğendim, çünkü TABLO yapıları da dahil olmak üzere tam açıklama veren ve neden farklı ek açıklamalara ihtiyacımız olduğunu açıklayan tek çözümdür.
Julien Kronegg

6

Tamam, biraz geç olduğunu biliyorum. Ama bunu zaman geçtikçe görecek olan cesur ruhlar için.

Belgelerde yazıldığı gibi :

@Basic: Bir veritabanı sütununa en basit eşleme türü. Temel ek açıklama, aşağıdaki türlerden herhangi birinin kalıcı özelliğine veya örnek değişkenine uygulanabilir: Java ilkel türleri, [...], numaralandırmalar ve java.io.Serializable uygulayan diğer tüm türler.

Önemli olan, Serileştirilebilir

Şimdiye kadar en basit ve kullanımı en kolay çözüm, List (veya herhangi bir serileştirilebilir kap) yerine ArrayList kullanmaktır:

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;

Ancak bunun sistem serileştirmesini kullanacağını unutmayın, bu nedenle aşağıdaki gibi bir fiyatla gelecektir:

  • serileştirilmiş nesne modeli değişecekse, verileri geri yükleyemeyebilirsiniz

  • depolanan her eleman için küçük ek yük eklenir.

Kısacası

bayrakları veya birkaç öğeyi saklamak oldukça basit, ama büyük büyüyebilecek verileri saklamak için tavsiye etmem.


bunu denedim ama sql tablosu datatype bir tinyblob yaptı. Bu, dize listesine ekleme ve alma işlemlerini çok zor hale getirmiyor mu? Yoksa jpa sizin için otomatik olarak serileştiriyor ve serisini kaldırıyor mu?
Dzhao

3

Thiago yanıtı doğru, soruya daha spesifik örnek ekleyerek, @ElementCollection veritabanınızda yeni bir tablo oluşturacak, ancak iki tablo eşleştirmeden, Bu koleksiyonun bir varlık koleksiyonu değil, basit türlerin (Dizeler, vb. .) veya katıştırılabilir öğelerden oluşan bir koleksiyon ( @Embeddable ile açıklamalı sınıf ).

İşte Dize listesi kalıcı örnek

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

Özel nesne listesinde kalıcı örnek

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

Bu durumda sınıfı Embeddable yapmalıyız

@Embeddable
public class Car {
}

3

@Converter ve StringTokenizer kullanarak bir Setin saklanması için çözüm burada. @ Jonck-van-der-kogel çözümüne karşı biraz daha kontrol .

Varlık sınıfınızda:

@Convert(converter = StringSetConverter.class)
@Column
private Set<String> washSaleTickers;

StringSetConverter:

package com.model.domain.converters;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;

@Converter
public class StringSetConverter implements AttributeConverter<Set<String>, String> {
    private final String GROUP_DELIMITER = "=IWILLNEVERHAPPEN=";

    @Override
    public String convertToDatabaseColumn(Set<String> stringList) {
        if (stringList == null) {
            return new String();
        }
        return String.join(GROUP_DELIMITER, stringList);
    }

    @Override
    public Set<String> convertToEntityAttribute(String string) {
        Set<String> resultingSet = new HashSet<>();
        StringTokenizer st = new StringTokenizer(string, GROUP_DELIMITER);
        while (st.hasMoreTokens())
            resultingSet.add(st.nextToken());
        return resultingSet;
    }
}

1

Bu konudaki düzeltmem birincil anahtarı yabancı anahtarla ayırmaktı. Tutulma kullanıyorsanız ve yukarıdaki değişiklikleri yaptıysanız lütfen veritabanı gezginini yenilemeyi unutmayın. Sonra tablolardan varlıkları yeniden oluşturun.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.