第十二章Hibernate一对一关系映射
一对一这种关联本身并不是一件非常复杂的事情,例如:在日常的工作之中肯定做过如下的系统:用户的注册分为快速注册和详细注册,那么如果是快速注册就发现只需要填写注册ID和密码就可以了,而对于详细注册,需要填写更多的内容,并且在以后也可以有用户自己去维护注册的信息。
那么这样的情况(《Oracle开发实战经典》)下,就可以基于一对一的关系实现,做一个单独的user_login表负责快速登录和注册,以及编写一个user_detail表,作为完整的信息。
范例:数据库创建脚本
-- 使用数据库 USE mldn ; -- 删除表 DROP TABLE user_details ; DROP TABLE user_login ; -- 创建表 CREATE TABLE user_login( userid VARCHAR(50) , password VARCHAR(32) , CONSTRAINT pk_userid1 PRIMARY KEY(userid) ) ; CREATE TABLE user_details( userid VARCHAR(50) , name VARCHAR(50) , age INT , birthday DATE , CONSTRAINT pk_userid2 PRIMARY KEY(userid) , CONSTRAINT fk_userid FOREIGN KEY(userid) REFERENCES user_login(userid) ON DELETE CASCADE ) ; |
对于此时的数据表也将采用两种方式实现,一种是基于传统的*.hbm.xml文件配置,另一种使用Annotation完成。
12.1、基于*.hbm.xml文件实现一对一的配置
那么首先为项目之中创建Hibernate支持,而后同时选择两张表一起创建Hibernate映射。记得要选择复选框“one-to-one”定义,那么此时生成的POJO类和*.hbm.xml文件几乎可以直接使用。
范例:观察Userlogin.java类的定义
package cn.cgj.pojo; @SuppressWarnings("serial") public class UserLogin implements java.io.Serializable { private String userid; private String password; private UserDetails userDetails; // 一个用户详情 public UserLogin() { } //setter、getter略 } |
这个地方发现在一个UserLogin对象里面需要对应一个UserDetails对象,表示一个用户的详情。
范例:观察UserDetails.java类定义
package cn.cgj.pojo; import java.util.Date; @SuppressWarnings("serial") public class UserDetails implements java.io.Serializable { private String userid; private UserLogin userLogin; // 与UserLogin类对应 private String name; private Integer age; private Date birthday; public UserDetails() { } //setter、getter略 } |
但是这个时候不单单是POJO类之间需要产生联系,同时还需要观察*.hbm.xml文件之中所应该产生的联系。
范例:观察userlogin.hbm.xml文件定义
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="cn.cgj.pojo.UserLogin" table="user_login" catalog="mldn"> <id name="userid" type="java.lang.String"> <column name="userid" length="50" /> <generator class="assigned"></generator> </id> <property name="password" type="java.lang.String"> <column name="password" length="32" /> </property> <!-- 表示一对一的映射,userDetails属性与UserDetails类对应 --> <one-to-one name="userDetails" class="cn.cgj.pojo.UserDetails" cascade="all" /> </class> </hibernate-mapping> |
范例:观察UserDetails.hbm.xml文件定义
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="cn.cgj.pojo.UserDetails" table="user_details" catalog="mldn"> <id name="userid" type="java.lang.String"> <column name="userid" length="50" /> <generator class="assigned"></generator> </id> <!-- 一对一映射,表示userLogin对象是UserLogin类的关联,同时受到UserLogin的约束控制 --> <one-to-one name="userLogin" class="cn.cgj.pojo.UserLogin" constrained="true"></one-to-one> <property name="name" type="java.lang.String"> <column name="name" length="50" /> </property> <property name="age" type="java.lang.Integer"> <column name="age" /> </property> <property name="birthday" type="java.util.Date"> <column name="birthday" length="10" /> </property> </class> </hibernate-mapping> |
现在就实现完成了一对一的关系,那么在进行一对一关系的时候,用户通过程序操作应该是以类对象的形式完成的,而后由Hibernate自己去分辨如何保存数据。
范例:增加数据——快速注册
package cn.cgj.pojo; import cn.cgj.dbc.HibernateSessionFactory; public class UserLoginTestInsert01 { public static void main(String[] args) { UserLogin login = new UserLogin(); login.setUserid("daguo" + System.currentTimeMillis()); login.setPassword("daguozuibang"); HibernateSessionFactory.getSession().save(login); HibernateSessionFactory.getSession().beginTransaction().commit(); HibernateSessionFactory.closeSession(); } } |
Hibernate: insert into mldn.user_login (password, userid) values (?, ?) |
这个时候并没有增加UserDetails类对象,所以此处只实现了user_login数据表内容的保存。
范例:增加数据——详细注册
package cn.cgj.test; import java.util.Date; import cn.cgj.dbc.HibernateSessionFactory; import cn.cgj.pojo.UserDetails; import cn.cgj.pojo.UserLogin; public class UserLoginTestInsert02 { public static void main(String[] args) { UserLogin login = new UserLogin(); login.setUserid("daguo" + System.currentTimeMillis()); login.setPassword("daguozuibang"); UserDetails details = new UserDetails(); details.setName("大国"); details.setAge(12); details.setBirthday(new Date()); details.setUserid(login.getUserid()); login.setUserDetails(details); HibernateSessionFactory.getSession().save(login); HibernateSessionFactory.getSession().beginTransaction().commit(); HibernateSessionFactory.closeSession(); } } |
Hibernate: select userdetail_.userid, userdetail_.name as name0_, userdetail_.age as age0_, userdetail_.birthday as birthday0_ from mldn.user_details userdetail_ where userdetail_.userid=? Hibernate: insert into mldn.user_login (password, userid) values (?, ?) Hibernate: insert into mldn.user_details (name, age, birthday, userid) values (?, ?, ?, ?) |
现在已经成功的实现了数据的增加,但是在此处发现执行的步骤一共有三个:
l 第一个是查询了一下User_details表中的数据。
l 增加User_login表中的数据。
l 增加User_details表中的数据。
但是以上完成了快速注册和完整注册,可是用户一开始选择的是快速注册,那么肯定是需要数据的维护,下面针对于已经保存的快速注册的用户做一个维护。
范例:实现完善注册
package cn.cgj.test; import java.util.Date; import cn.cgj.dbc.HibernateSessionFactory; import cn.cgj.pojo.UserDetails; import cn.cgj.pojo.UserLogin; public class UserLoginTestUpdate { public static void main(String[] args) { UserLogin login = new UserLogin(); login.setUserid("daguo1457145398037"); login.setPassword("daguozuibang"); UserDetails details = new UserDetails(); details.setName("小国"); details.setAge(14); details.setBirthday(new Date()); login.setUserDetails(details); details.setUserid(login.getUserid()); HibernateSessionFactory.getSession().update(login); HibernateSessionFactory.getSession().beginTransaction().commit(); HibernateSessionFactory.closeSession(); } } | |
Hibernate: select userdetail_.userid, userdetail_.name as name0_, userdetail_.age as age0_, userdetail_.birthday as birthday0_ from mldn.user_details userdetail_ where userdetail_.userid=? Hibernate: insert into mldn.user_details (name, age, birthday, userid) values (?, ?, ?, ?) Hibernate: update mldn.user_login set password=? where userid=? | Hibernate: select userdetail_.userid, userdetail_.name as name0_, userdetail_.age as age0_, userdetail_.birthday as birthday0_ from mldn.user_details userdetail_ where userdetail_.userid=? Hibernate: update mldn.user_login set password=? where userid=? Hibernate: update mldn.user_details set name=?, age=?, birthday=? where userid=? |
本操作首先还是发出了一个查询,而后发现数据不存在,就直接进行了一条新纪录的增加,随后更新了User_login表记录。User_details表中不存在与主表对应的的数据,那么更新就是增加,如果已经存在了与主表对应的数据,更新就是更新。
范例:查询数据
package cn.cgj.test; import cn.cgj.dbc.HibernateSessionFactory; import cn.cgj.pojo.UserLogin; public class UserLoginTestSelectById { public static void main(String[] args) { UserLogin login = (UserLogin) HibernateSessionFactory.getSession().get(UserLogin.class, "daguo1457145398037"); HibernateSessionFactory.closeSession(); System.out.println(login.getUserid()); System.out.println(login.getUserDetails().getName()); } } |
Hibernate: select userlogin0_.userid as userid1_1_, userlogin0_.password as password1_1_, userdetail1_.userid as userid0_0_, userdetail1_.name as name0_0_, userdetail1_.age as age0_0_, userdetail1_.birthday as birthday0_0_ from mldn.user_login userlogin0_ left outer join mldn.user_details userdetail1_ on userlogin0_.userid=userdetail1_.userid where userlogin0_.userid=? daguo1457145398037 小国 |
现在可以发现次查询是一次性完成的,基于left outer join 这个左外连接,而且采用了多表关联查询,所以从性能的角度上来讲也可以感觉到此类方式的性能不咋地,那么有人说了,那能不能使用延迟加载来解决问题呢?遗憾的是延迟加载不能使用,只能为false,所以这个时候应该采用的是一种取数据的配置(fetch)。默认此选项的内容为join(连接查询),可以修改为select,那么同样的操作再次观察结果。
<one-to-one name="userDetails" class="cn.cgj.pojo.UserDetails" cascade="all" fetch="select"/> |
这个时候使用了两个查询完成,而且都属于单表操作,那么这种做法就和实际之中的处理情况非常类似了。也就说在使用Hibernate处理关系的时候这一点要特别注意,而基于*.hbm.xml文件的配置也就这一点不好。
范例:删除数据
package cn.cgj.test; import org.hibernate.Query; import cn.cgj.dbc.HibernateSessionFactory; public class UserLoginTestDelete { public static void main(String[] args) { String hql = "DELETE FROM UserLogin As ul WHERE ul.userid=?"; Query query = HibernateSessionFactory.getSession().createQuery(hql); query.setString(0, "daguo1457145398037"); System.out.println(query.executeUpdate()); HibernateSessionFactory.getSession().beginTransaction().commit(); HibernateSessionFactory.closeSession(); } } |
Hibernate: delete from mldn.user_login where userid=? 1 |
删除数据的时候直接删除了user_login数据表,但是由于存在外键关联的支持,所以对应的子表记录也将同时被删除。
12.2、基于Annotation实现一对一的配置
如果使用Annotation实际上会更加的容易一些,唯一的区别也是在创建的时候体现。
范例:观察UserLogin.java类的定义
package cn.cgj.pojo; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "user_login", catalog = "mldn") public class UserLogin implements java.io.Serializable { private String userid; private String password; private UserDetails userDetails; public UserLogin() { }
@Id @Column(name = "userid", unique = true, nullable = false, length = 50) public String getUserid() { return this.userid; } public void setUserid(String userid) { this.userid = userid; } @Column(name = "password", length = 32) public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } // 现在进行一对多的关系配置 @OneToOne(fetch = FetchType.LAZY, mappedBy = "userLogin",cascade=CascadeType.ALL) public UserDetails getUserDetails() { return this.userDetails; } public void setUserDetails(UserDetails userDetails) { this.userDetails = userDetails; } } |
范例:观察UserDetails.java类的定义
package cn.cgj.pojo; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @SuppressWarnings("serial") @Entity @Table(name = "user_details", catalog = "mldn") public class UserDetails implements java.io.Serializable { private String userid; private UserLogin userLogin; private String name; private Integer age; private Date birthday; public UserDetails() { } @Id @Column(name = "userid", unique = true, nullable = false, length = 50) public String getUserid() { return this.userid; } public void setUserid(String userid) { this.userid = userid; } @OneToOne(fetch = FetchType.LAZY) @PrimaryKeyJoinColumn public UserLogin getUserLogin() { return this.userLogin; } public void setUserLogin(UserLogin userLogin) { this.userLogin = userLogin; } @Column(name = "name", length = 50) public String getName() { return this.name; } public void setName(String name) { this.name = name; } @Column(name = "age") public Integer getAge() { return this.age; } public void setAge(Integer age) { this.age = age; } @Temporal(TemporalType.DATE) @Column(name = "birthday", length = 10) public Date getBirthday() { return this.birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } } |
下面就可以采用之前的代码进行测试。
总结:
在使用一对一关系配置的时候,如果在处理级联的时候选择错误的方式,那么将直接影响到程序的性能。
基于Annotation的配置,默认情况下都是可以正常使用的配置了。