Как сохранить свойство типа List <String> в 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);
}
}
Этот код создает:
> 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: [email protected]
> 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
Используйте некоторую реализацию JPA 2: она добавляет аннотацию @ElementCollection, похожую на Hibernate, которая делает именно то, что вам нужно. Вот один пример здесь.
Изменить
Как уже упоминалось в комментариях ниже, правильная реализация JPA 2
javax.persistence.ElementCollection
@ElementCollection
Map<Key, Value> collection;
Смотрите: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html
Ответ 2
Этот ответ был выполнен с использованием пре-JPA2, если вы используете JPA2, см. ответ ElementCollection выше:
Списки объектов внутри объекта модели обычно считаются отношениями OneToMany с другим объектом. Однако String не является (по сути) допустимым клиентом отношения "один ко многим", так как он не имеет идентификатора.
Итак, вы должны преобразовать список строк в список объектов JPA объекта Argument, содержащих идентификатор и строку. Вы могли бы использовать String в качестве идентификатора, что бы сэкономить немного места в вашей таблице, как от удаления поля идентификатора, так и путем объединения строк, где строки равны, но вы потеряете возможность упорядочивать аргументы в исходном порядке (так как вы не сохранили информацию о заказе).
Кроме того, вы можете преобразовать свой список в @Transient и добавить другое поле (argStorage) в ваш класс, который является либо VARCHAR(), либо CLOB. Затем вам нужно добавить 3 функции: 2 из них одинаковы и должны преобразовать ваш список строк в одну строку (в argStorage), разделенную таким образом, что вы можете легко их разделить. Аннотируйте эти две функции (каждая из которых делает то же самое) с @PrePersist и @PreUpdate. Наконец, добавьте третью функцию, которая снова разделит argStorage в список строк и аннотирует ее @PostLoad. Это будет держать ваш CLOB обновленным со строками всякий раз, когда вы идете хранить команду, и сохраняйте поле argStorage до того, как вы сохраните его в БД.
Я все же предлагаю сделать первый случай. Это хорошая практика для реальных отношений позже.
Ответ 3
Извините за восстановление старого потока, но если кто-то ищет альтернативное решение, где вы храните свои списки строк как одно поле в вашей базе данных, вот как я это решил. Создайте конвертер как это:
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-объекте вы получите список с этими строками.
Надеюсь, это кому-нибудь пригодится.
Ответ 4
Согласно 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.
Ответ 5
Мы также можем использовать это.
@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;
Ответ 6
При использовании Hibernate-реализации JPA я обнаружил, что просто объявлять тип как ArrayList вместо List позволяет спящий режим хранить список данных.
Очевидно, что это имеет ряд недостатков по сравнению с созданием списка объектов Entity. Нет ленивой загрузки, нет возможности ссылаться на объекты в списке из других объектов, возможно, больше затруднений при построении запросов к базе данных. Однако, когда вы имеете дело со списками довольно примитивных типов, которые вы всегда хотите с нетерпением получить вместе с сущностью, тогда этот подход кажется мне хорошим.
@Entity
public class Command implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
ArrayList<String> arguments = new ArrayList<String>();
}
Ответ 7
У меня была такая же проблема, поэтому я вложил возможное решение, но в конце я решил реализовать свой ';' разделенный список String.
поэтому я
// a ; separated list of arguments
String arguments;
public List<String> getArguments() {
return Arrays.asList(arguments.split(";"));
}
Таким образом, список легко читается/редактируется в таблице базы данных;
Ответ 8
Хорошо, я знаю, что немного поздно. Но для тех смельчаков, которые увидят это с течением времени.
Как написано в документации:
@Basic: Самый простой тип отображения в столбце базы данных. Аннотация Basic может быть применена к постоянному свойству или переменной экземпляра любого из следующих типов: примитивные типы Java, [...], перечисления и любой другой тип, который реализует java.io.Serializable.
Важной частью является тип, который реализует Сериализуемый
Таким образом, на сегодняшний день самым простым и простым в использовании решением является использование ArrayList вместо List (или любого сериализуемого контейнера):
@Basic
ArrayList<Color> lovedColors;
@Basic
ArrayList<String> catNames;
Однако помните, что при этом будет использоваться сериализация системы, поэтому она будет стоить дорого, например:
если изменится сериализованная объектная модель, вы не сможете восстановить данные
небольшие накладные расходы добавляются для каждого хранимого элемента.
Короче говоря
хранить флаги или несколько элементов довольно просто, но я бы не стал рекомендуем хранить данные, которые могут стать большими.
Ответ 9
Мое исправление для этой проблемы состояло в том, чтобы отделить первичный ключ от внешнего ключа. Если вы используете eclipse и сделали указанные выше изменения, не забудьте обновить проводник базы данных. Затем воссоздайте сущности из таблиц.
Ответ 10
Ответ Тьяго правильный, добавив образец, более конкретный для вопроса, @ElementCollection создаст новую таблицу в вашей базе данных, но не будет отображать две таблицы. Это означает, что коллекция не является совокупностью объектов, а набор простых типов (строки и т.д.) или набор встраиваемых элементов (класс, аннотированный с помощью @Embeddable).
Вот пример, чтобы сохранить список Строка
@ElementCollection
private Collection<String> options = new ArrayList<String>();
Вот пример, чтобы сохранить список Пользовательский объект
@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
В этом случае нам нужно сделать класс Embeddable
@Embeddable
public class Car {
}
Ответ 11
Похоже, что ни один из ответов не исследовал наиболее важные настройки для @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
, 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)
);