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

问题

获得具有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: 
> ...

#1 热门回答(168 赞)

使用一些JPA 2实现:它添加了一个@ElementCollection注释,类似于Hibernate注释,它完全符合你的需要。有一个例子here
编辑
正如下面的评论中所提到的,正确的JPA 2实现是

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;

See:http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html


#2 热门回答(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字段存储到数据库之前保持更新。

我仍然建议做第一个案例。以后真正的关系是很好的做法。


#3 热门回答(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>();

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