Thursday, October 26, 2017

Hibernate Association Mapping

(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.
The @ElementCollection association has a targetClass attribute for the same purpose.

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;

}