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.
Classes | Tables |
---|---|
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