本文描述类图和ORM映射的关系。在例子部分,小鸡射手采用EJB 3.0 Annotation方式为主,数据库是HSQLDB。由于例子比较多,全文分三部分。
1 类图与ORM的关系
面向对象的分析类包括边界类、控制类和实体类,需要持久化的是实体类。
UML类图中,类和类的关系包括:
- 关联(Associate)
- 泛化(Generalize)
- 聚集(Aggregate)
- 复合(Compose)
- 多元关联(Association)
ORM映射方式如下:
- 复合关系:采用值类型(Value Type)映射,包括单个值类型和值类型集合
- 泛化关系:采用继承映射,包括:
一个继承结构使用一个表(Table per class hierarchy)
各子类使用单独的表(Table per subclass)
支持多态的各具体实体类使用单独的表(Table per concrete class with implicit polymorphism)
采用Union的各具体实体类使用单独的表(Table per concrete class with unions)
- 其他关系采用关联映射,包括:
单向ManyToOne,这是最基本、最重要的关联;
单向OneToMany,这是涉及到集合的最主要关联;
双向ManyToOne;
单向OneToOne;
双向OneToOne;
单向ManyToMany;
双向ManyToMany,一般地最好实现为两个OneToMany关系;
三元关联(Association)采用中间表和Map实现。
2 值类型映射
ORM映射不是简单地将一个类映射成一个数据库表。对象模型一般是细颗粒度,例如User类和Address类是复合关系,Address类可以映射为值对象。相对于实体,值对象不需要主键(Identity),和复合关系一样,它的生命期和所属实体一致。在ORM映射中,
应该尽量使用值对象。
下面的例子描述User类和Address类的映射。简单起见实体中只包括字段定义,如果需要增加字段属性如NOT NULL,可以增加@Column(nullable = false)等,例子中均省略了。
@Embeddable
public class Address1 ... {
private String street;
}
@Entity
public class User1 ... {
@Id private int id;
private String name;
@Embedded Address1 homeAddress;
}
public class Address1 ... {
private String street;
}
@Entity
public class User1 ... {
@Id private int id;
private String name;
@Embedded Address1 homeAddress;
}
由于Address类是值类型,数据库表只有一个:
create
table
"USER1"(
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
"STREET" VARCHAR ( 255 ),
constraint "SYS_PK_1637" primary key ("ID")
);
create unique index "SYS_PK_1637" on "USER1"("ID");
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
"STREET" VARCHAR ( 255 ),
constraint "SYS_PK_1637" primary key ("ID")
);
create unique index "SYS_PK_1637" on "USER1"("ID");
如果User类中有多个Address对象,例如homeAddress和companyAddress,则需要字段更名:
@Embeddable
public class Address2 ... {
private String street;
}
@Entity
public class User2 ... {
@Id private int id;
private String name;
@Embedded Address2 homeAddress;
@Embedded @AttributeOverrides(...{ @AttributeOverride(name="street",column=@Column(name="COMPANY_STREET"))})
Address2 companyAddress;
}
public class Address2 ... {
private String street;
}
@Entity
public class User2 ... {
@Id private int id;
private String name;
@Embedded Address2 homeAddress;
@Embedded @AttributeOverrides(...{ @AttributeOverride(name="street",column=@Column(name="COMPANY_STREET"))})
Address2 companyAddress;
}
数据库表如下:
create
table
"USER2"(
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
"STREET" VARCHAR ( 255 ),
"COMPANY_STREET" VARCHAR ( 255 ),
constraint "SYS_PK_1638" primary key ("ID")
);
create unique index "SYS_PK_1638" on "USER2"("ID");
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
"STREET" VARCHAR ( 255 ),
"COMPANY_STREET" VARCHAR ( 255 ),
constraint "SYS_PK_1638" primary key ("ID")
);
create unique index "SYS_PK_1638" on "USER2"("ID");
如果希望Address类既是值类型,数据又放在单独的表中,可以采用@SecondaryTable实现:
@Embeddable
public class Address3 ... {
private String street;
}
@Entity
@SecondaryTable(name = " USER3_ADDRESS3 " , pkJoinColumns = ... {@PrimaryKeyJoinColumn(name="id")} )
public class User3 ... {
@Id private int id;
private String name;
@Embedded
@AttributeOverrides(...{ @AttributeOverride(name = "street", column = @Column(name="STREET", table = "USER3_ADDRESS3"))})
private Address3 homeAddress;
}
public class Address3 ... {
private String street;
}
@Entity
@SecondaryTable(name = " USER3_ADDRESS3 " , pkJoinColumns = ... {@PrimaryKeyJoinColumn(name="id")} )
public class User3 ... {
@Id private int id;
private String name;
@Embedded
@AttributeOverrides(...{ @AttributeOverride(name = "street", column = @Column(name="STREET", table = "USER3_ADDRESS3"))})
private Address3 homeAddress;
}
数据库表如下:
create
table
"USER3"(
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1639" primary key ("ID")
);
create unique index "SYS_PK_1639" on "USER3"("ID");
create table "USER3_ADDRESS3"(
"STREET" VARCHAR ( 255 ),
"ID" INTEGER not null ,
constraint "SYS_PK_1636" primary key ("ID")
);
alter table "USER3_ADDRESS3"
add constraint "FKAD4A6F76C4D6A8B8"
foreign key ("ID")
references "USER3"("ID");
create unique index "SYS_PK_1636" on "USER3_ADDRESS3"("ID");
create index "SYS_IDX_1697" on "USER3_ADDRESS3"("ID");
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1639" primary key ("ID")
);
create unique index "SYS_PK_1639" on "USER3"("ID");
create table "USER3_ADDRESS3"(
"STREET" VARCHAR ( 255 ),
"ID" INTEGER not null ,
constraint "SYS_PK_1636" primary key ("ID")
);
alter table "USER3_ADDRESS3"
add constraint "FKAD4A6F76C4D6A8B8"
foreign key ("ID")
references "USER3"("ID");
create unique index "SYS_PK_1636" on "USER3_ADDRESS3"("ID");
create index "SYS_IDX_1697" on "USER3_ADDRESS3"("ID");
JPA规范目前不支持值类型的集合。如果采用Hibernate,则可以使用Hibernate特有的Annotation实现值类型集合。值类型可以是简单类型(如String),也可以是Value Type(如Address);Hibernate支持的集合类型包括:
Set:集合中不能包括重复的元素;
Bag:集合中允许重复元素,但不是排序的;
List:集合元素记录了位置信息;
Map:集合元素是<key,value>,并且不是排序的;
SortedSet:排序的Set集合;
SortedMap:排序的Map元素;
OrderedSet:数据库排序的Set集合;
OrderedMap:数据库排序的Map集合;
OrderedBag:数据库排序的Bag集合。
由于这不是JPA支持的特性,仅举两个例子说明。第一个例子是User类有一个String值类型的Set:
@Entity
public class User4 ... {
@Id private int id;
private String name;
@org.hibernate.annotations.CollectionOfElements
@JoinTable(name = "User4_Street4", joinColumns = @JoinColumn(name = "id"))
@Column(name = "street", nullable = false)
private Set<String> streets = new HashSet<String>();
}
public class User4 ... {
@Id private int id;
private String name;
@org.hibernate.annotations.CollectionOfElements
@JoinTable(name = "User4_Street4", joinColumns = @JoinColumn(name = "id"))
@Column(name = "street", nullable = false)
private Set<String> streets = new HashSet<String>();
}
数据库表如下:
create
table
"USER4"(
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1640" primary key ("ID")
);
create unique index "SYS_PK_1640" on "USER4"("ID");
create table "USER4_STREET4"(
"ID" INTEGER not null ,
"STREET" VARCHAR ( 255 ) not null ,
constraint "SYS_PK_1641" primary key ("ID","STREET")
);
alter table "USER4_STREET4"
add constraint "FKCAC0D3DBC4D6A8B9"
foreign key ("ID")
references "USER4"("ID");
create unique index "SYS_PK_1641" on "USER4_STREET4"("ID","STREET");
create index "SYS_IDX_1699" on "USER4_STREET4"("ID");
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1640" primary key ("ID")
);
create unique index "SYS_PK_1640" on "USER4"("ID");
create table "USER4_STREET4"(
"ID" INTEGER not null ,
"STREET" VARCHAR ( 255 ) not null ,
constraint "SYS_PK_1641" primary key ("ID","STREET")
);
alter table "USER4_STREET4"
add constraint "FKCAC0D3DBC4D6A8B9"
foreign key ("ID")
references "USER4"("ID");
create unique index "SYS_PK_1641" on "USER4_STREET4"("ID","STREET");
create index "SYS_IDX_1699" on "USER4_STREET4"("ID");
第二个例子是User类有一个Address值类型的Set:
@Embeddable
public class Address5 ... {
@org.hibernate.annotations.Parent
private User5 user;
private String street;
public User5 getUser() ...{ return user;}
public void setUser(User5 user) ...{ this.user = user;}
}
@Entity
public class User5 ... {
@Id private int id;
private String name;
@org.hibernate.annotations.CollectionOfElements
@JoinTable(name = "User5_Address5", joinColumns = @JoinColumn(name = "id"))
private Set<Address5> addresses = new HashSet<Address5>();
}
public class Address5 ... {
@org.hibernate.annotations.Parent
private User5 user;
private String street;
public User5 getUser() ...{ return user;}
public void setUser(User5 user) ...{ this.user = user;}
}
@Entity
public class User5 ... {
@Id private int id;
private String name;
@org.hibernate.annotations.CollectionOfElements
@JoinTable(name = "User5_Address5", joinColumns = @JoinColumn(name = "id"))
private Set<Address5> addresses = new HashSet<Address5>();
}
数据库表如下:
create
table
"USER5"(
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1642" primary key ("ID")
);
create unique index "SYS_PK_1642" on "USER5"("ID");
create table "USER5_ADDRESS5"(
"ID" INTEGER not null ,
"STREET" VARCHAR ( 255 )
);
alter table "USER5_ADDRESS5"
add constraint "FKCE3D99D6C4D6A8BA"
foreign key ("ID")
references "USER5"("ID");
create index "SYS_IDX_1701" on "USER5_ADDRESS5"("ID");
"ID" INTEGER not null ,
"NAME" VARCHAR ( 255 ),
constraint "SYS_PK_1642" primary key ("ID")
);
create unique index "SYS_PK_1642" on "USER5"("ID");
create table "USER5_ADDRESS5"(
"ID" INTEGER not null ,
"STREET" VARCHAR ( 255 )
);
alter table "USER5_ADDRESS5"
add constraint "FKCE3D99D6C4D6A8BA"
foreign key ("ID")
references "USER5"("ID");
create index "SYS_IDX_1701" on "USER5_ADDRESS5"("ID");