如何在 JPA 中持久化一個字符串列表?
一、概述
在本教程中,我們將討論如何在 JPA 中保留List<String>
類型的屬性。我們來看看實現這一點的可能性,它們有何不同,並藉助示例解釋優勢。
2.例子
作為模型,我們使用實體庫,它有一個自動生成的 ID、一個名稱、一個包含地址的List<String>
和一個包含書名的List<String>
:
@Entity(name = "library")
public class Library {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private List<String> addresses = new ArrayList<>();
private List<String> books = new ArrayList<>();
// getter, setter, and constructor
}
對於List<String>,
可以使用 id 和字符串創建第二個實體,然後使用OneToMany
關係對其進行註釋。我們將研究使用 JPA 簡化此行為的其他兩種可能性。
3. @ElementCollection
第一個選項是使用@ElementCollection
。這個註釋允許我們指定集合的目標類。在我們的例子中,這是一個String
。此外,我們可以指定列表是否應該以懶惰或急切的方式加載。默認值是惰性的。但是,為了示例的簡單起見,我們將值設置為 eager:
@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
@CollectionTable(name = "books", joinColumns = @JoinColumn(name = "library_id"))
@Column(name = "book", nullable = false)
private List<String> books = new ArrayList<>();
我們還可以使用@Transactional
註釋要訪問列表的方法或使用@Query(“SELECT l FROM library l JOIN FETCH l.books WHERE l.id = (:id)”)
列表的方法以避免LazyInitializationException
。
註釋導致以下 DDL:
CREATE TABLE books
(
library_id BIGINT NOT NULL,
book VARCHAR(255) NOT NULL
);
CREATE TABLE library
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
name VARCHAR(255),
addresses VARCHAR(255) NOT NULL,
CONSTRAINT pk_library PRIMARY KEY (id)
);
ALTER TABLE books
ADD CONSTRAINT fk_books_on_library FOREIGN KEY (library_id) REFERENCES library (id);
我們可以看到@CollectionTable
設置了第二個表的名稱和引用我們庫表的列。此外,外鍵也適當創建。因此,通過在此方法中使用@ElementCollection
,我們保存了OneToMany
鏈接通常需要的第二個實體。
4.屬性轉換器
另一種選擇是使用轉換器。為此,我們必須使用我們想要的對象來實現通用的AttributeConverter
。在我們的例子中,這是List<String>
;例如,所需的格式可以是String
。在convertToDatabaseColumn(List<String> stringList)
方法中,返回值是對象最終應該在數據庫中的數據類型,參數是我們的列表。
另一方面, convertToEntityAttribute(String string)
方法定義瞭如何將列中的字符串轉換回List<String>
。在我們的示例中,我們使用字符“;”分隔字符串:
@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
將我們的轉換器添加到字段中:
@Convert(converter = StringListConverter.class)
@Column(name = "addresses", nullable = false)
private List<String> addresses = new ArrayList<>();
或者,我們可以將列表作為 JSON 字符串存儲在列中。當我們決定使用AttributeConverter,
我們必須牢記我們的列表將增長到多大,因為它必須適合列的選定大小。
在我們的例子中,它必須適合 varchar(255) addresses
列。在ElemenCollection
方法中,我們的列表中可以有無限數量的項目,每個項目僅受列本身的varchar(255)
限制。
5.比較
接下來,我們將創建LibraryRepository
並測試我們的解決方案:
@Repository
public interface LibraryRepository extends CrudRepository<Library, Long> {
}
當我們現在執行代碼時,我們將像往常一樣將列表項添加到library
實體中:
Library library = new Library();
library.setAddresses(Arrays.asList("Address 1", "Address 2"));
library.setBooks(Arrays.asList("Book 1", "Book 2"));
libraryRepository.save(library);
Library lib = libraryRepository.findById(library.getId().longValue());
System.out.println("lib.getAddresses() = " + lib.getAddresses());
System.out.println("lib.getBooks() = " + lib.getBooks());
我們將得到以下輸出:
lib.getAddresses() = [Address 1, Address 2]
lib.getBooks() = [Book 1, Book 2]
正如我們所見,這兩種實現都按預期工作並且各有優勢:
元素集合 | 轉換器 | |
默認獲取類型 | 懶惰的 | 渴望的 |
列表的限制 | 無限列表項 | 受柱長限制 |
每個字符串的限制 | 受柱長限制 | 受列表項數量的限制,因此也受列長度的限制 |
桌子 | 創建一個額外的表 | 它不需要自己的表 |
六,結論
在本文中,我們討論了在 JPA 中存儲實體字符串列表的可能性。雖然我們展示了哪些地方可能存在限制,以及各種可能性之間存在哪些差異。
與往常一樣,示例代碼在 GitHub 上可用。