1:n:1

Typical examples for this kind of relations are company → contract → employee, Order → Orderposition → Article. The Hibernate reference uses the term ternary association for this kind of relation. There are three approaches to map this relation.

ClassesTables

images/one2n2one.jpg

images/one2n2one_table.jpg

Simple way

Use a 1:n relation and a second n:1 relation. You can use the examples we provided before. Map-key-many-to-many

A nice way is a special kind of mapping using a map. We will use company → employee → contract as an example. Employee will be used as a key to get the contract from a java.util.Map. Full source code is provided in the package: de.laliluna.relation.ternary

Annotation mapping. 

import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import org.hibernate.annotations.MapKeyManyToMany;
.......... snip ..........
@Entity
public class Company implements Serializable{

   @OneToMany(cascade=CascadeType.ALL)
   @JoinTable(name="company_contract",
      joinColumns={@JoinColumn(name="company_id")})
   @MapKeyManyToMany(joinColumns={@JoinColumn(name="workaholic_id")})
   private Map<Workaholic,Contract> contracts =
      new HashMap<Workaholic,Contract>();

Neither Workaholic, nor Contract have any annotation related to the relation. The @JoinTable is in fact obsolete, as it describes the default values. @MapKeyManyToMany(joinColumns={@JoinColumn(name="workaholic_id")}) is in fact the magic bringing the ternary relation. It defines that the key of the map is referenced by the workaholic_id column. The key of hour map is workaholic.

XML mapping of Company. 

<hibernate-mapping package="de.laliluna.relation.ternary">
  <class name="Company" table="tcompany" >
...... snip .....
    <map name="contracts">
    <key column="company_id"></key>
    <map-key-many-to-many column="workaholic_id" class="Workaholic"/>
    <one-to-many class="Contract"/>
    </map>
  </class>
</hibernate-mapping>

Neither Contract nor Workaholic have any relation specific tags in their mapping file. The created tables differ slightly, as the annotation requires a join table.

CREATE TABLE tcompany
(
  id int4 NOT NULL,
  name varchar(255),
  PRIMARY KEY (id)
) ;
CREATE TABLE tworkaholic
(
  id int4 NOT NULL,
  name varchar(255),
  PRIMARY KEY (id)
) ;
Annotation version:
CREATE TABLE annotation.contract
(
  id int4 NOT NULL,
  name varchar(255),
  PRIMARY KEY (id)
) ;
CREATE TABLE company_contract
(
  company_id int4 NOT NULL,
  contracts_id int4 NOT NULL,
  workaholic_id int4 NOT NULL,
  CONSTRAINT company_contract_pkey PRIMARY KEY (company_id, workaholic_id),
  CONSTRAINT fkc6d16ad43c5add7b FOREIGN KEY (contracts_id)
      REFERENCES contract (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fkc6d16ad48c60df4a FOREIGN KEY (workaholic_id)
      REFERENCES workaholic (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fkc6d16ad4da4faaaa FOREIGN KEY (company_id)
      REFERENCES company (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT company_contract_contracts_id_key UNIQUE (contracts_id)
)
XML version:
CREATE TABLE tcontract
(
  id int4 NOT NULL,
  name varchar(255),
  company_id int4,
  workaholic_id int4,
 PRIMARY KEY (id),
  FOREIGN KEY (workaholic_id)
      REFERENCES tworkaholic (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  FOREIGN KEY (company_id)
      REFERENCES tcompany (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
) ;

Usage samples:

/* create and set relation */
Workaholic workaholic1 = new Workaholic("Karl");
Workaholic workaholic2 = new Workaholic("Susi");

Company company = new Company("exploiter international");
Contract contract1 = new Contract("slave 123");
Contract contract2 = new Contract("no holiday");
session.save(workaholic1);
session.save(contract1);
session.save(workaholic2);
session.save(contract2);
company.getContracts().put(workaholic1, contract1);
company.getContracts().put(workaholic2, contract2);
session.save(company);

/* find company of a contract */
Company company = (Company) session.createQuery
   ("select c from Company c left join c.contracts cr where cr.id = ?“)
      .setInteger(0,id).uniqueResult();

However, there are however some aspects you have to consider. If you change a field of the Workaholic and try to access the contract directly after your change, you will not be lucky.

Workaholic.setName("Udo");
Contract c = (Contract) company.getContracts().get(workaholic);

Keep in mind that you are working with a map. Changing a field affects the hashCode. So do not change the map key! Component

You can use a component mapping to achieve this. The example for this kind of mapping can be found in chapter xref:RefComposition13An3A1