Hibernate使用——实体映射的几种策略
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 column="name" type="java.lang.String"/><component name="contact" 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 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 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 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 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 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 executedHibernate: insert into t_oham (meditation, id) values (?, ?)Hibernate: insert into t_member (name, email) values (?, ?)Set method executedHibernate: insert into t_oham (meditation, id) values (?, ?)Hibernate: insert into t_member (name, email) values (?, ?)Set method executedHibernate: 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 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。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?