¿Qué es @ElementCollection?
@ElementCollection es una anotación de JPA que permite almacenar colecciones de tipos básicos (String, Integer, Double, etc.) o embeddables que no son entidades independientes.
JPA crea automáticamente una tabla separada para almacenar los elementos de la colección, estableciendo una relación uno-a-muchos entre la entidad principal y los valores almacenados.
Diferencia con @OneToMany
A diferencia de @OneToMany, que relaciona entidades completas con su propia identidad (@Id), @ElementCollection se usa para valores simples que no necesitan existir independientemente de la entidad padre.
| Aspecto | @ElementCollection | @OneToMany |
|---|---|---|
| Tipo de datos | Tipos básicos o @Embeddable | Entidades con @Id |
| Existencia independiente | No, depende de la entidad padre | Sí, entidad independiente |
| Tabla adicional | Sí, automática | Sí, la tabla de la entidad |
| Ejemplo de uso | Lista de emails, tags, números | Lista de pedidos, comentarios |
¿Cuándo Usar @ElementCollection?
Casos de Uso Apropiados ✓
Emails, teléfonos, direcciones cuando no necesitan ser entidades independientes.
Tags, palabras clave, categorías simples asociadas a un producto o publicación.
Calificaciones, precios históricos, mediciones que no requieren metadatos adicionales.
URLs de redes sociales, habilidades, intereses, lenguajes de programación.
Cuándo NO Usar ✗
Si necesitas relacionar con otra entidad que tiene su propio ID, usa @OneToMany o @ManyToMany.
Si cada elemento necesita fecha de creación, autor, estado, etc., mejor crear una entidad completa.
Para colecciones muy grandes, el rendimiento puede verse afectado. Evalúa otras alternativas.
Si necesitas consultar frecuentemente los elementos sin la entidad padre, considera una entidad separada.
Usa @ElementCollection cuando los valores en la colección no tienen significado o existencia fuera del contexto de la entidad padre.
Ejemplos de Código
Producto con Tags (Colección de Strings)
@Entity
public class Producto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
private Double precio;
@ElementCollection
@CollectionTable(
name = "producto_tags",
joinColumns = @JoinColumn(name = "producto_id")
)
@Column(name = "tag")
private List<String> tags;
}
Estructura en Base de Datos
| id | nombre | precio |
|---|---|---|
| 1 | Laptop HP | 1200.00 |
| 2 | Mouse Logitech | 45.00 |
| 3 | Teclado Mecánico | 120.00 |
| producto_id | tag |
|---|---|
| 1 | electrónica |
| 1 | computador |
| 1 | portátil |
| 2 | periférico |
| 2 | accesorio |
| 3 | periférico |
| 3 | gaming |
Estudiante con Calificaciones (Colección de Números)
@Entity
public class Estudiante {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
private String programa;
@ElementCollection
@CollectionTable(
name = "calificaciones",
joinColumns = @JoinColumn(name = "estudiante_id")
)
@Column(name = "nota")
private List<Double> notas;
}
Estructura en Base de Datos
| id | nombre | programa |
|---|---|---|
| 1 | María García | Ingeniería de Software |
| 2 | Carlos López | Ingeniería de Sistemas |
| estudiante_id | nota |
|---|---|
| 1 | 4.5 |
| 1 | 4.8 |
| 1 | 4.2 |
| 2 | 3.9 |
| 2 | 4.1 |
Ejemplo Completo: Usuario con Múltiples Colecciones
@Entity
@Table(name = "usuarios")
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
private String nombreUsuario;
// Colección de emails
@ElementCollection
@CollectionTable(
name = "usuario_emails",
joinColumns = @JoinColumn(name = "usuario_id")
)
@Column(name = "email")
private Set<String> emails;
// Colección de teléfonos
@ElementCollection
@CollectionTable(
name = "usuario_telefonos",
joinColumns = @JoinColumn(name = "usuario_id")
)
@Column(name = "telefono")
private Set<String> telefonos;
// Colección de habilidades
@ElementCollection
@CollectionTable(
name = "usuario_habilidades",
joinColumns = @JoinColumn(name = "usuario_id")
)
@Column(name = "habilidad")
private List<String> habilidades;
}
Estructura en Base de Datos
| id | nombre | nombre_usuario |
|---|---|---|
| 1 | Juan Pérez | jperez |
| usuario_id | |
|---|---|
| 1 | juan@empresa.com |
| 1 | jperez@personal.com |
| usuario_id | telefono |
|---|---|
| 1 | +57 300 1234567 |
| 1 | +57 310 7654321 |
| usuario_id | habilidad |
|---|---|
| 1 | Java |
| 1 | Spring Boot |
| 1 | JPA |
| 1 | SQL |
Estructura de Base de Datos
JPA genera automáticamente las tablas necesarias para almacenar las colecciones. Veamos cómo quedan estructuradas:
Ejemplo 1: Producto con Tags
| id | nombre | precio |
|---|---|---|
| 1 | Laptop HP | 1200.00 |
| 2 | Mouse Logitech | 45.00 |
| 3 | Teclado Mecánico | 120.00 |
| producto_id | tag |
|---|---|
| 1 | electrónica |
| 1 | computador |
| 1 | portátil |
| 2 | periférico |
| 2 | accesorio |
| 3 | periférico |
| 3 | gaming |
Ejemplo 2: Estudiante con Calificaciones
| id | nombre | programa |
|---|---|---|
| 1 | María García | Ingeniería de Software |
| 2 | Carlos López | Ingeniería de Sistemas |
| estudiante_id | nota |
|---|---|
| 1 | 4.5 |
| 1 | 4.8 |
| 1 | 4.2 |
| 2 | 3.9 |
| 2 | 4.1 |
Ejemplo 3: Usuario con Múltiples Colecciones
| id | nombre | nombre_usuario |
|---|---|---|
| 1 | Juan Pérez | jperez |
| usuario_id | |
|---|---|
| 1 | juan@empresa.com |
| 1 | jperez@personal.com |
| usuario_id | telefono |
|---|---|
| 1 | +57 300 1234567 |
| 1 | +57 310 7654321 |
| usuario_id | habilidad |
|---|---|
| 1 | Java |
| 1 | Spring Boot |
| 1 | JPA |
| 1 | SQL |
Cada tabla de colección tiene una foreign key (usuario_id, producto_id, estudiante_id) que referencia a la tabla principal. Los elementos de la colección no tienen su propio ID único, dependen completamente de la entidad padre.
Anotaciones de JPA para Colecciones
@ElementCollection
Obligatoria. Marca un atributo como una colección de elementos básicos o embeddables. Sin esta anotación, JPA no procesará la colección correctamente.
@ElementCollection
private List<String> tags;
@CollectionTable
Opcional. Especifica el nombre y estructura de la tabla que almacenará los elementos. Si no se especifica, JPA usa convenciones por defecto.
@ElementCollection
@CollectionTable(
name = "producto_tags", // Nombre de la tabla
joinColumns = @JoinColumn(name = "producto_id") // FK
)
private List<String> tags;
@Column
Opcional. Define el nombre de la columna donde se almacenarán los valores de la colección.
@ElementCollection
@Column(name = "email_address")
private Set<String> emails;
@OrderColumn
Opcional. Para listas (List), mantiene el orden de los elementos creando una columna adicional para el índice.
@ElementCollection
@OrderColumn(name = "posicion")
private List<String> habilidades;
@Enumerated
Para enums. Define cómo almacenar valores de tipo enum (como String o como entero).
@ElementCollection
@Enumerated(EnumType.STRING)
private Set<Rol> roles;
Configuración por Defecto vs. Personalizada
| Aspecto | Valor por Defecto | Personalizado |
|---|---|---|
| Nombre tabla | entidad_atributo | @CollectionTable(name="...") |
| Nombre columna FK | entidad_id | @JoinColumn(name="...") |
| Nombre columna valor | nombre_atributo | @Column(name="...") |
Ejemplo: Configuración Mínima
// JPA genera nombres automáticamente
@ElementCollection
private List<String> tags;
// Tabla generada: Producto_tags
// Columnas: Producto_id, tags
Ejemplo: Configuración Completa
// Control total sobre nombres
@ElementCollection
@CollectionTable(
name = "producto_etiquetas",
joinColumns = @JoinColumn(name = "id_producto")
)
@Column(name = "etiqueta")
private List<String> tags;
// Tabla generada: producto_etiquetas
// Columnas: id_producto, etiqueta
Usa configuración personalizada en proyectos profesionales para mantener convenciones de nombres claras y consistentes con el diseño de tu base de datos.