Hibernate Collection Mapping
Mapping List in Collection Mapping
Map a Java List<String> with a Hibernate XML mapping file using a question-and-answers example.
Mapping a List with XML
If a persistent class contains a List, the collection can be mapped with Hibernate's legacy <list> element. A list is an indexed collection, so Hibernate stores the position in a separate index column.
<list name="answers" table="question_answer"> <key column="question_id" not-null="true"/> <index column="answer_position"/> <element column="answer" type="string"/> </list>
@ElementCollection and @OrderColumn. XML mapping remains useful when maintaining an existing Hibernate application or when mapping metadata must stay outside Java classes.1. Create the Persistent Class
This example models a forum in which one question has multiple text answers.
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Question {
private Long id;
private String questionText;
private List<String> answers = new ArrayList<>();
protected Question() {
// Required by Hibernate.
}
public Question(String questionText, List<String> answers) {
this.questionText = questionText;
this.answers = answers;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getQuestionText() { return questionText; }
public void setQuestionText(String text) { this.questionText = text; }
public List<String> getAnswers() { return answers; }
public void setAnswers(List<String> answers) { this.answers = answers; }
}Because the list contains strings rather than entity objects, the XML mapping uses <element>. If it contained Answer entities, use a relationship mapping such as <one-to-many> instead.
2. Create the XML Mapping File
Create question.hbm.xml in the application's resources directory. The key column links collection rows to the question, index stores list order, and element stores each string.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.example.Question" table="question">
<id name="id" column="id">
<generator class="identity"/>
</id>
<property name="questionText" column="question_text"/>
<list name="answers" table="question_answer">
<key column="question_id" not-null="true"/>
<index column="answer_position" base="0"/>
<element column="answer" type="string" not-null="true"/>
</list>
</class>
</hibernate-mapping>The collection table contains one row per answer. Its foreign key points to question.id, while answer_position preserves the list order.
3. Create the Configuration File
Place hibernate.cfg.xml in src/main/resources. This example uses H2 so it can run without an external database; replace the URL and dialect for MySQL, PostgreSQL, Oracle, or another supported database.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">org.h2.Driver</property>
<property name="hibernate.connection.url">jdbc:h2:mem:forum;DB_CLOSE_DELAY=-1</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="question.hbm.xml"/>
</session-factory>
</hibernate-configuration>Use create-drop only for a self-contained example. Production applications should manage tables and indexes with a migration tool.
4. Store Questions and Answers
Create two questions, assign each a list of strings, and persist them inside one transaction.
package com.example;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class StoreData {
public static void main(String[] args) {
try (SessionFactory factory = new Configuration()
.configure()
.buildSessionFactory();
Session session = factory.openSession()) {
Transaction transaction = session.beginTransaction();
try {
Question java = new Question(
"What is Java?",
List.of("Java is a programming language",
"Java is a platform"));
Question servlet = new Question(
"What is a Servlet?",
List.of("A Servlet is an interface",
"A Servlet is an API"));
session.persist(java);
session.persist(servlet);
transaction.commit();
System.out.println("Questions saved successfully");
} catch (RuntimeException exception) {
transaction.rollback();
throw exception;
}
}
}
}Tables Created by the Mapping
| Table | Important columns | Purpose |
|---|---|---|
question | id, question_text | Stores the persistent question. |
question_answer | question_id, answer_position, answer | Stores one row for each list element and its position. |
The relationship is one-to-many from question to collection rows. The collection table is not an entity table because its values are basic strings.
5. Fetch the List with HQL
Query the questions and iterate over each list. The collection is read while the session is open.
try (Session session = factory.openSession()) {
List<Question> questions = session
.createQuery("from Question", Question.class)
.getResultList();
for (Question question : questions) {
System.out.println(
"Question: " + question.getQuestionText());
for (String answer : question.getAnswers()) {
System.out.println("- " + answer);
}
}
}Hibernate loads the question rows and the associated collection rows according to the configured fetch strategy. For many questions, consider a deliberate fetch plan to avoid an N+1 query problem.
Important Mapping Rules
<list>requires an index column because list order is persisted.<element>is for basic values such as strings and numbers.- Use
<one-to-many>or<many-to-many>for entity references. - Use
not-null="true"on the key when every collection row must have an owner. - Initialize collection properties and avoid returning
null. - Do not access a lazy collection after its session has closed.
- Do not use the legacy
incrementidentifier strategy for concurrent production applications.
