获得具有持久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:
> ...
这个答案是在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.
我还是建议做第一种情况。这对以后的恋爱很有帮助。
好吧,我知道有点晚了。但对于那些勇敢的灵魂来说,随着时间的推移,他们会看到这一点。
如文件中所写:
@Basic:
到数据库列的最简单的映射类型。基本注释可以应用于以下类型中的任意一种的持久属性或实例变量:]、枚举和任何其他实现java.io.Serializable的类型。
重要的部分是实现Serializable的类型
所以到目前为止,最简单和最容易使用的解决方案是简单地使用ArrayList而不是List(或任何可序列化的容器):
@Basic
ArrayList<Color> lovedColors;
@Basic
ArrayList<String> catNames;
但是请记住,这将使用系统序列化,因此它将带来一些代价,例如:
如果序列化对象模型将改变,你可能无法恢复数据
存储的每个元素都会增加少量开销。
简而言之
存储标志或少数元素非常简单,但我不这么做
建议它存储可能变大的数据。
Thiago的答案是正确的,添加了更具体的示例问题,@ElementCollection将在您的数据库中创建新表,但没有映射两个表,这意味着集合不是实体的集合,而是简单类型的集合(字符串等)或可嵌入元素的集合(类注释@Embeddable)。
下面是持久化String列表的示例
@ElementCollection
private Collection<String> options = new ArrayList<String>();
下面是持久化自定义对象列表的示例
@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
在这种情况下,我们需要使类Embeddable
@Embeddable
public class Car {
}
如果有人正在寻找一个替代的解决方案,你存储你的字符串列表作为一个字段在你的数据库,这里是我如何解决这个问题。创建一个像这样的转换器:
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;
}
基本的@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)
);
下面是使用@Converter和StringTokenizer存储Set的解决方案。再检查一下@jonck-van-der-kogel的解决方案。
在你的实体类中:
@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;
}
}
我想要的是在一个表列中持久化一组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);
}
}
}
因为我的声誉还不足以评论@razvang写的被低估的回答:
这个问题是十多年前提出的,请记住,自那以后,世界发生了很大变化。我们现在有了支持原生JSON列的数据库,可以使用这个功能,而不是使用单独的实体、连接或自定义字符串到列表转换器,这些都是其他答案使用的。
让我对@razvang的精彩回答提出两个纯粹可选的修改,根据你的具体情况,这可能会很有趣:
您可以省略auto_apply = true,并将@Convert(converter = <CONVERTER_CLASS_NAME>.class)添加到实体字段,以控制何时使用转换器。
不要在转换失败时抛出RuntimeException,您可以在那里处理错误(例如传递一个空列表并写入日志消息),以使它在某种程度上优雅地失败。