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(당신이 알려진 정의 할 수 있습니다 주석 fetchtargetClass환경 설정)
  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.