环境:DB——MySQL,hibernate4.1.4
面向设计的粒度细分
通过对象细化,实现更加清晰的系统逻辑划分——情景:重新规划已有系统,通过case分析得出新的类设计,但相应地数据库的表的ER不希望改变。现有一表:t_person
create table t_person(
id int(11) not null auto_increment,
name varchar(80) not null default '',
address varchar(100),
tel varchar(12),
zipcode varchar(10),
primary key (id)
);
原来的TPerson.java如下:
package learnHibernate.bean;
import java.io.Serializable;
public class TPerson implements Serializable {
private static final long serialVersionUID = -7714660203394864063L;
private int id;
private String name;
private String address;
private String tel;
private String zipcode;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
经过重新规划后,决定将联系方式的信息封装到Contact类当中。
变成如下:由Tperson持有Contact对象
package learnHibernate.bean;
import java.io.Serializable;
public class TPerson implements Serializable {
private static final long serialVersionUID = -7714660203394864063L;
private int id;
private String name;
private Contact contact;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Contact getContact() {
return contact;
}
public void setContact(Contact contact) {
this.contact = contact;
}
}
Contact.java:
package learnHibernate.bean;
import java.io.Serializable;
public class Contact implements Serializable {
private static final long serialVersionUID = 2372937305763736126L;
private String address;
private String tel;
private String zipcode;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
对于上述,hibernate的hbm.xml映射文件用到了component节点:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TPerson" table="t_person"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> <component name="contact" class="learnHibernate.bean.Contact"> <property name="address" column="address" type="java.lang.String"/> <property name="tel" column="tel" type="java.lang.String"/> <property name="zipcode" column="zipcode" type="java.lang.String"/> </component> </class> </hibernate-mapping>
上述就ORM这一方面,与普通的类表映射没有太大区别,只是体现在设计上面的改进。
面向性能的粒度细分
1.情景:现在有一表T_user,其中有一粗大无比的字段resume:
CREATE TABLE t_user (
id int(11) NOT NULL auto_increment,
name varchar(80) NOT NULL default '',
resume longtext,
PRIMARY KEY (id)
);
有时候,我们只想列出user的name列表,此时若也把resume这个字段一并查出,这无疑造成不必要的性能浪费。。。此时可以用延迟加载的方式解决,此处不赘述。介绍另一种:
在继承层次上对粒度进一步细化:
原来的TUser.java:
package learnHibernate.bean;
import java.io.Serializable;
public class TUser implements Serializable{
private static final long serialVersionUID = -2983670695642662371L;
private int id;
private String name;
private String resume;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResume() {
return resume;
}
public void setResume(String resume) {
this.resume = resume;
}
}
现在将resume从Tuser.java中抽出,移到子类TUserInfo.java当中:
package learnHibernate.bean;
public class TuserInfo extends TUser {
private static final long serialVersionUID = -7362075358002914585L;
private String resume;
public String getResume() {
return resume;
}
public void setResume(String resume) {
this.resume = resume;
}
}
对应的映射文件如下:
TUser.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TUser" table="t_user"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> </class> </hibernate-mapping>
TUserInfo.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TUserInfo" table="t_user" polymorphism="explicit"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> <property name="resume" column="resume" type="java.lang.String"/> </class> </hibernate-mapping>
其中polymorphism="explicit"意思是声明一个显式的多态关系,声明为显式多态的类只有在明确指定类名的时候才会返回此类实例:
String hql1 = "From TUser where name='Oham'";
TUser tu = (TUser)session.createQuery(hql1).list().get(0);
System.out.println("=============");
String hql2 = "From TUserInfo where name='Oham'";
TUserInfo ti = (TUserInfo)session.createQuery(hql2).list().get(0);
若执行类似上述的代码,看后台log出的SQL:
Hibernate:
select
tuser0_.id as id0_,
tuser0_.name as name0_
from
t_user tuser0_
where
tuser0_.name='Oham'
=============
Hibernate:
select
tuserinfo0_.id as id0_,
tuserinfo0_.name as name0_,
tuserinfo0_.resume as resume0_
from
t_user tuserinfo0_
where
tuserinfo0_.name='Oham'
若执行createQuery("From Object").list(); 则将返回数据库中所有的表记录的数据对象,其中,对应t_user表的记录将以Tuse返回,而不是TUserInfo,也就是说不包含resume字段。
2.实体层次设计——继承关系是关系型数据与面向对象数据结构之间的主要差异之一,在关系型数据库的基础上,就对象的继承关系进行清晰合理的层次划分。
Hibernate中支持3种类型的继承形式
1)Table per concrete class —— 表与子类之间的独立一对一关系;
2)Table per subclass —— 每个子类对应一张子表,并与主类共享主表;
3)Table per class hierarchy —— 表与类的一对多关系;
现给出如下的类关系:
TMember.java
package learnHibernate.bean;
import java.io.Serializable;
import java.util.List;
public class TMember implements Serializable{
private static final long serialVersionUID = -2487367694260008988L;
private int id;
private String name;
private List email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getEmail() {
return email;
}
public void setEmail(List email) {
this.email = email;
}
}
TOham和TLulu都继承TMember,TOham.java:
package learnHibernate.bean;
public class TOham extends TMember {
private String meditation;
public String getMeditation() {
return meditation;
}
public void setMeditation(String meditation) {
this.meditation = meditation;
}
}
TLulu.java:
package learnHibernate.bean;
public class TLulu extends TMember {
private String sixthSense;
public String getSixthSense() {
return sixthSense;
}
public void setSixthSense(String sixthSense) {
this.sixthSense = sixthSense;
}
}
Table per concrete class —— 表与子类之间的独立一对一关系:
TOham和TLulu都继承于TMember,所以自然就包含了Tmember的属性了,在Table per concrete class模式当中,每个子类分别对应一个独立的表,表中包含了子类所需的所有字段:
t_oham表:
TOham.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TOham" table="t_oham"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> <property name="email" column="email" type="learnHibernate.bean.EmailList" /> <property name="meditation" column="meditation" type="java.lang.String"/> </class> </hibernate-mapping>
t_lulu表:
TLulu.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TLulu" table="t_lulu"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> <property name="email" column="email" type="learnHibernate.bean.EmailList" /> <property name="sixthSense" column="sixthsense" type="java.lang.String"/> </class> </hibernate-mapping>
从上述配置可以看出Table per concrete class 模式的映射方式似乎跟普通的映射并无区别,在hibernate的角度,以多态(polymorphism)来描述TOham,TLulu与TMember的继承关系,TOham,TLulu的映射配置文件没有出现polymorphism属性的定义,也就是说采用了默认的隐式多态模式(polymorphism=“implicit”)。
执行:
String hql = "From TMember";
List list = session.createQuery(hql).list();
后台 log出的hibernate SQL:
Hibernate:
select
tlulu0_.id as id3_,
tlulu0_.name as name3_,
tlulu0_.email as email3_,
tlulu0_.sixthsense as sixthsense3_
from
t_lulu tlulu0_
Hibernate:
select
toham0_.id as id2_,
toham0_.name as name2_,
toham0_.email as email2_,
toham0_.meditation as meditation2_
from
t_oham toham0_
Hibernate:
select
tmember0_.id as id1_,
tmember0_.name as name1_,
tmember0_.email as email1_
from
t_member tmember0_
Hibernate会在当前环境中查找所有polymorphism=“implicit”的子类,并返回子类所对应的所有表的记录。
可以看出,对象的继承关系在持久层得到了体现,不过此种映射方式也存在着一些局限,如t_oham,t_lulu的父字段必须保持一致,若父类TMember发生变动,子类必须同时修改。有时候我们会根据一个name字段进行查询,此时就可能要对每个子表查询后汇总,于是我们希望有的大表包含所有可能出现的字段。借助这种情形,下面介绍Table per subclass —— 每个子类对应一张子表,并与主类共享主表 和 Table per class hierarchy —— 表与类的一对多关系。
Table per subclass —— 每个子类对应一张子表
接着上述的例子由于父类TMember发生变动,子类TOham,TLulu必须同时修改,所以重新设计表ER,让t_oham和t_lulu字表只包含子类所扩展的属性,同时子表与父表通过外键相关联:
TMember.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TMember" table="t_member"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <property name="name" column="name" type="java.lang.String"/> <property name="email" column="email" type="learnHibernate.bean.EmailList" /> <joined-subclass name="TOham" table="t_oham"> <key column="id"/> <property name="meditation" column="meditation"/> </joined-subclass> <joined-subclass name="TLulu" table="t_lulu"> <key column="id"></key> <property name="sixthSense" column="sixthsense"/> </joined-subclass> </class> </hibernate-mapping>
通过joined-subclass节点在父类映射文件中对子类TOham, TLulu进行配置,joined-subclass节点与class节点类似,且joined-subclass节点可以嵌套。
执行:
TOham o = new TOham();
o.setName("oham2");
o.setMeditation("Civilization Rise");
session.save(o);
TOham o2 = new TOham();
o2.setName("oham3");
session.save(o2);
TLulu l = new TLulu();
l.setName("Lulu2");
l.setSixthSense("Dancing soul");
session.save(l);
后台log:
Hibernate:
insert
into
t_member
(name, email)
values
(?, ?)
Set method executed
Hibernate:
insert
into
t_oham
(meditation, id)
values
(?, ?)
Hibernate:
insert
into
t_member
(name, email)
values
(?, ?)
Set method executed
Hibernate:
insert
into
t_oham
(meditation, id)
values
(?, ?)
Hibernate:
insert
into
t_member
(name, email)
values
(?, ?)
Set method executed
Hibernate:
insert
into
t_lulu
(sixthsense, id)
values
(?, ?)
再执行:
String hql = "From TMember";
session.createQuery(hql).list();
后台log:
Hibernate:
select
tmember0_.id as id1_,
tmember0_.name as name1_,
tmember0_.email as email1_,
tmember0_1_.meditation as meditation2_,
tmember0_2_.sixthsense as sixthsense3_,
case
when tmember0_1_.id is not null then 1
when tmember0_2_.id is not null then 2
when tmember0_.id is not null then 0
end as clazz_
from
t_member tmember0_
left outer join
t_oham tmember0_1_
on tmember0_.id=tmember0_1_.id
left outer join
t_lulu tmember0_2_
on tmember0_.id=tmember0_2_.id
相对于Table per concrete class,Table per subclass 带来了更加清晰的数据逻辑划分,不过跟Table per concrete class类似,当遇到多表操作的时候,系统性能都不太高,对于高并发的数据存取都不利;以此来介绍Table per class hierarchy。
实际开发中,通过冗余字段表达同类型数据可能是我们在绝大多数情况下的选择。对于上述的示例,我们可以通过一个包含所有子类字段的t_member表存储所有信息。
对于上述例子,重建t_member表:
这样,数据的存取都能通过一条简单的sql即可完成。在简易和性能两方面考量都能得到一个较为满意的结果。但需要重新设计映射以体现不同子类的差异。
此时再对t_member表添加一个字段来标识不同的子类:Category。
Category 为1 是代表TOham记录
Category 为2 是代表TLulu记录。
为了hibernate能自动根据category节点识别对应的子类class类型,需要在配置文件中进行配置,而discriminator节点,则定义了这种配置关系。
TMember.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TMember" table="t_member"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <!-- 通过discriminator节点,声明了用于子类辨别标识的表字段名 --> <discriminator column="category" type="java.lang.String"/> <property name="name" column="name" type="java.lang.String"/> <property name="email" column="email" type="learnHibernate.bean.EmailList" /> <!-- 辨别标识的字段值为1时,对应子类为TOham --> <subclass name="TOham" discriminator-value="1"> <property name="meditation" column="meditation"/> </subclass> <!-- 辨别标识的字段值为2时,对应子类为TLulu --> <subclass name="TLulu" discriminator-value="2"> <property name="sixthSense" column="sixthsense"/> </subclass> </class> </hibernate-mapping>
如此,运行期hibernate在读取t_member表数据时,会根据指定的辨别标识进行判断,如果记录的category为1,则映射到TOham,为2映射到TLulu。
执行:
String hql1 = "From TOham";
String hql2 = "From TLulu";
session.createQuery(hql1).list();
session.createQuery(hql2).list();
后台log:
Hibernate:
select
toham0_.id as id1_,
toham0_.name as name1_,
toham0_.email as email1_,
toham0_.meditation as meditation1_
from
t_member toham0_
where
toham0_.category='1'
Hibernate:
select
tlulu0_.id as id1_,
tlulu0_.name as name1_,
tlulu0_.email as email1_,
tlulu0_.sixthsense as sixthsense1_
from
t_member tlulu0_
where
tlulu0_.category='2'
注意一点:discriminator 节点的type貌似不能指定为除String以外的类型,在下试过,说:
Caused by: org.hibernate.MappingException: Could not format discriminator value to SQL string。