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可以关联其他持久化类。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

posted @ 2017-01-18 14:24  刘二雄  阅读(218)  评论(0编辑  收藏  举报