CODE WITH SIBIN

Solving Real Problems with Real Code


Spring Data JPA: Using @Embeddable, @Embedded, and @ElementCollection

Introduction

This guide provides an in-depth look at three powerful annotations in Spring Data JPA that help model complex entity relationships and value objects: @Embeddable@Embedded, and @ElementCollection.

1. @Embeddable and @Embedded

Concept

The @Embeddable and @Embedded annotations allow you to model composition relationships where one object is logically part of another, but doesn't need its own identity.

@Embeddable

Use @Embeddable to mark a class whose instances are stored as part of an owning entity.

@Embeddable
public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;
    
    // constructors, getters, setters
}

@Embedded

Use @Embedded in your entity to include an embeddable object:

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @Embedded
    private Address address;
    
    // constructors, getters, setters
}

Customizing Column Names

You can override column names from the embeddable:

@Entity
public class Customer {
    // ...
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "home_street")),
        @AttributeOverride(name = "city", column = @Column(name = "home_city"))
    })
    private Address homeAddress;
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "work_street")),
        @AttributeOverride(name = "city", column = @Column(name = "work_city"))
    })
    private Address workAddress;
}

Best Practices

  1. Embeddable classes should be simple value objects
  2. They shouldn't have their own identity (@Id)
  3. They should represent a logical part of the owning entity
  4. Consider making them immutable

2. @ElementCollection

Concept

@ElementCollection allows you to map a collection of simple types or embeddables without creating a separate entity.

Basic Usage with Simple Types

@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    @ElementCollection
    @CollectionTable(name = "product_tags", joinColumns = @JoinColumn(name = "product_id"))
    @Column(name = "tag")
    private Set<String> tags = new HashSet<>();
}

With Embeddable Types

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String username;
    
    @ElementCollection
    @CollectionTable(name = "user_phone_numbers", joinColumns = @JoinColumn(name = "user_id"))
    private Set<PhoneNumber> phoneNumbers = new HashSet<>();
}

@Embeddable
public class PhoneNumber {
    private String type; // home, work, mobile
    private String number;
    
    // constructors, getters, setters
}

Customizing the Collection Table

@ElementCollection
@CollectionTable(
    name = "user_emails",
    joinColumns = @JoinColumn(name = "user_id"),
    uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "email_type"})
)
@MapKeyColumn(name = "email_type")
@Column(name = "email_address")
private Map<String, String> emails = new HashMap<>();

Fetch Strategies

// Default is LAZY for ElementCollections
@ElementCollection(fetch = FetchType.EAGER)
private Set<String> tags;

// For better performance with large collections
@ElementCollection
@BatchSize(size = 10)
private Set<PhoneNumber> phoneNumbers;

Best Practices

  1. Use for simple value collections that don't need their own identity
  2. Prefer LAZY loading for performance
  3. Consider @OneToMany for more complex relationships
  4. Be mindful of performance with large collections

3. Advanced Topics

Nested Embeddables

@Embeddable
public class Address {
    private String street;
    private String city;
    
    @Embedded
    private Coordinates coordinates;
}

@Embeddable
public class Coordinates {
    private Double latitude;
    private Double longitude;
}

Collections of Collections

JPA doesn't directly support collections of collections, but you can work around this:

@Embeddable
public class ContactInfo {
    @ElementCollection
    private Set<String> emails;
    
    @ElementCollection
    private Set<PhoneNumber> phoneNumbers;
}

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    @Embedded
    private ContactInfo contactInfo;
}

Immutable Embeddables

@Embeddable
public class Address {
    @Column(nullable = false)
    private final String street;
    
    @Column(nullable = false)
    private final String city;
    
    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
    
    // Only getters, no setters
}

Performance Considerations

  1. ElementCollections can lead to N+1 query problems
  2. Consider using @Entity with @OneToMany for large collections
  3. Use @BatchSize to mitigate performance issues
  4. For read-heavy scenarios, consider denormalization

4. Comparison Table

Feature@Embeddable/@Embedded@ElementCollection
PurposeSingle object compositionCollection of values
Database StructureColumns in owner tableSeparate table
IdentityNo own identityNo own identity
QueryingPart of ownerSeparate queries
PerformanceGenerally goodWatch for N+1

5. Common Pitfalls and Solutions

  • Modifying collections: Always modify collections through getters to ensure proper tracking
// Bad - changes might not be tracked
user.getPhoneNumbers().add(new PhoneNumber());

// Good
Set<PhoneNumber> numbers = user.getPhoneNumbers();
numbers.add(new PhoneNumber());
user.setPhoneNumbers(numbers);
  • Equals/HashCode: Implement properly in embeddables
@Embeddable
public class Address {
    // ...
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) &&
               Objects.equals(city, address.city);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(street, city);
    }
}
  • Null handling: Consider making embeddables non-nullable
@Embedded
@NotNull
private Address address;

6. Real-World Examples

Example 1: E-Commerce Domain

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    
    @Embedded
    private Money total;
    
    @ElementCollection
    @CollectionTable(name = "order_items", joinColumns = @JoinColumn(name = "order_id"))
    private List<OrderItem> items;
}

@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;
}

@Embeddable
public class OrderItem {
    private String productId;
    private String productName;
    private int quantity;
    @Embedded
    private Money price;
}

Example 2: User Profile

@Entity
public class UserProfile {
    @Id
    @GeneratedValue
    private Long id;
    
    @Embedded
    private PersonalInfo personalInfo;
    
    @ElementCollection
    @CollectionTable(name = "user_skills", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "skill")
    private Set<String> skills;
}

@Embeddable
public class PersonalInfo {
    private String firstName;
    private String lastName;
    
    @Embedded
    private Address address;
    
    @ElementCollection
    @CollectionTable(name = "user_contact_methods", joinColumns = @JoinColumn(name = "user_id"))
    private Set<ContactMethod> contactMethods;
}

@Embeddable
public class ContactMethod {
    private String type;
    private String value;
    private boolean preferred;
}

Conclusion

The @Embeddable@Embedded, and @ElementCollection annotations provide powerful tools for modeling complex domain objects in Spring Data JPA:

  • Use @Embeddable/@Embedded for value objects that are logically part of an entity
  • Use @ElementCollection for simple collections of values or embeddables
  • Be mindful of performance implications, especially with large collections
  • Properly implement equals/hashCode for embeddables
  • Consider immutability for value objects

These annotations help create a more expressive domain model while maintaining good database design principles.

Leave a Reply

Your email address will not be published. Required fields are marked *