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
.