`
oham_一1一
  • 浏览: 49958 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Hibernate使用——实体映射的几种策略

阅读更多

环境: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。

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

  • 大小: 11.9 KB
  • 大小: 10.3 KB
  • 大小: 10.5 KB
  • 大小: 4.5 KB
  • 大小: 4.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics