簡體   English   中英

如何持久化 List 類型的屬性<String>在 JPA 中?

[英]How to persist a property of type List<String> in JPA?

獲取具有 List 類型字段的實體的最聰明方法是什么?

命令.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);
    }
}

此代碼產生:

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

使用一些 JPA 2 實現:它添加了一個 @ElementCollection 注釋,類似於 Hibernate 注釋,這正是您所需要的。 有一個例子 在這里

編輯

正如下面的評論中提到的,正確的 JPA 2 實現是

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;

請參閱: http : //docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html

很抱歉恢復舊線程,但如果有人正在尋找替代解決方案,將字符串列表存儲為數據庫中的一個字段,這就是我解決的方法。 像這樣創建一個轉換器:

import java.util.Arrays;
import java.util.List;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import static java.util.Collections.*;

@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
    private static final String SPLIT_CHAR = ";";
    
    @Override
    public String convertToDatabaseColumn(List<String> stringList) {
        return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
    }

    @Override
    public List<String> convertToEntityAttribute(String string) {
        return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
    }
}

現在在您的實體上使用它,如下所示:

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

在數據庫中,您的列表將存儲為foo;bar;foobar ,而在您的 Java 對象中,您將獲得包含這些字符串的列表。

希望這對某人有幫助。

似乎沒有一個答案探討了@ElementCollection映射的最重要設置。

當您使用此注釋映射列表並讓 JPA/Hibernate 自動生成表、列等時,它也會使用自動生成的名稱。

那么,讓我們分析一個基本的例子:

@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. 基本的@ElementCollection注釋(您可以在其中定義已知的fetchtargetClass首選項)
  2. @CollectionTable注釋在為將要生成的表命名以及像joinColumnsforeignKey的、 indexesuniqueConstraints等定義時非常有用。
  3. @Column對於定義將存儲列表的varchar值的列的名稱很重要。

生成的 DDL 創建將是:

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

這個答案是在 JPA2 之前實現的,如果您使用的是 JPA2,請參閱上面的 ElementCollection 答案:

模型對象內的對象列表通常被視為與另一個對象的“一對多”關系。 但是,字符串本身不是一對多關系的允許客戶端,因為它沒有 ID。

因此,您應該將字符串列表轉換為包含 ID 和字符串的 Argument 類 JPA 對象列表。 您可能會使用字符串作為 ID,這樣可以通過刪除 ID 字段和合並字符串相等的行在表中節省一點空間,但您將無法將參數重新排序為原始順序(因為您沒有存儲任何訂購信息)。

或者,您可以將列表轉換為 @Transient 並將另一個字段 (argStorage) 添加到您的類中,該字段是 VARCHAR() 或 CLOB。 然后,您需要添加 3 個函數:其中 2 個是相同的,並且應該將您的字符串列表轉換為單個字符串(在 argStorage 中),以您可以輕松地將它們分開的方式分隔。 用@PrePersist 和@PreUpdate 注釋這兩個函數(每個函數都做同樣的事情)。 最后,再次添加將 argStorage 拆分為 Strings 列表的第三個函數,並將其注釋為 @PostLoad。 每當您去存儲命令時,這將使您的 CLOB 與字符串保持更新,並在將其存儲到數據庫之前保持更新 argStorage 字段。

我仍然建議做第一種情況。 這是以后建立真正關系的好習慣。

我們也可以使用這個。

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

根據Java Persistence with Hibernate

使用注釋映射值類型的集合 [...]。 在撰寫本文時,它不是 Java Persistence 標准的一部分

如果您使用的是 Hibernate,您可以執行以下操作:

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

更新:注意,這現在在 JPA2 中可用。

當使用 JPA 的 Hibernate 實現時,我發現簡單地將類型聲明為 ArrayList 而不是 List 允許 hibernate 存儲數據列表。

顯然,與創建實體對象列表相比,這有許多缺點。 沒有延遲加載,無法從其他對象引用列表中的實體,可能在構建數據庫查詢時更加困難。 然而,當您處理相當原始類型的列表時,您總是希望與實體一起急切地獲取,那么這種方法對我來說似乎很好。

@Entity
public class Command implements Serializable {

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

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


}

我遇到了同樣的問題,所以我投資了給出的可能解決方案,但最后我決定實施我的“;” 分隔的字符串列表。

所以我有

// a ; separated list of arguments
String arguments;

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

這樣,列表在數據庫表中很容易閱讀/編輯;

好吧,我知道它有點晚了。 但對於那些隨着時間的推移會看到這一點的勇敢的靈魂。

正如文檔中所寫:

@Basic:映射到數據庫列的最簡單類型。 Basic 注釋可以應用於以下任何類型的持久屬性或實例變量:Java 原始類型、[...]、枚舉和任何其他實現 java.io.Serializable 的類型。

重要的部分是實現 Serializable 的類型

所以到目前為止,最簡單和最容易使用的解決方案是簡單地使用 ArrayList 而不是 List(或任何可序列化的容器):

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;

但是請記住,這將使用系統序列化,因此它會帶來一些代價,例如:

  • 如果序列化對象模型會改變,你可能無法恢復數據

  • 為存儲的每個元素添加少量開銷。

簡而言之

存儲標志或少量元素非常簡單,但我不建議它存儲可能會變大的數據。

這是使用 @Converter 和 StringTokenizer 存儲 Set 的解決方案。 @jonck-van-der-kogel解決方案進行更多檢查。

在您的實體類中:

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

字符串集轉換器:

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

蒂亞戈的答案是正確的,添加更具體問題的示例,@ ElementCollection將在您的數據庫中創建新表,但不映射兩個表,這意味着該集合不是實體集合,而是簡單類型(字符串等)的集合.) 或可嵌入元素的集合(用@Embeddable注釋的類)。

這是持久化字符串列表的示例

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

這是保存自定義對象列表的示例

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

對於這種情況,我們需要使類Embeddable

@Embeddable
public class Car {
}

我對此問題的解決方法是將主鍵與外鍵分開。 如果您正在使用 eclipse 並進行了上述更改,請記住刷新數據庫資源管理器。 然后從表中重新創建實體。

我想要的是一種在表列中保留一組字符串的簡單方法。

我最終使用了 JSON,因為 MySQL 5.7+ 具有本機支持。 這是我的解決方案

    @Column(name = "eligible_approvers", columnDefinition = "json")
    @Convert(converter = ArrayJsonConverter.class)
    private Set<String> eligibleApprovers;

然后寫一個非常基本的轉換器

@Converter(autoApply = true)
public class ArrayJsonConverter implements AttributeConverter<Set, String> {

    static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Set list) {
        if (list == null)
            return null;
        try {
            return mapper.writeValueAsString(list);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public Set convertToEntityAttribute(String dbJson) {
        if (dbJson == null)
            return null;
        try {
            return mapper.readValue(dbJson, new TypeReference<Set<String>>() {
            });
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

由於我的聲譽還不足以評論@razvang 寫的被低估的答案:

正如十多年前提出的這個問題一樣,請記住,從那時起,世界的大部分地區都發生了變化。 我們現在擁有支持原生 JSON 列的數據庫,並且可以使用此功能而不是使用其他答案使用的單獨實體、連接或自定義字符串到列表轉換器。

不過,讓我建議對@razvang 的出色答案進行兩個純粹可選的更改,根據您的具體情況,這可能會很有趣:

  1. 您可以省略auto_apply = true並將@Convert(converter = <CONVERTER_CLASS_NAME>.class)到實體字段以控制何時使用轉換器。
  2. 不是在轉換失敗時拋出RuntimeException ,而是可以在那里處理錯誤(例如傳遞一個空列表並寫入日志消息)以使其稍微優雅地失敗。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM