Hibernate 性能优化一对一关联映射
概述:
hibernate提供了两种映射一对一关联的方式:按照外键映射和按照主键映射。
下面以员工账号和员工档案为例 ,介绍两种映射方式,并使用这两种映射方式分别完成以下持久化操作:
(1)保存员工档案的同时分配给员工一个账号
(2)加载员工档案的同时加载账号信息
1.按照外键映射
第一步:创建实体类users1(主表)和resume1
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 员工表 */ public class Users1 { private Integer userid; //员工编号 private String username; //名称 private String userpass; //密码 private Resume1 resume1; //档案对象 public Integer getUserid() { return userid; } public void setUserid(Integer userid) { this.userid = userid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUserpass() { return userpass; } public void setUserpass(String userpass) { this.userpass = userpass; } public Resume1 getResume1() { return resume1; } public void setResume1(Resume1 resume1) { this.resume1 = resume1; } }
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 档案表 */ public class Resume1 { private Integer resid; // 档案编号 private String resname; //档案名称 private String rescardno; //编号 private Users1 users1; //隶属的员工 public Integer getResid() { return resid; } public void setResid(Integer resid) { this.resid = resid; } public String getResname() { return resname; } public void setResname(String resname) { this.resname = resname; } public String getRescardno() { return rescardno; } public void setRescardno(String rescardno) { this.rescardno = rescardno; } public Users1 getUsers1() { return users1; } public void setUsers1(Users1 users1) { this.users1 = users1; } }
第二步:书写配置文件
Users1.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 package="cn.lex.entity"> <class name="Users1" table="USERS1"> <id name="userid"><generator class="native"></generator></id> <property name="username" column="USERNAME" type="string"></property> <property name="userpass" column="USERPASS" type="string"></property> <!-- 配置一对一外键方式的关联 property-ref:通过Resume1的users1属性,建立了从users1到Resume1对象的关联 --> <one-to-one name="resume1" class="Resume1" property-ref="users1"></one-to-one> </class> </hibernate-mapping>
Resume1.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 package="cn.lex.entity"> <class name="Resume1" table="RESUME1"> <id name="resid"><generator class="native"></generator></id> <property name="resname" column="RESNAME" type="string"></property> <property name="rescardno" column="RESCARDNO" type="string"></property>
<!-- column 与之关联表的外键 unique 可以表达Resume1对象和Users1对象之间的一对一关联关系
unique属性的默认值为false 在这里要设为true才有效
--> <many-to-one name="users1" class="Users1" cascade="all" unique="true" column="RESUSERID"></many-to-one> </class> </hibernate-mapping>
第三步:书写测试类
工具类:
package cn.lex.util; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * Created by accp on 2017/1/16. */ public class HibernateUtil { private static final ThreadLocal<Session> thLocal=new ThreadLocal<Session>(); private static Configuration cfg; private final static SessionFactory factory; static{ cfg=new Configuration().configure(); factory=cfg.buildSessionFactory(); } /** *静态的方法 返回session */ public static Session currentSession(){ Session session = thLocal.get(); if(session==null){ session=factory.openSession(); thLocal.set(session); } return session; } /** * 静态的方法 关闭session连接 */ public static void closeSession(){ Session session = thLocal.get(); if(session!=null){ thLocal.set(null); } session.close(); } }
测试类:
@Test /** * 插入数据 */ public void add(){ Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); //创建用户 Users1 users1=new Users1(); users1.setUsername("微冷的雨"); users1.setUserpass("123"); //创建档案 Resume1 resume1=new Resume1(); resume1.setResname("入职档案"); resume1.setRescardno("123"); //关联 users1.setResume1(resume1); resume1.setUsers1(users1); session.save(resume1); tx.commit(); HibernateUtil.closeSession(); }
SQL语句:
Hibernate:
drop sequence hibernate_sequence
Hibernate:
create sequence hibernate_sequence start with 1 increment by 1
Hibernate:
create table RESUME1 (
resid number(10,0) not null,
RESNAME varchar2(255 char),
RESCARDNO varchar2(255 char),
RESUSERID number(10,0),
primary key (resid)
)
Hibernate:
create table USERS1 (
userid number(10,0) not null,
USERNAME varchar2(255 char),
USERPASS varchar2(255 char),
primary key (userid)
)
Hibernate:
alter table RESUME1
add constraint UK_j52v359nm8h98x9dmuvbka6tb unique (RESUSERID)
Hibernate:
alter table RESUME1
add constraint FKhp8mabk7imse3kohw5lul5k29
foreign key (RESUSERID)
references USERS1
Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
insert
into
USERS1
(USERNAME, USERPASS, userid)
values
(?, ?, ?)
Hibernate:
insert
into
RESUME1
(RESNAME, RESCARDNO, RESUSERID, resid)
values
(?, ?, ?, ?)
结果:
看到这里是不是觉得跟前面学的多对一关联映射没什么不同?开始的时候我也是这种心态,但是你接下来看就会知道了,我们来做一个查询操作。
模拟一下场景,你是不是很不理解uses1.hbm.xml中<one-to-one>中为什么要加上property-ref,它到底是干什么的呢?
接下来我们先把它去掉,看看效果。
Users1.hbm.xml:
测试结果:
接下来我们看看当我们加上property-ref属性的时候得到的结果:
这样是不是很直观的就可以看出两个结果的不同,那么我们得到什么结论呢?
当我们做一对一映射时,在主键表的配置中,一定要书写property-ref属性,否则当你查询数据时,底层生成SQL时,会主动把两个表的主键id关联,从而得到错误的结果。
2.按照主键映射
第一步:实体类Users2(从表)和Resume2(主表)
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 员工表 从表 */ public class Users2 { private Integer userid; private String username; private String userpass; private Resume2 resume2; public Integer getUserid() { return userid; } public void setUserid(Integer userid) { this.userid = userid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUserpass() { return userpass; } public void setUserpass(String userpass) { this.userpass = userpass; } public Resume2 getResume2() { return resume2; } public void setResume2(Resume2 resume2) { this.resume2 = resume2; } }
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 档案表 主表 */ public class Resume2 { private Integer resid; private String resname; private String rescardno; private Users2 users2; public Integer getResid() { return resid; } public void setResid(Integer resid) { this.resid = resid; } public String getResname() { return resname; } public void setResname(String resname) { this.resname = resname; } public String getRescardno() { return rescardno; } public void setRescardno(String rescardno) { this.rescardno = rescardno; } public Users2 getUsers2() { return users2; } public void setUsers2(Users2 users2) { this.users2 = users2; } }
第二步:配置文件Users2.hbm.xml和Resume2.hbmxml
Resume2.hbmxml:
<?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 package="cn.lex.entity"> <class name="Resume2" table="RESUME2"> <id name="resid"> <!-- 主键生成策略--> <generator class="native"> </generator></id> <property name="resname" column="RESNAME" type="string"></property> <property name="rescardno" column="RESCARDNO" type="string"></property> <one-to-one name="users2" class="Users2" cascade="all"></one-to-one> </class> </hibernate-mapping>
Users2.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 package="cn.lex.entity"> <class name="Users2" table="USERS2"> <id name="userid"> <!-- 使用foreign 主键生成策略额,hibernate就会保证Users2对象与Resume2对象共享同一个OID--> <generator class="foreign"> <param name="property">resume2</param> </generator> </id> <property name="username" column="USERNAME" type="string"></property> <property name="userpass" column="USERPASS" type="string"></property> <!-- <one-to-one>元素的constrained属性为true ,表明Users2表的USERID主键同时作为外键参照RESUME2表的主键 --> <one-to-one name="resume2" class="Resume2" constrained="true"></one-to-one> </class> </hibernate-mapping>
第三步:书写测试类
@Test public void add1(){ Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); //创建用户 Users2 users2=new Users2(); users2.setUsername("帅的离谱"); users2.setUserpass("111"); //创建档案 Resume2 resume2=new Resume2(); resume2.setResname("离职档案"); resume2.setRescardno("112"); //关联 users2.setResume2(resume2); resume2.setUsers2(users2); session.save(resume2); tx.commit(); HibernateUtil.closeSession(); }
SQL语句:
Hibernate: create table RESUME2 ( resid number(10,0) not null, RESNAME varchar2(255 char), RESCARDNO varchar2(255 char), primary key (resid) ) Hibernate: create table USERS2 ( userid number(10,0) not null, USERNAME varchar2(255 char), USERPASS varchar2(255 char), primary key (userid) ) Hibernate: alter table USERS2 add constraint FKbhwvd9exxeaymfiyjkgdqgtcp foreign key (userid) references RESUME2 Hibernate: select hibernate_sequence.nextval from dual Hibernate: insert into RESUME2 (RESNAME, RESCARDNO, resid) values (?, ?, ?) Hibernate: insert into USERS2 (USERNAME, USERPASS, userid) values (?, ?, ?)
数据库体现:
结论:USERS2表的USERID字段是主键,同时作为外键参照RESUME2表的主键,即USERS2表与RESUME2表共享主键。
接下来我们写一个查询测试代码,看一下有什么不同?
测试代码:
@Test public void select1(){ Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Users2 users2 = session.load(Users2.class, 1); System.out.println(users2.getResume2().getResname()); tx.commit(); HibernateUtil.closeSession(); }
SQL:
以上是Hibernate提供的两种一对一关联映射的方式,在实际的开发中,按照数据库表的设计选择相应的映射方式,当两个类之间只有一对一的关联时,应该优先考虑使用主键映射方式。
3.组件映射
建立关系数据模型的一个重要原则是在不会导致数据冗余的前提下,尽可能减少数据库表的数目及表之间的外键参照关系。以员工信息为例,员工信息中有员工的家庭地址信息,如果把地址信息单独放在一张表中,然后建立员工信息表和地址信息表之间的外键参照关系,当每次查询员工信息时,都需建立者两个表的连接。建立表的连接是很耗时的操作,为了提高数据库运行性能,可以把这两张表的信息整合在一张员工信息表EMPINFO中。
第一步:创建EmpInfo类和EmpHomeAddress类
EmpInfo表:
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 员工信息表 */ public class EmpInfo { private Integer eid; private String ename; private EmpHomeAddress ehome; public Integer getEid() { return eid; } public void setEid(Integer eid) { this.eid = eid; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public EmpHomeAddress getEhome() { return ehome; } public void setEhome(EmpHomeAddress ehome) { this.ehome = ehome; } }
EmpHomeAddress表:
package cn.lex.entity; /** * Created by accp on 2017/1/18. * 员工地址表 */ public class EmpHomeAddress { private String ehomestreet; private String ehomecity; private String ehomeprovince; private String ehomezipcode; private EmpInfo empinfo; public String getEhomestreet() { return ehomestreet; } public void setEhomestreet(String ehomestreet) { this.ehomestreet = ehomestreet; } public String getEhomecity() { return ehomecity; } public void setEhomecity(String ehomecity) { this.ehomecity = ehomecity; } public String getEhomeprovince() { return ehomeprovince; } public void setEhomeprovince(String ehomeprovince) { this.ehomeprovince = ehomeprovince; } public String getEhomezipcode() { return ehomezipcode; } public void setEhomezipcode(String ehomezipcode) { this.ehomezipcode = ehomezipcode; } public EmpInfo getEmpinfo() { return empinfo; } public void setEmpinfo(EmpInfo empinfo) { this.empinfo = empinfo; } }
第二步:创建EmpInfo.hbm.xml配置文件
EmpInfo.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 package="cn.lex.entity"> <class name="EmpInfo" table="EMPINFO"> <id name="eid" column="EID"> <generator class="native"></generator> </id> <property name="ename" column="ENAME" type="string"></property> <component name="ehome" class="EmpHomeAddress"> <parent name="empinfo"/> <property name="ehomestreet" column="EHOMESTREET" type="string"></property> <property name="ehomecity" column="EHOMECITY" type="string"></property> <property name="ehomeprovince" column="EHOMEPROVINCE" type="string"></property> <property name="ehomezipcode" column="EHOMEZIPCODE" type="string"></property> </component> </class> </hibernate-mapping>
<component>元素:表明ehome属性是EmpInfo类的一个组成部分,在Hibernate中称为组件。
<component>元素有两个属性:
(1)name:设定被映射的持久化类的属性名,此处为EmpInfo类的ehome属性。
(2)class:设定ehome属性的类型,此处表明ehome属性为EmpHomeAddress类型。
<componet>元素还包含一个<parent>子元素和一系列<property>子元素。<parent>元素指定EmpHomeAddress类所属的整体类,在这里设为empinfo。
第三步:书写测试类:
/** * 插入数据 组件映射 */ @Test public void add2() { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); //创建一个员工对象 EmpInfo emp = new EmpInfo(); emp.setEname("张靓颖"); //创建一个员工地址对象 EmpHomeAddress address = new EmpHomeAddress(); address.setEhomecity("海淀区"); address.setEhomeprovince("北京"); address.setEhomestreet("五道口"); address.setEhomezipcode("100000"); address.setEmpinfo(emp); emp.setEhome(address); session.save(emp); tx.commit(); HibernateUtil.closeSession(); }
SQL :
数据库数据:
在这里是没有EmpHomeAddress类的映射文件,数据库也不会创建表。
查询语句:
@Test public void select2() { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); EmpInfo empInfo = session.load(EmpInfo.class, 1); System.out.println("姓名:" + empInfo.getEname()); System.out.print("地址:" + empInfo.getEhome().getEhomeprovince() + empInfo.getEhome().getEhomecity() + empInfo.getEhome().getEhomestreet()); tx.commit(); HibernateUtil.closeSession(); }
SQL语句:
上面以EmpInfo和EmpHomeAddress类为例介绍了组件映射,Hibernate用<componet>元素来映射EmpInfo类的ehome属性。
EmpHomeAddress类作为Hibernate的组件,具有以下特征:
(1)EmpHomeAddress类没有OID,在数据库中也没有与之对应的表,不需要单独创建EmpHomeAddress类的映射文件。
(2)不能单独持久化EmpHomeAddress对象。EmpHomeAddress对象的生命周期依赖于EmpInfo对象的生命周期。
(3)其他持久化类不允许关联EmpHomeAddress类,EmpHomeAddress可以关联其他持久化类。