We will model a user with a primary key, a name, a personal email address and a work email address; using Value Objects for everything.
The entity looks like this:
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
@Entity
public class EmbeddableUser {
@EmbeddedId
private UserId id;
private NaturalPersonName name;
@Embedded
@AttributeOverrides(@AttributeOverride(name = "value", column = @Column(name = "personal_email")))
private Email personalEmail;
@Embedded
@AttributeOverrides(@AttributeOverride(name = "value", column = @Column(name = "work_email")))
private Email workEmail;
protected EmbeddableUser() {
}
public EmbeddableUser(UserId id,
NaturalPersonName name,
Email personalEmail,
Email workEmail) {
this.id = id;
this.name = name;
this.personalEmail = personalEmail;
this.workEmail = workEmail;
}
// getters here
}
Notice how we use UserId, NaturalPersonName and Email as value objects. Our class also has an empty protected constructor since Hibernate needs that.
To make it all work with the value objects, we have the following code in those objects:
-
The UserId class:
import javax.persistence.Column;
import java.io.Serializable;
public class UserId implements Serializable { (1)
@Column(name = "id") (2)
private Long value;
protected UserId() { (3)
}
public UserId(Long value) {
this.value = value;
}
public Long getValue() {
return value;
}
}
| 1 |
Need to implement Serializable to serve as primary key |
| 2 |
We want to have a default column name of id for each entity that uses this class as a primary key |
| 3 |
Hibernate needs a protected constructor |
-
The NaturalPersonaName class:
import javax.persistence.Embeddable;
@Embeddable
public class NaturalPersonName {
private String givenName;
private String familyName;
protected NaturalPersonName() {
}
public NaturalPersonName(String givenName,
String familyName) {
this.givenName = givenName;
this.familyName = familyName;
}
public String getGivenName() {
return givenName;
}
public String getFamilyName() {
return familyName;
}
}
Nothing special needed here, just the @Embeddable annotation on the class level.
When the entity is written to the database, the field names used here will be used as column names.
-
The Email class:
@Embeddable
public class Email {
private String value;
protected Email() {
}
public Email(String value) {
Assert.hasText(value, "value should have text"); (1)
this.value = value;
}
public String getValue() {
return value;
}
}
| 1 |
This should use regex or a library to validate that the passed in String is a valid email address, but this is not important for this blog post. |
The database schema that matches with these classes is:
CREATE TABLE embeddable_user
(
id BIGINT NOT NULL,
family_name VARCHAR(255),
given_name VARCHAR(255),
personal_email VARCHAR(255),
work_email VARCHAR(255),
PRIMARY KEY (id)
);
I want to draw the attention to the email fields in the User entity code:
@Embedded
@AttributeOverrides(@AttributeOverride(name = "value", column = @Column(name = "personal_email")))
private Email personalEmail;
@Embedded
@AttributeOverrides(@AttributeOverride(name = "value", column = @Column(name = "work_email")))
private Email workEmail;
Note how we need to add AttributeOverrides to specify a different column name for both.
Without that, we would get this exception:
Repeated column in mapping for entity: EmbeddableUser column: value
There is no column value in EmbeddableUser you might think, but there is, because Email has a value field.
So Hibernate complains because both personalEmail and workEmail would be mapped to the same database column value.