获取具有List类型字段的实体的最明智的方法是什么?
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:
> ...
答案 0 :(得分:178)
使用一些JPA 2实现:它添加了一个@ElementCollection注释,类似于Hibernate注释,完全符合您的需要。有一个例子here。
修改
如下面的评论中所述,正确的JPA 2实现是
javax.persistence.ElementCollection
@ElementCollection
Map<Key, Value> collection;
请参阅:http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html
答案 1 :(得分: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字段存储到数据库之前保持更新。
我仍然建议做第一个案例。这对后来的真实关系来说是个好习惯。
答案 2 :(得分:18)
很抱歉重振旧线程,但是任何人都应该寻找替代解决方案,将字符串列表存储为数据库中的一个字段,以下是我如何解决这个问题。像这样创建一个转换器:
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));
}
}
现在在您的实体上使用它:
@Convert(converter = StringListConverter.class)
private List<String> yourList;
在数据库中,您的列表将存储为foo; bar; foobar,在Java对象中,您将获得包含这些字符串的列表。
希望这对某人有帮助。
答案 3 :(得分:15)
根据Java Persistence with Hibernate
使用注释[...]映射值类型的集合。在撰写本文时,它不是Java持久性标准的一部分
如果您使用的是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中使用。
答案 4 :(得分:9)
当使用JPA的Hibernate实现时,我发现简单地将类型声明为ArrayList而不是List允许hibernate存储数据列表。
显然,与创建Entity对象列表相比,这有许多缺点。没有延迟加载,没有能力从其他对象引用列表中的实体,可能在构造数据库查询时更加困难。但是当你处理相当原始类型的列表时,你总是希望与实体一起急切地获取,那么这种方法对我来说似乎很好。
@Entity
public class Command implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
ArrayList<String> arguments = new ArrayList<String>();
}
答案 5 :(得分:9)
我遇到了同样的问题所以我投入了可能的解决方案,但最后我决定实施我的';'分隔的字符串列表。
所以我有
// a ; separated list of arguments
String arguments;
public List<String> getArguments() {
return Arrays.asList(arguments.split(";"));
}
这样,列表在数据库表中很容易阅读/编辑;
答案 6 :(得分:8)
我们也可以使用它。
@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
答案 7 :(得分:6)
这是使用@Converter和StringTokenizer存储Set的解决方案。对@jonck-van-der-kogel解决方案进行了更多检查。
在您的Entity类中:
@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;
}
}
答案 8 :(得分:5)
好的,我知道它有点晚了。但是对于那些勇敢的灵魂来说,随着时间的推移,这将成为现实。
正如documentation所述:
@Basic: 最简单的映射到数据库列的类型。 Basic注释可以应用于以下任何类型的持久属性或实例变量:Java原始类型,[...],枚举以及实现java.io.Serializable的任何其他类型。
重要的部分是类型,它实现了Serializable
到目前为止,最简单易用的解决方案是使用ArrayList而不是List(或任何可序列化的容器):
@Basic
ArrayList<Color> lovedColors;
@Basic
ArrayList<String> catNames;
但请记住,这将使用系统序列化,因此它会带来一些代价,例如:
如果序列化对象模型发生变化,您可能无法恢复数据
为每个存储的元素添加小开销。
简而言之
存储标志或少量元素非常简单,但我不会 建议它存储可能变大的数据。
答案 9 :(得分:1)
我对此问题的解决方法是将主键与外键分开。如果您正在使用eclipse并进行了上述更改,请记得刷新数据库资源管理器。然后从表中重新创建实体。
答案 10 :(得分:1)
Thiago答案是正确的,添加更具体的问题样本, @ElementCollection 将在您的数据库中创建新表,但没有映射两个表,这意味着该集合不是实体的集合,但是一组简单类型(字符串等)或一组可嵌入元素(使用 @Embeddable 注释的类)。
以下是字符串
的持久列表示例@ElementCollection
private Collection<String> options = new ArrayList<String>();
以下是自定义对象
的持久列表示例@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
对于这种情况,我们需要将类嵌入
@Embeddable
public class Car {
}
答案 11 :(得分:0)
似乎没有一个答案探讨了@ElementCollection
映射的最重要设置。
当您使用此批注映射列表时,让JPA / Hibernate自动生成表,列等时,它也会使用自动生成的名称。
因此,让我们分析一个基本示例:
@Entity
@Table(name = "sample")
public class MySample {
@Id
@GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "my_list", joinColumns = @JoinColumn(name = "id"))
@Column(name = "list")
private List<String> list;
}
@ElementCollection
批注(您可以在其中定义已知的fetch
和targetClass
首选项)@CollectionTable
注释在为将要生成的表命名以及joinColumns
,foreignKey
,{{ 1}},indexes
等uniqueConstraints
对于定义将存储列表的@Column
值的列的名称很重要。生成的DDL创建为:
varchar
答案 12 :(得分:0)
我想要的是一种在表列中持久化一组字符串的简单方法。
我最终使用了 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);
}
}
}