(Hibernate Document Notes)
Mapping Types
Value Types
- Basic Types
- Embeddable types
- Collection Types
Entity Types
@Table(name = "phone")
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Basic // this is a default
@Column(name = "phone_number")
private String number;
@Enumerated(EnumType.ORDINAL)
@Column(name = "phone_type")
private PhoneType type;
@Column(name = "`timestamp`")
@Temporal(TemporalType.DATE)
private Date timestamp;
}
@Target
and @Parent mapping
The @Target
annotation
is used to specify the implementation class of a given association that is
mapped via an interface. The @ManyToOne
, @OneToOne
, @OneToMany
,
and @ManyToMany
feature
a targetEntity
attribute
to specify the actual class of the entity association when an interface is
used for the mapping.
public interface Coordinates {
double x();
double y();
}
@Embeddable
public static class GPS implements Coordinates {
...
}
@Entity(name = "City")
public static class City {
@Id
@GeneratedValue
private Long id;
@Embedded
@Target( GPS.class )
private Coordinates coordinates;
...
}
The Hibernate-specific @Parent
annotation allows you to reference the owner entity
from within an embeddable.
@Embeddable
public static class GPS {
…
@Parent
private City city;
private GPS() {
}
...
}
@Entity(name = "City")
public static class City {
@Id
@GeneratedValue
private Long id;
...
@Embedded
@Target( GPS.class )
private Coordinates coordinates;
...
}
Associations
@ManyToOne
It is the most common association,
having a direct equivalent in the relational database association of foreign
key.
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
...
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
@ManyToOne
@JoinColumn(name = "person_id",
foreignKey = @ForeignKey(name = "PERSON_ID_FK")
)
private Person person;
…
}
@OneToMany
Unidirectional @OneToMany
For an
unidirectional @OneToMany association, Hibernate uses a link table between
the two joining entities.
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
...
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
public Phone() {
}
...
}
Bidirectional @OneToMany
The bidirectional @OneToMany
association
also requires a @ManyToOne
association on the child side.
Although the Domain Model exposes two sides to navigate this association,
behind the scenes, the relational database has only one foreign key for this
relationship.
Every
bidirectional association must have one owning side only (the child side), the
other one being referred to as the inverse (or
the mappedBy
) side.
Parent = mappedBy side = inverse side = One side
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL,
orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public List<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
phones.add( phone );
phone.setPerson( this );
}
public void removePhone(Phone phone) {
phones.remove( phone );
phone.setPerson( null );
}
…
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@NaturalId
@Column(name = "`number`", unique = true)
private String number;
@ManyToOne
private Person person;
// @ManyToOne( fetch = FetchType.LAZY )
// @NotFound ( action = NotFoundAction.IGNORE )
// @JoinColumn(
// name = "firstName",
// referencedColumnName = "first_name",
// insertable = false,
// updatable = false
// private
Person
person;
)
…
}
Whenever a bidirectional association is formed, the
application developer must make sure both sides are in-sync at all times. The
addPhone() and removePhone() are utilities methods that synchronize both ends
whenever a child element is added or removed.
The unidirectional associations are not very efficient when
it comes to removing child entities. In this particular example, upon flushing
the persistence context, Hibernate deletes all database child entries and
reinserts the ones that are still found in the in-memory persistence context.
On the other hand, a bidirectional @OneToMany association is
much more efficient because the child entity controls the association.
@OneToOne
The @OneToOne
association can either be unidirectional or
bidirectional. A unidirectional association follows the relational database
foreign key semantics, the client-side owning the relationship. A bidirectional
association features a mappedBy
@OneToOne
parent side too.
Unidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
@OneToOne
@JoinColumn(name = "details_id")
private PhoneDetails details;
public Phone() {
}
...
}
@Entity(name = "PhoneDetails")
public static class PhoneDetails {
@Id
@GeneratedValue
private Long id;
...
}
Bidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
@OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
…
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
@Entity(name = "PhoneDetails")
public static class PhoneDetails {
@Id
@GeneratedValue
private Long id;
private String provider;
private String technology;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
…
}
@ManyToMany
Unidirectional @ManyToMany
Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Address> addresses = new ArrayList<>();
public Person() {
}
…
}
@Entity(name = "Address")
public static class Address {
@Id
@GeneratedValue
private Long id;
private String street;
@Column(name = "`number`")
private String number;
public Address() {
}
…
}
For @ManyToMany associations, the REMOVE entity state
transition doesn’t make sense to be cascaded because it will propagate beyond
the link table. Since the other side might be referenced by other entities on
the parent-side, the automatic removal might end up in a
ConstraintViolationException.
For example, if @ManyToMany(cascade = CascadeType.ALL) was defined
and the first person would be deleted, Hibernate would throw an exception
because another person is still associated with the address that’s being
deleted.
Bidirectional @ManyToMany
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Address> addresses = new ArrayList<>();
public Person() {
}
…
public List<Address> getAddresses() {
return addresses;
}
public void addAddress(Address address) {
addresses.add( address );
address.getOwners().add( this );
}
public void removeAddress(Address address) {
addresses.remove( address );
address.getOwners().remove( this );
}
…
}
@Entity(name = "Address")
public static class Address {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
@ManyToMany(mappedBy = "addresses")
private List<Person> owners = new ArrayList<>();
public Address() {
}
…
}
If a bidirectional @OneToMany
association
performs better when removing or changing the order of child elements,
the @ManyToMany
relationship cannot benefit from such
an optimization because the foreign key side is not in control. To overcome
this limitation, the link table must be directly exposed and the @ManyToMany
association
split into two bidirectional @OneToMany
relationships.
Bidirectional many-to-many with a link entity
To most natural @ManyToMany
association
follows the same logic employed by the database schema, and the link table has
an associated entity which controls the relationship for both sides that need
to be joined.
@Entity(name = "Person")
public static class Person implements Serializable {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL,
orphanRemoval = true)
private List<PersonAddress> addresses = new ArrayList<>();
public Person() {
}
…
public List<PersonAddress> getAddresses() {
return addresses;
}
public void addAddress(Address address) {
PersonAddress personAddress = new PersonAddress( this, address );
addresses.add( personAddress );
address.getOwners().add( personAddress );
}
public void removeAddress(Address address) {
PersonAddress personAddress = new PersonAddress( this, address );
address.getOwners().remove( personAddress );
addresses.remove( personAddress );
personAddress.setPerson( null );
personAddress.setAddress( null );
}
…
}
@Entity(name = "PersonAddress")
public static class PersonAddress implements Serializable {
@Id
@ManyToOne
private Person person;
@Id
@ManyToOne
private Address address;
…
}
@Entity(name = "Address")
public static class Address implements Serializable {
@Id
@GeneratedValue
private Long id;
private String street;
@Column(name = "`number`")
private String number;
private String postalCode;
@OneToMany(mappedBy = "address", cascade = CascadeType.ALL, orphanRemoval = true) // no set method for this field!
private List<PersonAddress> owners = new ArrayList<>();
public Address() {
}
public Address(String street, String number, String postalCode) {
this.street = street;
this.number = number;
this.postalCode = postalCode;
}
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public String getNumber() {
return number;
}
public String getPostalCode() {
return postalCode;
}
public List<PersonAddress> getOwners() {
return owners;
}
@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( number, address.number ) &&
Objects.equals( postalCode, address.postalCode );
}
@Override
public int hashCode() {
return Objects.hash( street, number, postalCode );
}
}
Collections of Value Types
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@ElementCollection
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
…
}
@Embeddable
public static class Phone {
…
}
Bags, Ordered Lists, Sets, Sorted Sets
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
// @OrderBy("number") // for Ordered Lists
private List<Phone> phones = new ArrayList<>();
// private Set<Phone> phones = new HashSet<>(); // for Sets
// @SortNatural // for Sorted Sets
// private SortedSet<Phone> phones = new TreeSet<>();
…
}
@Entity(name = "Phone")
public static class Phone {
…
}
Arrays
Hibernate does support the mapping of
arrays in the Java domain model - conceptually the same as mapping a List.
However, it is important to realize that it is impossible for Hibernate to
offer lazy-loading for arrays of entities and, for this reason, it is strongly
recommended to map a "collection" of entities using a List rather
than an array.
Maps
public enum PhoneType {
LAND_LINE,
MOBILE
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@Temporal(TemporalType.TIMESTAMP)
@ElementCollection
@CollectionTable(name = "phone_register")
@Column(name = "since")
@MapKeyJoinColumn(name = "phone_id", referencedColumnName = "id")
private Map<Phone, Date> phoneRegister = new HashMap<>();
public Person() {}
public Person(Long id) {
this.id = id;
}
public Map<Phone, Date> getPhoneRegister() {
return phoneRegister;
}
}
@Embeddable
public static class Phone {
…
}
create table call_register (
person_id int8 not null,
phone_number int4,
call_timestamp_epoch int8 not null,
primary key (person_id, call_key)
)
@Entity
@Table(name = "person")
public static class Person {
@Id
private Long id;
@ElementCollection
@CollectionTable(
name = "call_register",
joinColumns = @JoinColumn(name = "person_id")
)
@MapKeyType(
@Type(
type = "org.hibernate.userguide.collections.type.TimestampEpochType"
)
)
@MapKeyColumn( name = "call_timestamp_epoch" )
@Column(name = "phone_number")
private Map<Date, Integer> callRegister = new HashMap<>();
public void setId(Long id) {
this.id = id;
}
public Map<Date, Integer> getCallRegister() {
return callRegister;
}
}
Unidirectional maps
A unidirectional map
exposes a parent-child association from the parent-side only.
public enum PhoneType {
LAND_LINE,
MOBILE
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true)
@JoinTable(
name = "phone_register",
joinColumns = @JoinColumn(name = "phone_id"),
inverseJoinColumns = @JoinColumn(name = "person_id"))
@MapKey(name = "since")
@MapKeyTemporal(TemporalType.TIMESTAMP)
private Map<Date, Phone> phoneRegister = new HashMap<>();
…
}
@Entity(name = "Phone")
public static class Phone {
…
}
Bidirectional maps
@Entity(name = "Person")
public static class Person {
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
@MapKey(name = "type")
@MapKeyEnumerated
private Map<PhoneType, Phone> phoneRegister = new HashMap<>();
…
}
@Entity(name = "Phone")
public static class Phone {
…
@ManyToOne
private Person person;
…
}