1. Overview
In this tutorial, we’ll discuss how to persist a property of type List
2. Example
As a model, we use the entity library, which has an automatically generated ID, a name, a List
@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
}
For the List
3. @ElementCollection
The first option is to use @ElementCollection. This annotation allows us to specify the target class of the collection. In our case, this is a String. Furthermore, we can specify if the list should be loaded in a lazy or eager way. The default value is lazy. However, for the sake of simplicity for the examples, we set the value to 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<>();
We can also annotate the method that wants to access the list with @Transactional or the repository method with @Query(“SELECT l FROM library l JOIN FETCH l.books WHERE l.id = (:id)”) to avoid a LazyInitializationException.
The annotations result in the following 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);
We can see that @CollectionTable sets the name of the second table and the column that references our library table. In addition, the Foreign Key is also created appropriately. So by using @ElementCollection in this method, we save the second entity we would normally need for an OneToMany link.
4. Attribute Converter
Another alternative is to use a converter. For this, we have to implement the generic AttributeConverter with our desired object. In our case, this is the List
The convertToEntityAttribute(String string) method, on the other hand, defines how the string from the column is converted back to the List
@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();
}
}
We also have to add our converter to the field with @Convert:
@Convert(converter = StringListConverter.class)
@Column(name = "addresses", nullable = false)
private List<String> addresses = new ArrayList<>();
Alternatively, we could store the list as a JSON string in the column. When we decide to use the AttributeConverter, we have to keep in mind how big our list will grow since it has to fit in the selected size of the column.
In our case, it must fit into the varchar(255) addresses column. In the ElemenCollection approach, we can have an unlimited number of items in our list, each only limited by the varchar(255) of the column itself.
5. Comparison
Next, we’ll create the LibraryRepository and test our solutions:
@Repository
public interface LibraryRepository extends CrudRepository<Library, Long> {
}
When we now execute the code, we’ll add the list items to the library entity as usual:
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());
We’ll get the following output:
lib.getAddresses() = [Address 1, Address 2]
lib.getBooks() = [Book 1, Book 2]
As we can see, both implementation work as expected and have their own advantages:
Element Collection
Converter
Default Fetch Type
Lazy
Eager
Limit of List
Unlimited list items
Limited by the column length
Limit of each String
Limited by the column length
Limited by the number of list items and so also limited by the column length
Table
Creates an extra table
It doesn’t need its own table
6. Conclusion
In this article, we have discussed what kind of possibilities there are in JPA to store a list of strings of an entity. While we showed where there are possible limitations and what differences there are in the respective possibilities.
As always, the example code is available over on GitHub.