获得具有持久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);
    }
}

这段代码产生:

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

当前回答

我解决这个问题的方法是将主键和外键分开。如果您正在使用eclipse并进行了上述更改,请记得刷新数据库资源管理器。然后从表中重新创建实体。

其他回答

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

模型对象内的对象列表通常被认为是与另一个对象的“OneToMany”关系。但是,String本身并不是一对多关系的允许客户端,因为它没有ID。

因此,您应该将字符串列表转换为包含ID和String的参数类JPA对象列表。您可以潜在地使用String作为ID,这将在您的表中节省一点空间,既可以删除ID字段,也可以合并字符串相等的行,但您将失去将参数按原始顺序排序的能力(因为您没有存储任何排序信息)。

Alternatively, you could convert your list to @Transient and add another field (argStorage) to your class that is either a VARCHAR() or a CLOB. You'll then need to add 3 functions: 2 of them are the same and should convert your list of Strings into a single String (in argStorage) delimited in a fashion that you can easily separate them. Annotate these two functions (that each do the same thing) with @PrePersist and @PreUpdate. Finally, add the third function that splits the argStorage into the list of Strings again and annotate it @PostLoad. This will keep your CLOB updated with the strings whenever you go to store the Command, and keep the argStorage field updated before you store it to the DB.

我还是建议做第一种情况。这对以后的恋爱很有帮助。

我解决这个问题的方法是将主键和外键分开。如果您正在使用eclipse并进行了上述更改,请记得刷新数据库资源管理器。然后从表中重新创建实体。

我想要的是在一个表列中持久化一组string的简单方法。

我最终使用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);
        }
    }
}

似乎没有一个答案探讨了@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;
    
}

基本的@ElementCollection注释(您可以在其中定义已知的fetch和targetClass首选项) @CollectionTable注释在为将要生成的表命名时非常有用,它还可以定义像joinColumns、foreignKey’s、indexes、uniqueConstraints等。 @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)
);

如果有人正在寻找一个替代的解决方案,你存储你的字符串列表作为一个字段在你的数据库,这里是我如何解决这个问题。创建一个像这样的转换器:

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对象中,您将获得一个包含这些字符串的列表。