Hibernate Collection Mapping

Mapping Bag in Collection Mapping

Map a Hibernate bag collection with an XML mapping file when the collection does not need a stored index or duplicate elimination.

What Is a Bag Collection?

A Hibernate bag is an unordered, non-indexed collection. It is similar to a list in that duplicate values are allowed, but Hibernate does not persist a position for each element. Therefore, a <bag> mapping does not use an <index> element.

<bag name="answers" table="question_answer">
  <key column="question_id" not-null="true"/>
  <element column="answer" type="string"/>
</bag>
Choose carefully: use a bag when ordering is not meaningful and duplicates are acceptable. Use a list when position matters, or a set when duplicate values must be removed.

Bag Compared with List and Set

CollectionIndex columnDuplicatesOrdering
bagNoAllowedNot guaranteed
listRequiredAllowedStored by position
setNoRemoved according to equalityNot guaranteed unless ordered

1. Create the Persistent Class

This forum example stores multiple text answers for each question. The Java property can be declared as a List for legacy XML compatibility, but the collection must be treated as unordered.

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 text, List<String> answers) {
        this.questionText = text;
        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; }
}

For new code, consider exposing a Collection<String> or using an explicit @ElementCollection mapping. The XML <bag> element controls the persistence semantics.

2. Create the Bag Mapping File

Create question.hbm.xml in the resources directory. The key links each answer row to its question and element stores the basic string value.

<?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"/>

    <bag name="answers" table="question_answer">
      <key column="question_id" not-null="true"/>
      <element column="answer" type="string" not-null="true"/>
    </bag>
  </class>
</hibernate-mapping>

No index column is created because the mapping does not promise a stable order. The collection table can contain duplicate answer values for the same question.

3. Create the Configuration File

Place hibernate.cfg.xml in src/main/resources. H2 is used here for a portable example.

<?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-bag;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 migrations instead of create-drop for a production database.

4. Store Questions and Answers

Persist two questions with their answer collections in 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;
            }
        }
    }
}

5. Fetch the Bag with HQL

Access the collection while the Hibernate session is open. Since a bag has no persisted index, do not rely on the order in which answers are returned.

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

For a large number of questions, plan fetching deliberately to avoid N+1 queries. Use a query with an explicit fetch join or an entity graph when the use case requires the answers immediately.

Bag of Entity References

A bag can also hold entity references. Replace <element> with a relationship element in legacy XML:

<bag name="answers" table="question_answer">
  <key column="question_id" not-null="true"/>
  <one-to-many class="com.example.Answer"/>
</bag>

For modern mappings, use @OneToMany. Define cascade, orphan removal, ownership, and equality behavior carefully before allowing duplicate entity references.

Bag Mapping Best Practices

  • Use a bag only when order is not part of the business meaning.
  • Use a list with an order column when the position must be preserved.
  • Use a set when duplicate values are not valid.
  • Keep the collection initialized and avoid null.
  • Do not access a lazy bag after its session has closed.
  • Use not-null="true" when every collection row must have an owner.
  • Use database indexes on foreign-key columns for large collections.
  • Use schema migrations rather than automatic schema updates in production.
Result: the question is stored in the parent table and each answer is stored in the collection table without an index column. Duplicate answers are allowed, and their retrieval order is not guaranteed.