1、整个继承树映射到一张表
在一个论坛中有用户(User)这个实体类,其中User这个实体中有分为普通用户,管理员与游客这3种身份,而这3种现在身份的用户就只有一个字段(即类型)不相同,可以考虑用Hibernate中的Table per class hierarchy策略
对象模型(Java类结构)
表结构
User代码:
package org.hibernate.domain;
import java.util.Date;
/**
* 实体类
*/
public class User {
private int id;
private String name;
private Date birthday;
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 Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", birthday=" + birthday
+ "]";
}
}
Admin代码:
package org.hibernate.domain;
public class Admin extends User {
private String admin;
public String getAdmin() {
return admin;
}
public void setAdmin(String admin) {
this.admin = admin;
}
}
package org.hibernate.domain;
public class Guest extends User {
private String guest;
public String getGuest() {
return guest;
}
public void setGuest(String guest) {
this.guest = guest;
}
}
配置文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.hibernate.domain"> <class name="User" table="user" discriminator-value="0"> <id name="id"> <generator class="native" /> </id> <!-- 默认为string类型,这里指定为int类型 --> <discriminator column="type" type="int" /> <property name="name" /> <property name="birthday" /> <subclass name="Admin" discriminator-value="1"> <property name="admin" column="admin" /> </subclass> <subclass name="Guest" discriminator-value="2"> <property name="guest" column="guest" /> </subclass> </class> </hibernate-mapping>
测试类:
package org.hibernate.test;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.domain.Admin;
import org.hibernate.domain.Guest;
import org.hibernate.domain.User;
import org.hibernate.util.HibernateUtil;
public class TestUser extends TestCase {
public void testAddUser() {
Session session = null;
Transaction tx = null;
try {
User user = new User();
user.setName("user1");
Admin admin = new Admin();
admin.setName("user2");
admin.setAdmin("admin user");
Guest guest = new Guest();
guest.setName("user3");
guest.setGuest("guest user");
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.save(user);
session.save(admin);
session.save(guest);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
public void testQuery() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.get(User.class, 1);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
}
testAddUser方法测试结果:
Hibernate: insert into user (name, birthday, type) values (?, ?, 0) Hibernate: insert into user (name, birthday, admin, type) values (?, ?, ?, 1) Hibernate: insert into user (name, birthday, guest, type) values (?, ?, ?, 2)
可以看到3个对象都保存在了同一张表(User)中,其中靠type字段来区分不同的用户
User表记录如下
现在我们测试testQuery方法,结果如下:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_, user0_.birthday as birthday0_0_, user0_.admin as admin0_0_, user0_.guest as guest0_0_, user0_.type as type0_0_ from user user0_ where user0_.id=?
将测试代码中的
session.get(User.class, 1);
更改为
session.get(User.class, 2);
则结果如下:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_, user0_.birthday as birthday0_0_, user0_.admin as admin0_0_, user0_.guest as guest0_0_, user0_.type as type0_0_ from user user0_ where user0_.id=?
因为hibernate支持多态查询,故查询语句不变
将测试代码中的
session.get(User.class, 2);
更改为
session.get(Guest.class, 2);
则结果如下:
Hibernate: select guest0_.id as id0_0_, guest0_.name as name0_0_, guest0_.birthday as birthday0_0_, guest0_.guest as guest0_0_ from user guest0_ where guest0_.id=? and guest0_.type=2
从上述结果可以看到如果是明确知道查询是哪一类用户,则查询语句后面会指定type类型
采用这种策略只需要一张表即可。它有一个很大的限制:要求那些由子类定义的字段, 如admin
,不能有非空(NOT NULL)
约束。
2、每个类映射到一张表
可以看到Hibernate整个类映射到一张表策略中,如果子类中有很多字段的情况下,则表就会产生很多的空字段,由此当子类中有很多字段的情况下,可以采用“每个子类一张表”的映射策略
表结构如下:
修改User.hbm.xml映射文件如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.hibernate.domain"> <class name="User" table="user"> <id name="id" column="userId"> <generator class="native" /> </id> <property name="name" /> <property name="birthday" /> <joined-subclass name="Admin" table="admin"> <key column="userId" /> <property name="admin" column="admin" /> </joined-subclass> <joined-subclass name="Guest" table="guest"> <key column="userId" /> <property name="guest" column="guest" /> </joined-subclass> </class> </hibernate-mapping>
测试类:
package org.hibernate.test;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.domain.Admin;
import org.hibernate.domain.Guest;
import org.hibernate.domain.User;
import org.hibernate.util.HibernateUtil;
public class TestUser extends TestCase {
public void testAddUser() {
Session session = null;
Transaction tx = null;
try {
User user = new User();
user.setName("user1");
Admin admin = new Admin();
admin.setName("user2");
admin.setAdmin("admin user");
Guest guest = new Guest();
guest.setName("user3");
guest.setGuest("guest user");
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.save(user);
session.save(admin);
session.save(guest);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
public void testQuery() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.get(User.class, 1);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
}
testAddUser方法测试结果:
Hibernate: insert into user (name, birthday) values (?, ?) Hibernate: insert into user (name, birthday) values (?, ?) Hibernate: insert into admin (admin, userId) values (?, ?) Hibernate: insert into user (name, birthday) values (?, ?) Hibernate: insert into guest (guest, userId) values (?, ?)
当插入的用户不是普通用户的时候,需要往2张表中插入数据
数据库表记录如下:
测试testQuery方法,结果如下:
Hibernate: select user0_.userId as userId0_0_, user0_.name as name0_0_, user0_.birthday as birthday0_0_, user0_1_.admin as admin1_0_, user0_2_.guest as guest2_0_, case when user0_1_.userId is not null then 1 when user0_2_.userId is not null then 2 when user0_.userId is not null then 0 end as clazz_0_ from user user0_ left outer join admin user0_1_ on user0_.userId=user0_1_.userId left outer join guest user0_2_ on user0_.userId=user0_2_.userId where user0_.userId=?将测试代码中的
session.get(User.class, 1);
更改为
session.get(Admin.class, 1);
则结果如下:
Hibernate: select admin0_.userId as userId0_0_, admin0_1_.name as name0_0_, admin0_1_.birthday as birthday0_0_, admin0_.admin as admin1_0_ from admin admin0_ inner join user admin0_1_ on admin0_.userId=admin0_1_.userId where admin0_.userId=?相比前面的查询语句,这句就简单的多了,所以我们在用“每个子类一张表”的映射策略的时候,最好明确要查询的类,这样可以提高查询的效率
3、混合使用“整个继承树映射到一张表 ”和“每个子类一张表”的映射策略
假设,guest用户的属性很少,而admin的属性很多,则可以使用混合使用“整个继承树映射到一张 表 ”和“每个子类一张表””的映射策略
测试类:
package org.hibernate.test;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.domain.Admin;
import org.hibernate.domain.Guest;
import org.hibernate.domain.User;
import org.hibernate.util.HibernateUtil;
public class TestUser extends TestCase {
public void testAddUser() {
Session session = null;
Transaction tx = null;
try {
User user = new User();
user.setName("user1");
Admin admin = new Admin();
admin.setName("user2");
admin.setAdmin("admin user");
Guest guest = new Guest();
guest.setName("user3");
guest.setGuest("guest user");
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.save(user);
session.save(admin);
session.save(guest);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
public void testQuery() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.get(User.class, 1);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
}
如果在hibernate.cfg.xml配置中配置了如下项:
<property name="hbm2ddl.auto">create</property>此时测试testAddUser方法时,测试单元会报如下错误:
程序运行时,会删除数据库中的user表,admin表,而user表中有admin表的外键关联,所以不能删除user数据表了,所以抛出了上面的异常。所以需要先手动删除数据表,再运行程序。
此时测试testAdd方法时,结果如下:
Hibernate: insert into user (name, birthday, type) values (?, ?, 0) Hibernate: insert into user (name, birthday, type) values (?, ?, 2) Hibernate: insert into admin (admin, userId) values (?, ?) Hibernate: insert into user (name, birthday, guest, type) values (?, ?, ?, 1)与采用“每个子类一张表”的映射策略相比,插入数据的时候,guest只插入一张表,admin插入了2张表,效率提高了些,如果要查询数据,则当查询admin的时候才会使用连接查询
4、"每个具体类一张表"的映射策略
前面第一种映射策略中表结构存在null字段,第二种与第三种中查询的时候需要用到表连接,采用"每个具体类一张表"的映射策略则不会出现以上情况
表结构:
测试类:
package org.hibernate.test;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.domain.Admin;
import org.hibernate.domain.Guest;
import org.hibernate.domain.User;
import org.hibernate.util.HibernateUtil;
public class TestUser extends TestCase {
public void testAddUser() {
Session session = null;
Transaction tx = null;
try {
User user = new User();
user.setName("user1");
Admin admin = new Admin();
admin.setName("user2");
admin.setAdmin("admin user");
Guest guest = new Guest();
guest.setName("user3");
guest.setGuest("guest user");
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.save(user);
session.save(admin);
session.save(guest);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
public void testQuery() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
session.get(User.class, 1);
tx.commit();
} finally {
if (session != null)
session.close();
}
}
}
配置文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.hibernate.domain"> <class name="User" table="user"> <id name="id" column="userId"> <generator class="hilo" /> </id> <property name="name" /> <property name="birthday" /> <union-subclass name="Admin" table="admin"> <property name="admin" /> </union-subclass> <union-subclass name="Guest" table="guest"> <property name="guest" /> </union-subclass> </class> </hibernate-mapping>
testAddUser方法测试结果:
Hibernate: insert into user (name, birthday, userId) values (?, ?, ?) Hibernate: insert into admin (name, birthday, admin, userId) values (?, ?, ?, ?) Hibernate: insert into guest (name, birthday, guest, userId) values (?, ?, ?, ?)可以看到3个对象分别插入到3张表中
testQuery方法测试结果:
Hibernate: select user0_.userId as userId0_0_, user0_.name as name0_0_, user0_.birthday as birthday0_0_, user0_.admin as admin1_0_, user0_.guest as guest2_0_, user0_.clazz_ as clazz_0_ from ( select birthday, null as admin, name, userId, null as guest, 0 as clazz_ from user union select birthday, admin, name, userId, null as guest, 1 as clazz_ from admin union select birthday, null as admin, name, userId, guest, 2 as clazz_ from guest ) user0_ where user0_.userId=?可以看到Hibernate会把所有的表都连接起来做查询,然后再从中查询出符合条件的记录