Computer scienceBackendSpring BootSpring DataSpring Data JPAJPA Entities

Embeddable and ElementCollection

8 minutes read

When developing backend applications, it's crucial to understand how to effectively manage and persist data. In this topic, we'll delve into two concepts that are vital to efficient data management: @Embeddable and @Embedded objects and @ElementCollection annotation. Essentially, they help to make your code cleaner and to use a more modular approach to data persistence, and allow developers to model complex data structures.

Structured data

Consider a situation where we have an entity class with multiple fields. For example, a class called Location that stores the name and geo-coordinates of a location:

Java
@Entity
public class Location {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String longitude;
    private String latitude;

    // getters and setters
}
Kotlin
@Entity
class Location(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long?,
    var name: String,
    var longitude: String,
    var latitude: String
)

Note that two fields, longitude and latitude, are related to each other and can be abstracted into a dedicated class:

Java
public class Coordinates {
    private String longitude;
    private String latitude;

    // getters and setters
}
Kotlin
class Coordinates(
    var longitude: String,
    var latitude: String
)

This gives us some benefits:

  • If we have multiple entities that need to store latitude and longitude values, we can reuse the Coordinates class in all of them, rather than duplicating the code for the latitude and longitude fields in each entity.

  • By encapsulating the longitude and latitude fields in a separate class, we group related fields together and treat them as a single data object.

  • If we need to change the implementation of how longitude and latitude are stored or calculated, we can make the changes in the Coordinates class, and all the entities that use it will automatically pick up the changes.

  • Defining a separate class for the longitude and latitude fields, we can enforce type safety and prevent errors that might occur if we were to represent the coordinates as separate String fields in each entity.

However, if we simply replace the longitude and latitude fields with a field of the Coordinates type, our application will crash because the Spring Framework will not be able to correctly handle the Coordinates class inside a JPA entity.

Embeddable and Embedded

To fix this problem, we can tell the Spring Data to embed the Coordinates object into the Location entity. To do this, we should annotate the Coordinates class as @Embeddable and the corresponding coordinates field as @Embedded:

Java
@Embeddable
public class Coordinates {
    private String longitude;
    private String latitude;

    // getters and setters
}

@Entity
public class Location {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @Embedded
    private Coordinates coordinates;

    // getters and setters
}
Kotlin
@Embeddable
class Coordinates(
    var longitude: String,
    var latitude: String
)

@Entity
class Location(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long?,
    var name: String,

    @Embedded
    var coordinates: Coordinates
)

The @Embeddable annotation informs the Spring Data that the Coordinates class isn't a JPA entity in itself but can be embedded in other entities. The @Embedded annotation indicates that the specified embeddable object is to be persisted as part of the parent entity.

Hibernate is the default JPA implementation used in Spring Data, and it allows us to omit either the @Embeddable or @Embedded annotation if one of them is present. Hibernate will understand that if an entity has a field of a type annotated as @Embeddable, it should be embedded. Similarly, if a field of a certain type is annotated as @Embedded, Hibernate will treat the corresponding class as @Embeddable.

Keep in mind that these objects exist solely within the context of the parent entity. If we look at the table generated by Spring Data for the Location class, we will see that it stores all its fields in a single table, together with the fields of the embedded object:

spring jpa embedded class table

ElementCollection

Let's enhance the Location class and add a field for storing hashtags associated with it. Since there may be many hashtags for one location, we will store them as a set of strings:

Java
@Entity
public class Location {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @Embedded
    private Coordinates coordinates;

    @ElementCollection
    private Set<String> tags;

    // getters and setters
}
Kotlin
@Entity
class Location(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long?,
    var name: String,

    @Embedded
    var coordinates: Coordinates,

    @ElementCollection
    var hashtags: Set<String>
)

Notice that the field tags is annotated with @ElementCollection. This annotation is used to define a collection of standard wrapper types or strings or composite embeddable objects within an entity. This eliminates the need to create separate entities and configure their relations to handle these simple collections.

The @ElementCollection can be used with standard library types like String, Integer or with embeddable objects annotated as @Embeddable. The tags field shows an example of using the @ElementCollection annotation with a basic type like Set<String>. We can also use @ElementCollection with embeddable types to create more complex data structures. Here's an example of using this annotation with an embeddable object:

Java
@Embeddable
public class Rating {
    private String username;
    private int stars;

    // getters and setters
}

@Entity
public class Location {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @Embedded
    private Coordinates coordinates;

    @ElementCollection
    private Set<String> tags;

    @ElementCollection
    private Set<Rating> ratings;

    // getters and setters
}
Kotlin
@Embeddable
class Rating(
    var username: String,
    var stars: Int
)

@Entity
class Location(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long?,
    var name: String,

    @Embedded
    var coordinates: Coordinates,

    @ElementCollection
    var hashtags: Set<String>,

    @ElementCollection
    var ratings: Set<Rating>
)

In this example, Spring Data JPA creates separate tables for each collection and uses the parent entity id as a foreign key:

spring jpa element collection tables

By default, the fetch type of an element collection is lazy, which means that the collection is not eagerly fetched unless requested. We can change it via the relevant property in the @ElementCollection, for example:

Java
@ElementCollection(fetch = FetchType.EAGER)
private Set<Rating> ratings;
Kotlin
@ElementCollection(fetch = FetchType.EAGER)
var ratings: Set<Rating>

The @ElementCollection annotation is a useful feature in JPA for mapping complex data structures. It allows us to store collections of values as separate entities, without having to create an additional entity class. Its primary advantages include simplicity and improved readability of code.

Conclusion

Embeddable and embedded objects are useful for creating modular, reusable components that can be easily integrated into larger entities. By leveraging the @Embeddable and @Embedded annotations, we can maintain cleaner code by decomposing entities into smaller parts. These objects exist solely within the context of the parent entity, resulting in a more streamlined persistence approach.

The @ElementCollection annotation in JPA allows us to store collections of standard library wrapper types or strings or embeddable objects within an entity, without having to create separate entities and define their relations. We can use it to model complex data structures in a simple and reliable way.

46 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo