3 关联映射
3.1 单向ManyToOne关联
这是实体间最基本、最重要的关联,在数据库表间建立外键关系。理论上可以只使用ManyToOne关联而不用其他关联来实现整个应用;另一方向的关联可以通过查询(query)实现。
Employee类和Company类是ManyToOne的关系,例子如下:
@Entity

public class Company1 ...{
@Id private int id;
private String companyName;
}

@Entity

public class Employee1 ...{
@Id private int id;
private String name;
@ManyToOne(optional = false)
private Company1 company;
}

数据库表如下:
create table "COMPANY1"(
"ID" INTEGER not null,
"COMPANYNAME" VARCHAR(255),
constraint "SYS_PK_1992" primary key ("ID")
);
create unique index "SYS_PK_1992" on "COMPANY1"("ID");

create table "EMPLOYEE1"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
"COMPANY_ID" INTEGER not null,
constraint "SYS_PK_2006" primary key ("ID")
);
alter table "EMPLOYEE1"
add constraint "FK14AC0F233D3817C"
foreign key ("COMPANY_ID")
references "COMPANY1"("ID");
create unique index "SYS_PK_2006" on "EMPLOYEE1"("ID");
create index "SYS_IDX_2075" on "EMPLOYEE1"("COMPANY_ID");



































3.2 单向OneToMany关联
OneToMany是涉及到集合的最重要的关联,可以采用Set、Collection和List来表示集合。
Set实现集合的例子如下:
@Entity

public class Employee2 ...{
@Id private int id;
private String name;
}

@Entity

public class Company2 ...{
@Id private int id;
private String companyName;
@OneToMany private Set<Employee2> employees = new HashSet();
}

数据库表如下:
create table "EMPLOYEE2"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
constraint "SYS_PK_2007" primary key ("ID")
);
create unique index "SYS_PK_2007" on "EMPLOYEE2"("ID");

create table "COMPANY2"(
"ID" INTEGER not null,
"COMPANYNAME" VARCHAR(255),
constraint "SYS_PK_1993" primary key ("ID")
);
create unique index "SYS_PK_1993" on "COMPANY2"("ID");

create table "COMPANY2_EMPLOYEE2"(
"COMPANY2_ID" INTEGER not null,
"EMPLOYEES_ID" INTEGER not null,
constraint "SYS_PK_1995" primary key ("COMPANY2_ID","EMPLOYEES_ID")
);
alter table "COMPANY2_EMPLOYEE2"
add constraint "FK4BD4B2DA17FCDBA5"
foreign key ("COMPANY2_ID")
references "COMPANY2"("ID");
alter table "COMPANY2_EMPLOYEE2"
add constraint "FK4BD4B2DA1F5EA2AE"
foreign key ("EMPLOYEES_ID")
references "EMPLOYEE2"("ID");
create unique index "SYS_IDX_SYS_CT_1994_1996" on "COMPANY2_EMPLOYEE2"("EMPLOYEES_ID");
create unique index "SYS_PK_1995" on "COMPANY2_EMPLOYEE2"("COMPANY2_ID","EMPLOYEES_ID");
create index "SYS_IDX_2065" on "COMPANY2_EMPLOYEE2"("COMPANY2_ID");
create index "SYS_IDX_2067" on "COMPANY2_EMPLOYEE2"("EMPLOYEES_ID");














































采用Collection实现集合只是用Collection代替Set。如果采用List实现集合,集合中的元素包括位置信息,但是需要Hibernate特有的Annotation支持:
@Entity

public class Employee3 ...{
@Id private int id;
private String name;
}

@Entity

public class Company3 ...{
@Id private int id;
private String companyName;
@OneToMany
@org.hibernate.annotations.IndexColumn(name = "EMPLOYEE_POSITION")
private List<Employee3> employees = new ArrayList<Employee3>();
}

数据库表与上面的例子比,COMPANY3_EMPLOYEE3表中多了EMPLOYEE_POSITION字段。

















3.3 双向ManyToOne关联
如果需要实现双向关联,可以增加另一个方向的关联。
@Entity

public class Employee4 ...{
@Id private int id;
private String name;
@ManyToOne Company4 company;
}

@Entity

public class Company4 ...{
@Id private int id;
private String name;
@OneToMany(mappedBy="company")
private Set<Employee4> employees = new HashSet();
}

数据库表如下:
create table "COMPANY4"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
constraint "SYS_PK_2001" primary key ("ID")
);
create unique index "SYS_PK_2001" on "COMPANY4"("ID");

create table "EMPLOYEE4"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
"COMPANY_ID" INTEGER,
constraint "SYS_PK_2009" primary key ("ID")
);
alter table "EMPLOYEE4"
add constraint "FK14AC0F263D3817F"
foreign key ("COMPANY_ID")
references "COMPANY4"("ID");
create unique index "SYS_PK_2009" on "EMPLOYEE4"("ID");
create index "SYS_IDX_2077" on "EMPLOYEE4"("COMPANY_ID");




































需要注意的是,为了正确持久化,双方对象需要相互引用;mappedBy属性说明ManyToOne和OneToMany是镜像关系,如果没有该属性Hibernate可能会有不必要的更新。还有正确cascade属性,保证关联对象自动更新。
对于可选的Many关系(Zero或Many关系),如果不希望Employee类中的company字段可以是null,则可以通过@JoinTable采用中间表实现:






















































3.4 单向OneToOne关联
OneToOne关联不是常用的关联。一般地如User类和Address类采用值类型映射;如果Address类还被其他类关联,则Address类需要实现为实体。这时候User类和Address类就是OneToOne关联。
@Entity

public class Address6 ...{
@Id private int id;
private String street;
}

@Entity

public class User6 ...{
@Id private int id;
private String name;
@OneToOne private Address6 homeAddress;
}

数据库表如下:
create table "ADDRESS6"(
"ID" INTEGER not null,
"STREET" VARCHAR(255),
constraint "SYS_PK_1967" primary key ("ID")
);
create unique index "SYS_PK_1967" on "ADDRESS6"("ID");

create table "USER6"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
"HOMEADDRESS_ID" INTEGER,
constraint "SYS_PK_2026" primary key ("ID")
);
alter table "USER6"
add constraint "FK4E39DAB932C1432"
foreign key ("HOMEADDRESS_ID")
references "ADDRESS6"("ID");
create unique index "SYS_PK_2026" on "USER6"("ID");
create index "SYS_IDX_2085" on "USER6"("HOMEADDRESS_ID");


































OneToOne的一方如果是可选的,也就是说One to Zero或One。这种关联可以采用中间表方式实现,又可以分为JointTable和SecondaryTable方式。JoinTable方式例子如下:
@Entity

public class Address7 ...{
@Id private int id;
private String street;
}

@Entity

public class User7 ...{
@Id private int id;
private String name;
@OneToOne
@JoinTable(
name="User7_Address7",
joinColumns = @JoinColumn(name = "User_ID"),
inverseJoinColumns = @JoinColumn(name = "Address_ID")
)
private Address7 homeAddress;
}

数据库表如下:
create table "ADDRESS7"(
"ID" INTEGER not null,
"STREET" VARCHAR(255),
constraint "SYS_PK_1968" primary key ("ID")
);
create unique index "SYS_PK_1968" on "ADDRESS7"("ID");

create table "USER7"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
constraint "SYS_PK_2027" primary key ("ID")
);
create unique index "SYS_PK_2027" on "USER7"("ID");

create table "USER7_ADDRESS7"(
"USER_ID" INTEGER not null,
"ADDRESS_ID" INTEGER,
constraint "SYS_PK_2029" primary key ("USER_ID")
);
alter table "USER7_ADDRESS7"
add constraint "FKB6D07C1661646A14"
foreign key ("ADDRESS_ID")
references "ADDRESS7"("ID");
alter table "USER7_ADDRESS7"
add constraint "FKB6D07C16BC1189B0"
foreign key ("USER_ID")
references "USER7"("ID");
create unique index "SYS_PK_2029" on "USER7_ADDRESS7"("USER_ID");
create index "SYS_IDX_2087" on "USER7_ADDRESS7"("USER_ID");
create index "SYS_IDX_2089" on "USER7_ADDRESS7"("ADDRESS_ID");



















































SecondaryTable方式的例子如下:


















3.5 双向OneToOne关联
双向OneToOne关联采用外键关联,例子如下:




































3.6 单向ManyToMany关联
单向ManyToMany采用中间表实现,包括Set、Collection和List三种方式,其中Set中没有重复元素,Collection可以包括重复元素,List可以记录元素的位置。Collection和List方式都要用到Hibernate特有的Annotation。Set的例子如下:
@Entity

public class Item1 ...{
@Id private int id;
private String description;
}

@Entity

public class Category1 ...{
@Id private int id;
private String name;
@ManyToMany
@JoinTable(
name = "CATEGORY1_ITEM1",

joinColumns = ...{@JoinColumn(name = "CATEGORY_ID")},

inverseJoinColumns = ...{@JoinColumn(name = "ITEM_ID")}
)
private Set<Item1> items = new HashSet<Item1>();
}

数据库表如下:
create table "ITEM1"(
"ID" INTEGER not null,
"DESCRIPTION" VARCHAR(255),
constraint "SYS_PK_2011" primary key ("ID")
);
create unique index "SYS_PK_2011" on "ITEM1"("ID");

create table "CATEGORY1"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
constraint "SYS_PK_1985" primary key ("ID")
);
create unique index "SYS_PK_1985" on "CATEGORY1"("ID");

create table "CATEGORY1_ITEM1"(
"CATEGORY_ID" INTEGER not null,
"ITEM_ID" INTEGER not null,
constraint "SYS_PK_1977" primary key ("CATEGORY_ID","ITEM_ID")
);
alter table "CATEGORY1_ITEM1"
add constraint "FK43420332A218AA44"
foreign key ("CATEGORY_ID")
references "CATEGORY1"("ID");
alter table "CATEGORY1_ITEM1"
add constraint "FK434203324250AF1A"
foreign key ("ITEM_ID")
references "ITEM1"("ID");
create unique index "SYS_PK_1977" on "CATEGORY1_ITEM1"("CATEGORY_ID","ITEM_ID");
create index "SYS_IDX_2035" on "CATEGORY1_ITEM1"("ITEM_ID");
create index "SYS_IDX_2037" on "CATEGORY1_ITEM1"("CATEGORY_ID");





















































Collection的例子如下:




























List的例子如下:

























3.7 双向ManyToMany关联
双向ManyToMany关联最好采用两个OneToMany来实现。当然采用ManyToMany关联也是可以的。例子如下:
@Entity

public class Item4 ...{
@Id private int id;
private String description;
@ManyToMany(mappedBy = "items")
private Set<Category4> categories = new HashSet<Category4>();
}

@Entity

public class Category4 ...{
@Id private int id;
private String name;
@ManyToMany
@JoinTable(
name = "CATEGORY4_ITEM4",

joinColumns = ...{@JoinColumn(name = "CATEGORY_ID")},

inverseJoinColumns = ...{@JoinColumn(name = "ITEM_ID")}
)
private Set<Item4> items = new HashSet<Item4>();
}

数据库表和前面的例子类似,不再给出。

























如果在双向关联中,还有其他字段信息,则可以采用值对象JoinTable或中间实体JoinTable实现。下面的例子假定在Category类和Item类关联时还需要增加日期信息。值对象JoinTable方式相对简单,但是需要用到Hibernate特有的Annotation,例子如下:
@Entity

public class Item5 ...{
@Id private int id;
private String description;
}

@Embeddable

public class CategorizedItem5 ...{
@org.hibernate.annotations.Parent // Optional back-pointer
private Category5 category;
@ManyToOne
@JoinColumn(name = "ITEM_ID", nullable = false, updatable = false)
private Item5 item;
private Date dateAdded;

public Category5 getCategory() ...{ return category;}

public void setCategory(Category5 category) ...{ this.category = category; }
}

@Entity

public class Category5 ...{
@Id private int id;
private String name;
@org.hibernate.annotations.CollectionOfElements
@JoinTable(
name = "CategorizedItem5",
joinColumns = @JoinColumn(name = "CATEGORY_ID"))
private Set<CategorizedItem5> categorizedItems = new HashSet<CategorizedItem5>();
}

数据库表如下:
create table "ITEM5"(
"ID" INTEGER not null,
"DESCRIPTION" VARCHAR(255),
constraint "SYS_PK_2015" primary key ("ID")
);
create unique index "SYS_PK_2015" on "ITEM5"("ID");

create table "CATEGORY5"(
"ID" INTEGER not null,
"NAME" VARCHAR(255),
constraint "SYS_PK_1989" primary key ("ID")
);
create unique index "SYS_PK_1989" on "CATEGORY5"("ID");

create table "CATEGORIZEDITEM5"(
"CATEGORY_ID" INTEGER not null,
"ITEM_ID" INTEGER not null,
"DATEADDED" TIMESTAMP,
constraint "SYS_PK_1983" primary key ("CATEGORY_ID","ITEM_ID")
);
alter table "CATEGORIZEDITEM5"
add constraint "FKD6EE8B77A218AA48"
foreign key ("CATEGORY_ID")
references "CATEGORY5"("ID");
alter table "CATEGORIZEDITEM5"
add constraint "FKD6EE8B774250AF1E"
foreign key ("ITEM_ID")
references "ITEM5"("ID");
create unique index "SYS_PK_1983" on "CATEGORIZEDITEM5"("CATEGORY_ID","ITEM_ID");
create index "SYS_IDX_2061" on "CATEGORIZEDITEM5"("ITEM_ID");
create index "SYS_IDX_2063" on "CATEGORIZEDITEM5"("CATEGORY_ID");






































































中间实体JoinTable方式的例子如下:
@Entity

public class Item6 ...{
@Id private int id;
private String description;
@OneToMany(mappedBy = "item")
private Set<CategorizedItem6> categorizedItems = new HashSet<CategorizedItem6>();
}

@Entity

public class Category6 ...{
@Id private int id;
private String name;
@OneToMany(mappedBy = "category")
private Set<CategorizedItem6> categorizedItems = new HashSet<CategorizedItem6>();
}

@Entity

public class CategorizedItem6 ...{
@Embeddable

public static class Id implements Serializable ...{
@Column(name = "CATEGORY_ID")
private Long categoryId;
@Column(name = "ITEM_ID")
private Long itemId;

public Id() ...{}

public Id(Long categoryId, Long itemId) ...{
this.categoryId = categoryId;
this.itemId = itemId;
}

public boolean equals(Object o) ...{

if (o != null && o instanceof Id) ...{
Id that = (Id)o;
return this.categoryId.equals(that.categoryId) &&
this.itemId.equals(that.itemId);

} else ...{
return false;
}
}

public int hashCode() ...{
return categoryId.hashCode() + itemId.hashCode();
}
}
@EmbeddedId
private Id id = new Id();
private Date dateAdded = new Date();
@ManyToOne
@JoinColumn(name="ITEM_ID", insertable = false,updatable = false)
private Item6 item;
@ManyToOne
@JoinColumn(name="CATEGORY_ID", insertable = false, updatable = false)
private Category6 category;
}

数据库表和上面的例子类似,不再给出。































































3.8 三元关联
三元关联(三个表的关联)可以采用Map实现。下面的例子建立Category类、Item类和User类的三元关联:











































































最后,正如UML类图只是描述系统的静态结构,这里小鸡射手仅描述了ORM映射部分。如果要编写高质量的ORM程序,还需要了解实体的生命期、事务和并发、Query、Fetch机制和缓存等动态特性。
全文完。