本文描述类图和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;}
由于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");
如果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;}
数据库表如下:
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");
如果希望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; }
数据库表如下:
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");
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>(); }
数据库表如下:
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");
第二个例子是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>();}
数据库表如下:
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");
本文的第一部分到此结束,
类图和ORM关系(二)介绍继承的映射,
类图和ORM关系(三)介绍关联的映射。