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

And*_*cia 147 java orm jpa

获得具有List类型字段的实体的最智能方法是什么?

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码生成:

> 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: 
> ...
Run Code Online (Sandbox Code Playgroud)

小智 187

使用一些JPA 2实现:它增加了一个@ElementCollection注释,类似于Hibernate的一个,这不正是你需要的.有一个例子在这里.

编辑

正如下面的评论中所提到的,正确的JPA 2实现是

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;
Run Code Online (Sandbox Code Playgroud)

请参阅:http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html

  • 我的错误是还添加了 @OneToMany 注释...删除它并保留 @ElementCollection 后它就起作用了 (2认同)

bos*_*sco 33

似乎没有一个答案探讨了@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;
    
}
Run Code Online (Sandbox Code Playgroud)
  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)
);
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案,因为它是唯一提出的解决方案,它给出了包括表结构在内的完整描述,并解释了为什么我们需要不同的注释。 (9认同)

bil*_*dev 29

这个答案是在JPA2之前的实现中做出的,如果您使用的是JPA2,请参阅上面的ElementCollection答案:

模型对象内的对象列表通常被视为与另一个对象的"OneToMany"关系.但是,String不是(本身)一对多关系的允许客户端,因为它没有ID.

因此,您应该将您的Strings列表转换为包含ID和String的Argument-class JPA对象列表.您可以使用String作为ID,这样可以节省表中的一小部分空间,既可以删除ID字段,又可以合并字符串相等的行,但是您将失去将参数重新排序为原始顺序的能力(因为您没有存储任何订购信息).

或者,您可以将列表转换为@Transient,并将另一个字段(argStorage)添加到您的类中,该类是VARCHAR()或CLOB.然后,您需要添加3个函数:其中2个是相同的,并且应该将您的字符串列表转换为单个字符串(在argStorage中),以一种您可以轻松分隔它们的方式分隔.使用@PrePersist和@PreUpdate注释这两个函数(每个函数都做同样的事情).最后,添加第三个函数,将argStorage再次拆分为字符串列表,并将其注释为@PostLoad.每当您存储命令时,这将使您的CLOB更新为字符串,并在将argStorage字段存储到数据库之前保持更新.

我仍然建议做第一个案例.以后这是真正的关系的好习惯.

  • 但这会迫使你在查询那个字段时使用(imho)丑陋的语句. (2认同)

Jon*_*gel 28

很抱歉重振旧线程,但是任何人都应该寻找替代解决方案,将字符串列表存储为数据库中的一个字段,以下是我如何解决这个问题.像这样创建一个转换器:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在在您的实体上使用它,如下所示:

@Convert(converter = StringListConverter.class)
private List<String> yourList;
Run Code Online (Sandbox Code Playgroud)

在数据库中,您的列表将存储为foo; bar; foobar,在Java对象中,您将获得包含这些字符串的列表.

希望这对某人有帮助.

  • @Please_Dont_Bully_Me_SO_Lords 它不太适合该用例,因为您的数据将在数据库中作为“foo;bar;foobar”。如果您想查询数据,那么 ElementCollection + JoinTable 可能是适合您情况的方法。 (3认同)
  • 这也意味着您的字符串中不能出现任何“SPLIT_CHAR”。 (2认同)

too*_*kit 15

根据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>();
Run Code Online (Sandbox Code Playgroud)

更新:请注意,现在可以在JPA2中使用.


小智 11

我们也可以用这个.

@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
Run Code Online (Sandbox Code Playgroud)


小智 9

当使用JPA的Hibernate实现时,我发现简单地将类型声明为ArrayList而不是List允许hibernate存储数据列表.

显然,与创建实体对象列表相比,这有许多缺点.没有延迟加载,没有能力从其他对象引用列表中的实体,可能在构造数据库查询时更加困难.但是当你处理相当原始类型的列表时,你总是希望与实体一起急切地获取,那么这种方法对我来说似乎很好.

@Entity
public class Command implements Serializable {

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

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


}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢.这适用于所有JPA实现,Arraylist是Serializable,保存在BLOB字段中.这种方法的问题是:1)BLOB大小是固定的2)您可以搜索或索引数组元素3)只有知道Java序列化格式的客户端才能读取这些元素. (2认同)

Ant*_*ony 9

我有同样的问题,所以我投入了可能的解决方案,但最后我决定实施我的';' 分隔的String列表.

所以我有

// a ; separated list of arguments
String arguments;

public List<String> getArguments() {
    return Arrays.asList(arguments.split(";"));
}
Run Code Online (Sandbox Code Playgroud)

这样,列表在数据库表中很容易读/可编辑;


Inv*_*rce 6

好吧,我知道它有点晚了.但是对于那些勇敢的灵魂来说,随着时间的推移,这将成为现实.

文档中所述:

@Basic:最简单的映射到数据库列的类型.Basic注释可以应用于以下任何类型的持久属性或实例变量:Java原始类型,[...],枚举以及实现java.io.Serializable的任何其他类型.

重要的部分是实现Serializable的类型

到目前为止,最简单易用的解决方案是使用ArrayList而不是List(或任何可序列化的容器):

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;
Run Code Online (Sandbox Code Playgroud)

但请记住,这将使用系统序列化,因此它会带来一些代价,例如:

  • 如果序列化对象模型将更改,则可能无法还原数据

  • 为存储的每个元素添加小开销.

简而言之

存储标志或少量元素非常简单,但我不会建议它存储可能变大的数据.


小智 6

这是使用 @Converter 和 StringTokenizer 存储 Set 的解决方案。对@jonck-van-der-kogel解决方案进行更多检查。

在您的实体类中:

@Convert(converter = StringSetConverter.class)
@Column
private Set<String> washSaleTickers;
Run Code Online (Sandbox Code Playgroud)

字符串集转换器:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Zia*_*Zia 5

蒂亚戈的答案是正确的,添加更具体问题的示例,@ ElementCollection将在您的数据库中创建新表,但不映射两个表,这意味着该集合不是实体集合,而是简单类型(字符串等)的集合.) 或可嵌入元素的集合(用@Embeddable注释的类)。

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

@ElementCollection
private Collection<String> options = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

这是保存自定义对象列表的示例

@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
Run Code Online (Sandbox Code Playgroud)

对于这种情况,我们需要使类Embeddable

@Embeddable
public class Car {
}
Run Code Online (Sandbox Code Playgroud)