Làm cách nào để duy trì thuộc tính loại Danh sách <Chuỗi> trong JPA?


158

Cách thông minh nhất để có được một thực thể với một trường loại Danh sách vẫn tồn tại là gì?

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

Mã này tạo ra:

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

Câu trả lời:


197

Sử dụng một số triển khai JPA 2: nó thêm chú thích @EuityCollection, tương tự như chú thích Hibernate, thực hiện chính xác những gì bạn cần. Có một ví dụ ở đây .

Biên tập

Như đã đề cập trong các ý kiến ​​dưới đây, việc triển khai JPA 2 chính xác là

javax.persistence.ElementCollection

@ElementCollection
Map<Key, Value> collection;

Xem: http://docs.oracle.com/javaee/6/api/javax/persistence/EuityCollection.html


1
Lỗi của tôi là thêm chú thích @ OneToMany ... sau khi xóa nó và chỉ để lại @ ElementCollection, nó hoạt động
Willi Mentzel

46

Xin lỗi để hồi sinh một luồng cũ nhưng bất kỳ ai cũng đang tìm kiếm một giải pháp thay thế nơi bạn lưu trữ danh sách chuỗi của bạn dưới dạng một trường trong cơ sở dữ liệu của bạn, đây là cách tôi giải quyết vấn đề đó. Tạo một Trình chuyển đổi như thế này:

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

Bây giờ sử dụng nó trên các Thực thể của bạn như thế này:

@Convert(converter = StringListConverter.class)
private List<String> yourList;

Trong cơ sở dữ liệu, danh sách của bạn sẽ được lưu trữ dưới dạng foo; bar; foobar và trong đối tượng Java của bạn, bạn sẽ nhận được một danh sách với các chuỗi đó.

Hy vọng điều này là hữu ích cho một ai đó.


Nó có hoạt động với kho jpa để lọc kết quả theo nội dung của trường đó không?
Vui lòng_Dont_Bully_Me_SO_Lords

1
@Please_Dont_Bully_Me_SO_Lords Nó không phù hợp với trường hợp sử dụng đó vì dữ liệu của bạn sẽ nằm trong cơ sở dữ liệu dưới dạng "foo; bar; foobar". Nếu bạn muốn truy vấn dữ liệu thì có lẽ ElementCollection + JoinTable là cách phù hợp với tình huống của bạn.
Jonck van der Kogel

Điều này cũng có nghĩa là bạn không thể có bất kỳ SPLIT_CHARsự xuất hiện nào trong chuỗi của mình.
nghiền nát

@crush đúng rồi. Mặc dù tất nhiên bạn có thể cho phép nó bằng cách mã hóa chuỗi của bạn sau khi bạn đã phân tách chính xác chuỗi đó. Nhưng giải pháp tôi đăng ở đây chủ yếu là cho các trường hợp sử dụng đơn giản; đối với các tình huống phức tạp hơn, có thể bạn sẽ có giá tốt hơn với ElementCollection + JoinTable
Jonck van der Kogel

Vui lòng sửa mã của bạn. Tôi coi đó là 'mã thư viện' vì vậy nó phải phòng thủ, ví dụ như ít nhất nó phải có kiểm tra null
ZZ 5

30

Câu trả lời này đã được thực hiện trước khi thực hiện JPA2, nếu bạn đang sử dụng JPA2, hãy xem câu trả lời ElementCollection ở trên:

Danh sách các đối tượng bên trong một đối tượng mô hình thường được coi là mối quan hệ "OneToMany" với một đối tượng khác. Tuy nhiên, một Chuỗi không phải (tự nó) là một khách hàng được phép của mối quan hệ Một-Nhiều, vì nó không có ID.

Vì vậy, bạn nên chuyển đổi danh sách Chuỗi của mình thành danh sách các đối tượng JPA lớp Đối số có chứa ID và Chuỗi. Bạn có khả năng có thể sử dụng Chuỗi làm ID, điều này sẽ tiết kiệm một ít không gian trong bảng của bạn cả khi xóa trường ID và bằng cách hợp nhất các hàng có Chuỗi bằng nhau, nhưng bạn sẽ mất khả năng sắp xếp lại các đối số theo thứ tự ban đầu (vì bạn không lưu trữ bất kỳ thông tin đặt hàng nào).

Ngoài ra, bạn có thể chuyển đổi danh sách của mình thành @Transient và thêm một trường khác (argStorage) vào lớp của bạn là VARCHAR () hoặc CLOB. Sau đó, bạn sẽ cần thêm 3 hàm: 2 trong số chúng giống nhau và nên chuyển đổi danh sách Chuỗi của bạn thành một Chuỗi (trong argStorage) được phân tách theo kiểu mà bạn có thể dễ dàng tách chúng ra. Chú thích hai hàm này (mỗi hàm làm cùng một thứ) với @PrePersist và @PreUpdate. Cuối cùng, thêm chức năng thứ ba phân tách argStorage vào danh sách Chuỗi một lần nữa và chú thích nó @PostLoad. Điều này sẽ giữ cho CLOB của bạn được cập nhật với các chuỗi bất cứ khi nào bạn lưu trữ Lệnh và giữ cho trường argStorage được cập nhật trước khi bạn lưu trữ nó vào DB.

Tôi vẫn đề nghị làm trường hợp đầu tiên. Đó là thực hành tốt cho các mối quan hệ thực sự sau này.


Thay đổi từ ArrayList <String> thành String với các giá trị được phân tách bằng dấu phẩy làm việc cho tôi.
Chris Dale

2
Nhưng điều này buộc bạn phải sử dụng (imho) những câu lệnh xấu xí khi truy vấn trường đó.
whiskeyierra

Vâng, như tôi đã nói ... làm tùy chọn đầu tiên, nó tốt hơn. Nếu bạn không thể tự mình làm điều đó, tùy chọn 2 có thể hoạt động.
billjamesdev

15

Theo Java Persistence với Hibernate

bộ sưu tập ánh xạ của các loại giá trị với chú thích [...]. Tại thời điểm viết nó không phải là một phần của tiêu chuẩn Kiên trì Java

Nếu bạn đang sử dụng Hibernate, bạn có thể làm một cái gì đó như:

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

Cập nhật: Lưu ý, điều này hiện có sẵn trong JPA2.


12

Chúng tôi cũng có thể sử dụng này.

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

1
có lẽ cộng với @JoinTable.
phil294

9

Khi sử dụng triển khai JPA Hibernate, tôi thấy rằng chỉ cần khai báo kiểu là ArrayList thay vì List cho phép ngủ đông lưu trữ danh sách dữ liệu.

Rõ ràng điều này có một số nhược điểm so với việc tạo một danh sách các đối tượng Thực thể. Không tải lười biếng, không có khả năng tham chiếu các thực thể trong danh sách từ các đối tượng khác, có lẽ khó khăn hơn trong việc xây dựng các truy vấn cơ sở dữ liệu. Tuy nhiên, khi bạn đang xử lý các danh sách các loại khá nguyên thủy mà bạn sẽ luôn muốn háo hức tìm kiếm cùng với thực thể, thì cách tiếp cận này có vẻ tốt với tôi.

@Entity
public class Command implements Serializable {

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

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


}

2
Cảm ơn. Công việc này với tất cả các triển khai JPA, Arraylist là serializable được lưu trong trường BLOB. Các vấn đề với phương pháp này là 1) kích thước BLOB được cố định 2) bạn có thể tìm kiếm hoặc lập chỉ mục các phần tử mảng 3) chỉ khách hàng biết về định dạng tuần tự hóa Java có thể đọc các phần tử này.
Andrea Francia

Trong trường hợp nếu bạn thử phương pháp này với @OneToMany @ManyToOne @ElementCollectionnó sẽ cho bạn Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElementsngoại lệ khi khởi động máy chủ. Bởi vì ngủ đông muốn bạn sử dụng giao diện bộ sưu tập.
Paramvir Singh Karwal 23/03/19

9

Tôi có cùng một vấn đề vì vậy tôi đã đầu tư giải pháp có thể được đưa ra nhưng cuối cùng tôi quyết định thực hiện ';' của mình danh sách String riêng biệt.

vì vậy tôi có

// a ; separated list of arguments
String arguments;

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

Bằng cách này, danh sách có thể dễ dàng đọc / chỉnh sửa trong bảng cơ sở dữ liệu;


1
Điều này là hoàn toàn hợp lệ nhưng hãy xem xét sự phát triển của ứng dụng của bạn và sự phát triển lược đồ. Đôi khi trong tương lai (gần) cuối cùng bạn có thể chuyển sang cách tiếp cận dựa trên thực thể.
whiskeyierra

Tôi đồng ý, điều đó là hoàn toàn hợp lệ. Tuy nhiên, tôi đề nghị xem xét đầy đủ logic cũng như việc thực thi mã. Nếu String argumentslà danh sách các quyền truy cập, thì việc có một ký tự đặc biệt, a separator, có thể dễ bị tổn thương đối với các cuộc tấn công leo thang đặc quyền.
Thắng Phạm

1
Đây thực sự là một lời khuyên tồi, chuỗi của bạn có thể chứa ;ứng dụng sẽ phá vỡ ứng dụng của bạn.
agilob

9

Dường như không có câu trả lời nào khám phá các cài đặt quan trọng nhất cho @ElementCollectionánh xạ.

Khi bạn ánh xạ một danh sách với chú thích này và để tự động JPA / Hibernate tạo các bảng, cột, v.v., nó cũng sẽ sử dụng tên được tạo tự động.

Vì vậy, hãy phân tích một ví dụ cơ bản:

@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. @ElementCollectionChú thích cơ bản (nơi bạn có thể xác định các tùy chọn đã biết fetchtargetClasstùy chọn)
  2. Các @CollectionTablechú thích là rất hữu ích khi nói đến đặt tên cho bảng đó sẽ được tạo ra, cũng như định nghĩa như joinColumns, foreignKey's indexes, uniqueConstraintsvv
  3. @Columnrất quan trọng để xác định tên của cột sẽ lưu trữ varchargiá trị của danh sách.

Việc tạo DDL được tạo sẽ là:

-- 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
Tôi thích giải pháp này vì đây là giải pháp được đề xuất duy nhất cung cấp mô tả đầy đủ bao gồm các cấu trúc BẢNG và giải thích lý do tại sao chúng ta cần các chú thích khác nhau.
Julien Kronegg

6

Ok tôi biết nó hơi muộn. Nhưng đối với những linh hồn dũng cảm sẽ thấy điều này khi thời gian trôi qua.

Như được viết trong tài liệu :

@Basic: Loại ánh xạ đơn giản nhất vào cột cơ sở dữ liệu. Chú thích cơ bản có thể được áp dụng cho một thuộc tính hoặc biến đối tượng liên tục của bất kỳ loại nào sau đây: các kiểu nguyên thủy Java, [...], enums và bất kỳ loại nào khác thực hiện java.io.Serializable.

Phần quan trọng là loại thực hiện Nối tiếp

Vì vậy, cho đến nay, giải pháp đơn giản và dễ sử dụng nhất chỉ đơn giản là sử dụng ArrayList thay vì List (hoặc bất kỳ vùng chứa tuần tự hóa nào):

@Basic
ArrayList<Color> lovedColors;

@Basic
ArrayList<String> catNames;

Tuy nhiên, hãy nhớ rằng điều này sẽ sử dụng tuần tự hóa hệ thống, vì vậy nó sẽ đi kèm với một số giá, chẳng hạn như:

  • nếu mô hình đối tượng được tuần tự hóa sẽ thay đổi, bạn có thể không thể khôi phục dữ liệu

  • chi phí nhỏ được thêm vào cho mỗi phần tử được lưu trữ.

Nói ngắn gọn

Việc lưu trữ cờ hoặc một vài thành phần khá đơn giản, nhưng tôi sẽ không đề xuất nó để lưu trữ dữ liệu có thể phát triển lớn.


đã thử điều này nhưng bảng sql làm cho kiểu dữ liệu nhỏ xíu. Điều này có làm cho việc chèn và truy xuất danh sách các chuỗi rất bất tiện không? Hay jpa tự động tuần tự hóa và giải trừ cho bạn?
Dzhao

3

Câu trả lời của Thiago là chính xác, thêm mẫu cụ thể hơn vào câu hỏi, @EuityCollection sẽ tạo bảng mới trong cơ sở dữ liệu của bạn, nhưng không ánh xạ hai bảng, điều đó có nghĩa là bộ sưu tập không phải là một tập hợp các thực thể, mà là một tập hợp các loại đơn giản (Chuỗi, v.v. .) hoặc một tập hợp các phần tử có thể nhúng (lớp được chú thích bằng @Embeddable ).

Dưới đây là mẫu để duy trì danh sách Chuỗi

@ElementCollection
private Collection<String> options = new ArrayList<String>();

Dưới đây là mẫu để duy trì danh sách đối tượng Tùy chỉnh

@Embedded
@ElementCollection
private Collection<Car> carList = new ArrayList<Car>();

Đối với trường hợp này, chúng ta cần tạo lớp nhúng

@Embeddable
public class Car {
}

3

Đây là giải pháp để lưu trữ Tập hợp bằng cách sử dụng @Converter và StringTokenizer. Thêm một chút kiểm tra đối với giải pháp @ jonck-van-der-kogel .

Trong lớp Thực thể của bạn:

@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

Cách khắc phục của tôi cho vấn đề này là tách khóa chính với khóa ngoại. Nếu bạn đang sử dụng nhật thực và thực hiện các thay đổi ở trên, hãy nhớ làm mới trình khám phá cơ sở dữ liệu. Sau đó tạo lại các thực thể từ các bảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.