JPA에서 List <String> 유형의 속성을 유지하는 방법은 무엇입니까?


158

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

답변:


197

JPA 2 구현을 사용하십시오 : Hibernate와 비슷한 @ElementCollection 주석을 추가하여 필요한 것을 정확하게 수행합니다. 여기에 한 가지 예가 있습니다 .

편집하다

아래 의견에서 언급했듯이 올바른 JPA 2 구현은

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;

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


1
내 실수를 제거하고 일을 단지을 ElementCollection @ 떠난 후 ...뿐만 아니라 @ OneToMany 주석을 추가하는 것이 었습니다
— 윌리 Mentzel을

47

오래된 스레드를 되살려 미안하지만 누구나 문자열 목록을 데이터베이스에 하나의 필드로 저장하는 대체 솔루션을 찾고 있어야합니다. 여기에서 해결했습니다. 다음과 같이 변환기를 작성하십시오.

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 객체에는 해당 문자열이 포함 된 목록이 표시됩니다.

이것이 누군가에게 도움이되기를 바랍니다.


해당 필드의 내용으로 결과를 필터링하기 위해 jpa 리포지토리와 함께 작동합니까?
— Please_Dont_Bully_Me_SO_Lords

1
@Please_Dont_Bully_Me_SO_Lords 데이터베이스에 데이터가 "foo; bar; foobar"로 표시되므로 사용 사례에 적합하지 않습니다. 데이터를 쿼리하려면 ElementCollection + JoinTable이 상황에 맞는 방법 일 것입니다.
— Jonck van der Kogel

이것은 또한 SPLIT_CHAR문자열에 어떤 일도 발생할 수 없음을 의미 합니다.
— 호감

@crush가 맞습니다. 물론 정확하게 구분 한 후에 문자열을 인코딩하는 등의 방법으로 허용 할 수 있습니다. 그러나 여기에 게시 된 솔루션은 주로 간단한 사용 사례를위한 것입니다. 더 복잡한 상황에서는 아마도 ElementCollection + JoinTable을 사용하여 더 잘 지낼 것입니다
— Jonck van der Kogel

코드를 수정하십시오. 나는 그것을 '라이브러리 코드'로 간주하므로 방어 적이어야합니다. 예를 들어 적어도 null 검사가 있어야합니다
— ZZ 5

30

이 답변은 JPA2 이전에 구현되었으며 JPA2를 사용하는 경우 위의 ElementCollection 답변을 참조하십시오.

모델 개체 내부의 개체 목록은 일반적으로 다른 개체와의 "OneToMany"관계로 간주됩니다. 그러나 문자열 자체는 ID가 없기 때문에 일대 다 관계의 허용 가능한 클라이언트가 아닙니다.

그래서, 당신은 해야 인수 급 JPA의 목록에 문자열 목록을 변환 ID와 문자열을 포함하는 객체. 문자열을 ID로 사용하면 잠재적으로 ID 필드를 제거하고 문자열이 동일한 행을 통합하여 테이블의 공간을 절약 할 수 있지만 인수를 원래 순서로 다시 정렬하는 기능을 잃을 수 있습니다 (주문 정보를 저장하지 않았으므로).

또는 목록을 @Transient로 변환하고 클래스에 VARCHAR () 또는 CLOB 인 다른 필드 (argStorage)를 추가 할 수 있습니다. 그런 다음 3 개의 함수를 추가해야합니다. 그 중 2 개는 동일하며 문자열 목록을 쉽게 분리 할 수있는 방식으로 구분 된 단일 문자열 (argStorage)로 변환해야합니다. @PrePersist 및 @PreUpdate를 사용하여이 두 가지 기능 (각각 동일한 기능)에 주석을 답니다. 마지막으로 argStorage를 문자열 목록으로 다시 분할하는 세 번째 함수를 추가하고 @PostLoad에 주석을 답니다. 이렇게하면 명령을 저장할 때마다 CLOB가 문자열로 업데이트되고 argStorage 필드가 DB에 저장되기 전에 업데이트됩니다.

나는 여전히 첫 번째 사례를 제안합니다. 나중에 실제 관계를 유지하는 것이 좋습니다.


쉼표로 구분 된 값을 사용하여 ArrayList <String>에서 String으로 변경하면 효과가 있습니다.
— Chris Dale

2
그러나 이로 인해 해당 필드를 쿼리 할 때 추악한 문장을 사용하게됩니다.
— 위스키 시에라

예, 내가 말했듯이 ... 첫 번째 옵션을 수행하는 것이 좋습니다. 당신이 그것을 할 수 없다면 옵션 2가 효과가 있습니다.
— billjamesdev

15

Hibernate 를 사용한 Java Persistence 에 따르면

주석으로 값 유형의 콜렉션 맵핑 [...]. 작성 당시에는 Java Persistence 표준의 일부가 아닙니다.

최대 절전 모드를 사용하는 경우 다음과 같은 작업을 수행 할 수 있습니다.

@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에서 사용할 수 있습니다.


12

이것을 사용할 수도 있습니다.

@Column(name="arguments")
@ElementCollection(targetClass=String.class)
private List<String> arguments;

1
아마 플러스 @JoinTable.
— phil294

10

나는 같은 문제가 있었기 때문에 주어진 가능한 해결책에 투자했지만 결국에는 ';' 분리 된 문자열 목록.

그래서 나는 가지고있다

// a ; separated list of arguments
String arguments;

public List<String> getArguments() {
    return Arrays.asList(arguments.split(";"));
}

이런 식으로 데이터베이스 테이블에서 목록을 쉽게 읽고 편집 할 수 있습니다.


1
이것은 완전히 유효하지만 응용 프로그램의 성장과 스키마 발전을 고려하십시오. (가까운) 언젠가는 결국 엔터티 기반 접근 방식으로 전환 할 수 있습니다.
— 위스키 시에라

나는 그것이 완전히 유효하다는 것에 동의합니다. 그러나 코드 구현뿐만 아니라 논리를 완전히 검토하는 것이 좋습니다. String arguments이 액세스 권한 목록 인 경우 특수 문자 a separator가 있으면 권한 에스컬레이션 공격에 취약 할 수 있습니다.
— Thang Pham

1
이것은 정말 나쁜 조언입니다. 문자열에 ;앱이 손상 될 수 있습니다.
— agilob

9

JPA의 Hibernate 구현을 사용할 때, 단순히 List 대신 ArrayList로 유형을 선언하면 최대 절전 모드가 데이터 목록을 저장할 수 있다는 것을 알았습니다.

분명히 이것은 Entity 객체 목록을 만드는 것과 비교하여 많은 단점이 있습니다. 게으른 로딩이나 다른 개체에서 목록의 엔터티를 참조 할 수 없으며 데이터베이스 쿼리를 구성하는 데 어려움이있을 수 있습니다. 그러나 엔터티와 함께 ​​항상 열심히 가져오고 싶은 상당히 원시적 인 유형의 목록을 처리 할 때이 접근법은 나에게 잘 보입니다.

@Entity
public class Command implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;

    ArrayList<String> arguments = new ArrayList<String>();


}

2
감사. 이것은 모든 JPA 구현에서 작동하며, Arraylist is Serializable은 BLOB 필드에 저장됩니다. 이 방법의 문제점은 1) BLOB 크기가 고정되어 있습니다. 2) 배열 요소를 검색하거나 색인화 할 수 있습니다. 3) Java 직렬화 형식을 알고있는 클라이언트 만이이 요소를 읽을 수 있다는 것입니다.
— Andrea Francia

혹시이 접근하려고하면 @OneToMany @ManyToOne @ElementCollection당신에게 줄 것이다 그것을 Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements서버 시작에 예외를. 최대 절전 모드는 컬렉션 인터페이스를 사용하기를 원하기 때문입니다.
— Paramvir Singh Karwal

9

@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;

}
  1. 기본 @ElementCollection(당신이 알려진 정의 할 수 있습니다 주석 fetch및 targetClass환경 설정)
  2. @CollectionTable가 생성됩니다 테이블에 이름을 줄뿐만 아니라 같은 정의에 관해서 주석은 매우 유용합니다 joinColumns, foreignKey의, ' indexes, uniqueConstraints등
  3. @Columnvarchar목록 의 값을 저장할 열 이름을 정의하는 것이 중요 합니다.

생성 된 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)
);

4
이 솔루션은 TABLE 구조를 포함하여 전체 설명을 제공하고 왜 다른 주석이 필요한지 설명하는 유일한 제안 솔루션이므로이 솔루션이 마음에 듭니다.
— Julien Kronegg

6

알았어 조금 늦었 어 그러나 시간이 지남에 따라 이것을 볼 용감한 영혼을 위해.

문서에 작성된 바와 같이 :

@Basic : 데이터베이스 열에 매핑하는 가장 간단한 유형입니다. 기본 어노테이션은 Java 기본 유형, [...], 열거 형 및 java.io.Serializable을 구현하는 기타 유형 중 하나의 지속적 특성 또는 인스턴스 변수에 적용 할 수 있습니다.

중요한 부분은 Serializable을 구현하는 유형입니다

따라서 가장 간단하고 사용하기 쉬운 솔루션은 List (또는 직렬화 가능한 컨테이너) 대신 ArrayList를 사용하는 것입니다.

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;

그러나 이것은 시스템 직렬화를 사용하므로 다음과 같은 가격이 부과됩니다.

  • 직렬화 된 객체 모델이 변경되면 데이터를 복원하지 못할 수 있습니다

  • 저장된 각 요소에 대해 작은 오버 헤드가 추가됩니다.

한마디로

플래그 또는 몇 가지 요소를 저장하는 것은 매우 간단하지만 커질 수있는 데이터를 저장하는 데 권장하지 않습니다.


이것을 시도했지만 SQL 테이블은 데이터 유형을 작게 만들었습니다. 이렇게하면 문자열 목록을 삽입하고 검색하는 것이 매우 불편하지 않습니까? 또는 jpa가 자동으로 직렬화 및 직렬화 해제합니까?
— Dzhao

3

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 {
}

3

@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;
    }
}

1

이 문제에 대한 나의 해결책은 기본 키와 외래 키를 분리하는 것이 었습니다. 이클립스를 사용하고 위의 내용을 변경 한 경우 데이터베이스 탐색기를 새로 고쳐야합니다. 그런 다음 테이블에서 엔티티를 다시 작성하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.