In this article, You’ll learn how to map a collection of basic as well as embeddable types using JPA’s @ElementCollection
and @CollectionTable
annotations.
Let’s say that the users of your application can have multiple phone numbers and addresses. To map this requirement into the database schema, you need to create separate tables for storing the phone numbers and addresses -
Both the tables user_phone_numbers
and user_addresses
contain a foreign key to the users
table.
You can implement such relationship at the object level using JPA’s one-to-many mapping. But for basic and embeddable types like the one in the above schema, JPA has a simple solution in the form of ElementCollection.
Let’s create a project from scratch and learn how to use an ElementCollection to map the above schema in your application using hibernate.
Creating the Application
If you have Spring Boot CLI installed, then simply type the following command in your terminal to generate the application -
spring init -n=jpa-element-collection-demo -d=web,jpa,mysql --package-name=com.example.jpa jpa-element-collection-demo
Alternatively, You can use Spring Initializr web app to generate the application by following the instructions below -
- Open http://start.spring.io
- Enter Artifact as “jpa-element-collection-demo”
- Click Options dropdown to see all the options related to project metadata.
- Change Package Name to “com.example.jpa”
- Select Web, JPA and Mysql dependencies.
- Click Generate to generate and download the project.
Configuring MySQL database and Hibernate Log levels
Let’s first configure the database URL, username, password, hibernate log levels and other properties.
Open src/main/resources/application.properties
and add the following properties to it -
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_element_collection_demo?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username=root
spring.datasource.password=root
# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Please change spring.datasource.username
and spring.datasource.password
as per your MySQL installation. Also, create a database named jpa_element_collection_demo
before proceeding to the next section.
Defining the Entity classes
Next, We’ll define the Entity classes that will be mapped to the database tables we saw earlier.
Before defining the User
entity, let’s first define the Address
type which will be embedded inside the User
entity.
All the domain models will go inside the package com.example.jpa.model
.
1. Address - Embeddable Type
package com.example.jpa.model;
import javax.persistence.Embeddable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Embeddable
public class Address {
@NotNull
@Size(max = 100)
private String addressLine1;
@NotNull
@Size(max = 100)
private String addressLine2;
@NotNull
@Size(max = 100)
private String city;
@NotNull
@Size(max = 100)
private String state;
@NotNull
@Size(max = 100)
private String country;
@NotNull
@Size(max = 100)
private String zipCode;
public Address() {
}
public Address(String addressLine1, String addressLine2, String city,
String state, String country, String zipCode) {
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
this.city = city;
this.state = state;
this.country = country;
this.zipCode = zipCode;
}
// Getters and Setters (Omitted for brevity)
}
2. User Entity
Let’s now see how we can map a collection of basic types (phone numbers) and embeddable types (addresses) using hibernate -
package com.example.jpa.model;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(max = 100)
private String name;
@NotNull
@Email
@Size(max = 100)
@Column(unique = true)
private String email;
@ElementCollection
@CollectionTable(name = "user_phone_numbers", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "phone_number")
private Set<String> phoneNumbers = new HashSet<>();
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "user_addresses", joinColumns = @JoinColumn(name = "user_id"))
@AttributeOverrides({
@AttributeOverride(name = "addressLine1", column = @Column(name = "house_number")),
@AttributeOverride(name = "addressLine2", column = @Column(name = "street"))
})
private Set<Address> addresses = new HashSet<>();
public User() {
}
public User(String name, String email, Set<String> phoneNumbers, Set<Address> addresses) {
this.name = name;
this.email = email;
this.phoneNumbers = phoneNumbers;
this.addresses = addresses;
}
// Getters and Setters (Omitted for brevity)
}
We use @ElementCollection
annotation to declare an element-collection mapping. All the records of the collection are stored in a separate table. The configuration for this table is specified using the @CollectionTable
annotation.
The @CollectionTable
annotation is used to specify the name of the table that stores all the records of the collection, and the JoinColumn
that refers to the primary table.
Moreover, When you’re using an Embeddable type with Element Collection, you can use the @AttributeOverrides
and @AttributeOverride
annotations to override/customize the fields of the embeddable type.
Defining the Repository
Next, Let’s create the repository for accessing the user’s data from the database. You need to create a new package called repository
inside com.example.jpa
package and add the following interface inside the repository
package -
package com.example.jpa.repository;
import com.example.jpa.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Testing the ElementCollection Setup
Finally, Let’s write the code to test our setup in the main class JpaElementCollectionDemoApplication.java
-
package com.example.jpa;
import com.example.jpa.model.Address;
import com.example.jpa.model.User;
import com.example.jpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.HashSet;
import java.util.Set;
@SpringBootApplication
public class JpaElementCollectionDemoApplication implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(JpaElementCollectionDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Cleanup database tables.
userRepository.deleteAll();
// Insert a user with multiple phone numbers and addresses.
Set<String> phoneNumbers = new HashSet<>();
phoneNumbers.add("+91-9999999999");
phoneNumbers.add("+91-9898989898");
Set<Address> addresses = new HashSet<>();
addresses.add(new Address("747", "Golf View Road", "Bangalore",
"Karnataka", "India", "560008"));
addresses.add(new Address("Plot No 44", "Electronic City", "Bangalore",
"Karnataka", "India", "560001"));
User user = new User("Rajeev Kumar Singh", "rajeev@callicoder.com",
phoneNumbers, addresses);
userRepository.save(user);
}
}
The main class implements the CommandLineRunner
interface and provides the implementation of its run()
method.
The run()
method is executed post application startup. In the run()
method, we first cleanup all the tables and then insert a new user with multiple phone numbers and addresses.
You can run the application by typing mvn spring-boot:run
from the root directory of the project.
After running the application, Go ahead and check all the tables in MySQL. The users
table will have a new entry, the user_phone_numbers
and user_addresses
table will have two new entries -
mysql> select * from users;
+----+-----------------------+--------------------+
| id | email | name |
+----+-----------------------+--------------------+
| 3 | rajeev@callicoder.com | Rajeev Kumar Singh |
+----+-----------------------+--------------------+
1 row in set (0.01 sec)
mysql> select * from user_phone_numbers;
+---------+----------------+
| user_id | phone_number |
+---------+----------------+
| 3 | +91-9898989898 |
| 3 | +91-9999999999 |
+---------+----------------+
2 rows in set (0.00 sec)
mysql> select * from user_addresses;
+---------+--------------+-----------------+-----------+---------+-----------+----------+
| user_id | house_number | street | city | country | state | zip_code |
+---------+--------------+-----------------+-----------+---------+-----------+----------+
| 3 | 747 | Golf View Road | Bangalore | India | Karnataka | 560008 |
| 3 | Plot No 44 | Electronic City | Bangalore | India | Karnataka | 560001 |
+---------+--------------+-----------------+-----------+---------+-----------+----------+
2 rows in set (0.00 sec)
Conclusion
That’s all in this article Folks. I hope you learned how to use hibernate’s element-collection with Spring Boot.
You can find the entire source code for the project in my hibernate-tutorials github repository.
Thanks for reading guys. Happy Coding! :)