现实生活中,一对一(One To One)关系的实体比比皆是。比如,人和身份证的关系,人和社会属性的关系等。
在Hibernate中,OneToOne关系分为两种策略:主键关联和唯一外键关联。主键关联,就是两个一对一的实体共用相同的id。比如,人(Person)和身份证(IdCard),将Person的id主键,关联到IdCard的主键。这样,两个实体,就通过主键关联在一起了。唯一外键的含义也很明确。就是在Person实体中定义一个额外的字段,比如card_id,用这个字段外键关联到IdCard的主键id。
下面就让我们看看主键关联:
Person.java
public class Person {
private Integer id;
private String name;
private String sex;
private Integer age;
private Idcard idcard;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Idcard getIdcard() {
return idcard;
}
public void setIdcard(Idcard idcard) {
this.idcard = idcard;
}
Idcard.java
public class Idcard {
private Integer id;
private String cardNum;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCardNum() {
return cardNum;
}
public void setCardNum(String cardNum) {
this.cardNum = cardNum;
}
}
以上是两个实体。
Person.hbm.xml
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="demo.hibernate.domain">
<class name="Person">
<id name="id" unsaved-value="null">
<!--在单独使用的时候,可以使用序列策略,这里对应的数据库是Oracle,不同的数据库,拥有不同的主键生成策略-->
<!--<generator class="sequence">-->
<!--<param name="sequence">seq_person</param>-->
<!--</generator>-->
<!--因为人和身份证是一对一关联,避免冗余的数据生成,采用主键相同的策略-->
<generator class="foreign">
<param name="property">idcard</param>
</generator>
</id>
<property name="name"/>
<property name="sex" />
<property name="age" />
<!--cascade=all :在插入一条Person信息的时候,可以插入一条Idcard。constrained=true:因为这里存在一个外键约束,person的主键,关联到idcard的主键-->
<!--这里lazy=false,这样,在抓取person信息的时候,就同时抓取了idcard。否则,如果lazy=proxy,这也是hibernate默认的抓取方式,这样你在获取person后再获取idcard,就
会报no Session 异常。在web开发中,可以开启openSessionInView,确保Session的生命周期延续到前台页面,lazy=false,对性能会有影响-->
<one-to-one name="idcard" cascade="none" constrained="true" lazy="false"></one-to-one>
</class>
</hibernate-mapping>
Idcard.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="demo.hibernate.domain">
<class name="Idcard">
<id name="id" unsaved-value="null">
<generator class="sequence">
<param name="sequence">seq_person</param>
</generator>
</id>
<property name="cardNum"/>
</class>
</hibernate-mapping>
以上是Person.java和Idcard.java对应的映射文件。
public class PersonDao {
public Integer save(Person person){
Integer id = null;
try {
if(person == null){
return null;
}
Session s = HibernateUtils.getCurrentSession();
Transaction t = s.beginTransaction();
id = (Integer)s.save(person);
t.commit();
} catch (HibernateException e) {
throw new RuntimeException("保存person信息失败");
}finally {
HibernateUtils.closeSession();
}
return id;
}
public Person get(int id){
Person p = null;
try {
Session s = HibernateUtils.getCurrentSession();
p = (Person)s.get(Person.class, id);
return p;
} catch (HibernateException e) {
throw new RuntimeException("获取person信息失败");
} finally {
HibernateUtils.closeSession();
}
}
public List<Person> getPersonList(String name){
List<Person> persons = null;
try {
Session s = HibernateUtils.getCurrentSession();
StringBuilder hql = new StringBuilder("from Person");
Query q = null;
if(name != null && !name.isEmpty()){
hql.append("where name = :name");
q = s.createQuery(hql.toString());
q.setParameter("name",name);
}else {
q = s.createQuery(hql.toString());
}
persons = q.list();
return persons;
} catch (HibernateException e) {
throw new RuntimeException("获取person信息失败");
} finally {
HibernateUtils.closeSession();
}
}
}
上面是PersonDao.java,封装了对Person实体的基本操作。
public class TestPersonDao {
static Person person = null;
static Idcard idcard = null;
static PersonDao personDao = null;
static Integer personId = 21;
@BeforeClass
public static void init(){
initOne2One();
}
private static void initOne2One(){
idcard = new Idcard();
idcard.setCardNum("342401198911045870");
person = new Person();
person.setName("new");
person.setAge(12);
person.setSex("m");
person.setIdcard(idcard);
//这里注意,Person和Idcard是一对一关联。
//这里,在保存Person的时候,idcard是一个瞬时对象,未持久化。但是,配置的时候,cascade=all,
//这样,在保存Person对象的时候,hibernate会首先将idcard一并保存。
personDao = new PersonDao();
}
@Test
public void testSave(){
personId = personDao.save(person);
Assert.assertNotNull(personId);
Integer id1 = personDao.save(null);
Assert.assertNull(id1);
}
@Test
public void testGet(){
Person p = personDao.get(personId);
Assert.assertNotNull(p);
Assert.assertNotNull(p.getIdcard());
Assert.assertNotNull(p.getIdcard().getCardNum());
}
@Test
public void testGetPersonList(){
List<Person> persons = personDao.getPersonList(null);
//One-To-One:这里,就会出现一个N+1次查询的问题,
//因为One-To-One关联时,在Idcard中并不存在和person关联的信息,
//只有先查到person,然后再根据每一个person的id获取到与其关联的idcard。这样总共就做了N+1次查询。
//这样非常消耗性能
//@select * from person; 得到10条数据。
//接下来,做10次循环:@for 1 to 10 do select * from idcard d where d.id = person.id end for;
}
}
上面是PersonDao的测试类。