打赏

Hibernate(5)—— 联合主键 、一对一关联关系映射(xml和注解) 和 领域驱动设计

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下:

  • One to One 映射关系
    • 一对一单向外键(XML/Annotation)
    • 一对一双向外键关联(XML/Annotation)
    • 联合主键
    • 一对一单向外键联合主键(Xml/Annotation)
    • 一对一组件关联(XML/Annotation)
    • 理解组件
  • 领域驱动设计——自动生成数据库脚本
  • 一对一关系的小结
  • 一些出错问题的总结

 

  自动生成数据库脚本

  一般在项目开发过程中,我们的习惯是先建好数据库和表,然后在进行开发,而hibernate作为一款ORM架构模式的实现框架,我们要好好利用,可以利用hibernate反向工程生成*.hbm.xml文件跟POJO类,个人认为由于目前所使用的数据库大部分还都是关系数据库,而hibernate把对数据库的操作都对象化了,更应当从对象出发,生成数据库里面相关表,这样更加符合人认知事物的习惯,hibernate3就已经提供了自带的工具hbm2ddl,能够通过对象建立数据库脚本。

  要生成数据库表,得先建好实体类,然后建立对应的配置文件(or 注解),最后和主配置文件hibernate.cfg.xml关联即可。作用的代码如下:

        SchemaExport se = new SchemaExport(new AnnotationConfiguration().configure());
        // 生成,并输出sql到文件(当前目录)和数据库, 创建表结构,第一个true 表示在控制台打印sql语句,第二个true 表示导入sql语句到数据库 
        se.create(true, true);
View Code

  这就是领域驱动设计/开发(DDD,Domain Driven Design/Development)。

  系统的设计应该基于对象模型,考虑对象的设计和逻辑,然后按照对象模型建立数据库关系模型,这才是现在面向对象开发的步骤,不是说先设计数据库,然后再设计对象。当在设计时,我们的领域模型需要改变,只需修改Hibernate的结构和应用程序,不需要修改数据库架构,只要利用SchemaExport工具重新生成数据库架构就ok。

 

  一对一的单向外键关系

  比如一个人对应一张身份证,反过来一张身份证必须对应一个人,不存在歧义。一个学生类,一个身份证类,学生唯一对应一张身份证,反过来,身份证唯一表示一个学生。这就是一对一的关系,其中学生能持有身份证,故学生表里的身份证的id就是学生表的外键,学生id是学生表的主键。这也叫一对一单向外键关系。

  注解方式:实体类和配置如下:

import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

// 身份证类
@Entity
public class IdCard {
    private String pid;// 身份证号码
    
    private String province;// 省份
    
    @Id
    @GeneratedValue(generator="pid")// 声明一个主键生成策略, 需要用到下面的hibernate的主键生成策略,叫pid
    @GenericGenerator(name="pid",strategy="assigned") // 声明一个主键生成策略,身份证需要手工指定,设置主键生成器的名字,必须是pid
    // @GeneratedValue的作用是JPA的默认实现自定义主键生成策略,@GenericGenerator是hibernate在JPA的基础上增强。
    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }
}
View Code

下面是学生类:

import javax.persistence.*;

// 学生实体类
@Entity
public class Students {
    private int sid;  // 学生编号

    private String sname;

    private IdCard cardId; // 身份证
    
    @OneToOne(cascade = CascadeType.ALL) // 设置学生表的外键:一对一单向外键(Annotation),设置为全部级联更新
    @JoinColumn(name = "pid",unique=true) // 在主表中加入外键pid,唯一的
    public IdCard getCardId() {
        return cardId;
    }

    public void setCardId(IdCard cardId) {
        this.cardId = cardId;
    }
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) // 学生类的主键因为是整型,故设置自动增长
    //@GeneratedValue // 不写其实也ok、相当于上面的效果,也就是默认是auto(MySQL的native类型)
    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }
}
View Code

最后反向生成数据库表。注意:一对一的关系,必须先保存外键的对象,再保存主键的对象,完整的CRUD的代码如下

import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.classic.Session;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestStudentsByAnno{
    private static final Logger LOG = LoggerFactory.getLogger(TestStudentsByAnno.class);

    private static SessionFactory sessionFactory;

    @Before
    public void setUp() {
        LOG.info("setUp");
        AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration().configure();
        sessionFactory = annotationConfiguration.buildSessionFactory();
        testSchemaExport(annotationConfiguration);
    }

    @After
    public void tearDown() {
        LOG.info("tearDown");
        sessionFactory.close();
    }

    @Test
    public void testSave() {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = session.beginTransaction();

        try    {
            IdCard c = new IdCard();
            c.setPid("66666666666");
            c.setProvince("shanxi");
            Students s = new Students();
            s.setSname("zhangsan");
            s.setCardId(c);

            //先保存身份证
            session.save(c);
            //再保存学生
            session.save(s);

            tx.commit();
        } catch(Exception ex) {
            ex.printStackTrace();
            tx.rollback();
        }
    }

    private void testSchemaExport(AnnotationConfiguration annotationConfiguration) {
        LOG.info("=============================testSchemaExport");
        SchemaExport se = new SchemaExport(annotationConfiguration);
        // 生成,并输出sql到文件(当前目录)和数据库, 创建表结构,第一个true 表示在控制台打印sql语句,第二个true 表示导入sql语句到数据库
        se.create(true, true);
    }
}
View Code

Hibernate: insert into IdCard (province, pid) values (?, ?)
Hibernate: insert into Students (pid, sname) values (?, ?)

 

  XML配置方式:注意主控方的配置文件里需要加上<many-to-one name="cardId" column="pid" unique="true"/>,主控方就是持有唯一外键的那个表,name=外键id,保持唯一,因为一对一,必须设置为unique为true!

身份证的配置:

<hibernate-mapping>
  <class name="net.nw.vo.fk.IdCard" table="idcard">
    <id name="pid" column="pid" type="string">
      <generator class="assigned"/>
    </id>

    <property name="province" column="province" type="string"/>
  </class>
</hibernate-mapping>
View Code

学生的配置:作为主控方,需要加上一对一的关系映射属性,使用的是many-to-one标签

<hibernate-mapping>
  <class name="net.nw.vo.fk.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>

    <property name="sname" column="sname" type="string"/>
    <many-to-one name="cardId" column="pid" unique="true"/>
  </class>
</hibernate-mapping>
View Code

还有主配置文件的关联,下面是测试代码:

    public void testSave() {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = session.beginTransaction();

        try    {
            IdCard c = new IdCard();
            c.setPid("1111111111");
            c.setProvince("dadadada");
            Students s = new Students();
            s.setSname("daaaaaaa");
            s.setCardId(c);
            
            //先保存身份证
            session.save(c);
            //再保存学生
            session.save(s);
            
            tx.commit();
        } catch(Exception ex) {
            ex.printStackTrace();
            tx.rollback();
        }
    }
View Code

成功插入了数据

 

  一对一双向外键关联

  很简单,可以理解为双方都持有对方的引用,也就是你中有我,我中有你的一对一关联关系,比如学生表包含身份证id,同时身份证表也包含学生id,同时互为对方的外键。

  注解方式:实体类和配置如下:

  两个实体类其中之一必须使用注解:@OneToOne(mappedBy=“实体类的引用”) ,属性mappedBy是必须的,目的是设置将控制权交给另外一方(实体类)。双向的关联必须设置mappedBy属性。也就是说双向关联只能交给一方去控制,不可能在双方都设置外键保存关联关系,否则都无法保存。因为学生表包含身份证id,同时身份证表也包含学生id,互为对方的外键。如果互为主控方,那么先保存谁呢?如果要保存学生,则应该先保存身份证,而身份证也是主控方,要保存身份证必须先保存学生……死循环发生。

 1 import javax.persistence.*;
 2 
 3 //学生实体类
 4 @Entity
 5 public class Students {
 6     private int sid;  //编号
 7 
 8     private String sname; //姓名
 9 
10     private IdCard cardId; // 学生持有身份证的引用,因为学生表关联了身份证表
11 
12 //    cascade属性需要设置为all,表明删除student时同时删除对应的身份证
13     @OneToOne(cascade = CascadeType.ALL) // 本例子里学生作为主控方,则需要在身份证类那儿声明mappedBy属性
14     @JoinColumn(name="pid",unique=true) // 学生表的外键是身份证id:pid,唯一的
15     public IdCard getCardId() {
16         return cardId;
17     }
18 
19     public void setCardId(IdCard cardId) {
20         this.cardId = cardId;
21     }
22     
23     @Id
24     @GeneratedValue
25     // 默认 等价于 @GeneratedValue(strategy=GenerationType.AUTO)
26     public int getSid() {
27         return sid;
28     }
29 
30     public void setSid(int sid) {
31         this.sid = sid;
32     }
33 
34     public String getSname() {
35         return sname;
36     }
37 
38     public void setSname(String sname) {
39         this.sname = sname;
40     }
41 }
View Code

  本例子里学生表(实体类)作为主控方,那么本类不需要设置@OneToOne(mappedBy=“”),而是需要在另一方——身份证类里声明@OneToOne(mappedBy=“”)属性

 1 import org.hibernate.annotations.GenericGenerator;
 2 import javax.persistence.*;
 3 
 4 //身份证类
 5 @Entity
 6 public class IdCard {
 7     private String pid;//身份证号码
 8 
 9     private String province;//省份
10 
11     private Students stu;//反过来身份证表也关联学生表,对应程序也就是身份证类包含学生对象的引用
12 
13     // 身份证把控制权交给学生控制,mappedBy值是主控方持有的引用(外键)
14     // 记住:只要双向关联,必须双方有一方要指定mappedBy属性,不能互相指定,目的是避免死循环发生
15     @OneToOne(mappedBy="cardId")
16     public Students getStu() {
17         return stu;
18     }
19 
20     public void setStu(Students stu) {
21         this.stu = stu;
22     }
23 
24     @Id
25     @GeneratedValue(generator="pid")
26     @GenericGenerator(name="pid",strategy="assigned")
27     public String getPid() {
28         return pid;
29     }
30 
31     public void setPid(String pid) {
32         this.pid = pid;
33     }
34 
35     public String getProvince() {
36         return province;
37     }
38 
39     public void setProvince(String province) {
40         this.province = province;
41     }
42 }
View Code

  测试生成数据库脚本:

alter table Students drop foreign key FK73AC29B8917F52BE
drop table if exists IdCard
drop table if exists Students
create table IdCard (pid varchar(255) not null, province varchar(255), primary key (pid))
create table Students (sid integer not null auto_increment, sname varchar(255), pid varchar(255) unique, primary key (sid))
alter table Students add index FK73AC29B8917F52BE (pid), add constraint FK73AC29B8917F52BE foreign key (pid) references IdCard (pid)
View Code

  我发现其实和刚刚一对一单向外键关联的表结构一样,好像没什么区别啊?其实关键问题是所谓的一对一双向关联,就是实体类里虽然彼此包含了对方的引用,但是在实际设计数据库时的关键问题是——把控制权交给谁?上例把IdCard的stu交给了学生类去控制,如果反过来,学生类stu的身份证pid交给身份证去控制,看代码:

 1 import org.hibernate.annotations.GenericGenerator;
 2 import javax.persistence.*;
 3 
 4 //身份证类
 5 @Entity
 6 public class IdCard {
 7     private String pid;//身份证号码
 8 
 9     private String province;//省份
10 
11     private Students stu;//反过来身份证表也关联学生表,对应程序也就是身份证类包含学生对象的引用
12 
13     @OneToOne(cascade=CascadeType.ALL) // 现在身份证是主控方
14     @JoinColumn(name="sid",unique=true)
15     public Students getStu() {
16         return stu;
17     }
18 
19     public void setStu(Students stu) {
20         this.stu = stu;
21     }
22 
23     @Id
24     @GeneratedValue(generator="pid")
25     @GenericGenerator(name="pid",strategy="assigned")
26     public String getPid() {
27         return pid;
28     }
29 
30     public void setPid(String pid) {
31         this.pid = pid;
32     }
33 
34     public String getProvince() {
35         return province;
36     }
37 
38     public void setProvince(String province) {
39         this.province = province;
40     }
41 }
View Code

  主控方交给身份证

 1 import javax.persistence.*;
 2 
 3 //学生实体类
 4 @Entity
 5 public class Students {
 6     private int sid;  //编号
 7 
 8     private String sname; //姓名
 9 
10     private IdCard cardId; // 学生持有身份证,学生表关联了身份证表
11 
12     @OneToOne(mappedBy="stu") // 学生持有的身份证把控制权交给身份证控制
13     public IdCard getCardId() {
14         return cardId;
15     }
16 
17     public void setCardId(IdCard cardId) {
18         this.cardId = cardId;
19     }
20     
21     @Id
22     @GeneratedValue
23     // 默认 等价于 @GeneratedValue(strategy=GenerationType.AUTO)
24     public int getSid() {
25         return sid;
26     }
27 
28     public void setSid(int sid) {
29         this.sid = sid;
30     }
31 
32     public String getSname() {
33         return sname;
34     }
35 
36     public void setSname(String sname) {
37         this.sname = sname;
38     }
39 }
View Code

  测试数据库脚本,发现和刚刚不一样了,身份证里有外键sid,而学生表没有了之前的外键pid,说明身份证去控制学生了。

create table IdCard (pid varchar(255) not null, province varchar(255), sid integer unique, primary key (pid))

create table Students (sid integer not null auto_increment, sname varchar(255), primary key (sid))
View Code

  注意:千万别让双方都持有对方的引用作为外键,这样是没有意义的。虽然语法不会出错,执行起来肯定会出死循环的问题。继续测试,现在是身份证控制学生,那么先保存学生(学生id是外键),在保存身份证:

 1 import org.hibernate.Session;
 2 import org.hibernate.SessionFactory;
 3 import org.hibernate.Transaction;
 4 import org.hibernate.cfg.AnnotationConfiguration;
 5 import org.hibernate.cfg.Configuration;
 6 import org.hibernate.tool.hbm2ddl.SchemaExport;
 7 import org.junit.After;
 8 import org.junit.Before;
 9 import org.junit.Ignore;
10 import org.junit.Test;
11 
12 public class TestStudentsByAnno {
13     private static SessionFactory sessionFactory;
14     
15     @Before
16     public void setUp() throws Exception {
17         System.out.println("setUp()...");
18         sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
19     }
20 
21     @After
22     public void tearDown() throws Exception {
23         System.out.println("tearDown()...");
24         sessionFactory.close();
25     }
26 
27     @Test
28 
29     public void testSave() {
30         Session session = sessionFactory.getCurrentSession();
31         Transaction tx = session.beginTransaction();
32 
33         try    {
34             Students s = new Students();
35             s.setSname("xiaoli");
36             IdCard c = new IdCard();
37             c.setPid("12345656788");
38             c.setProvince("hebei");
39             c.setStu(s);
40             //先保存学生
41             session.save(s);
42             //再保存身份证
43             session.save(c);
44             
45             tx.commit();
46         } catch(Exception ex)    {
47             ex.printStackTrace();
48             tx.rollback();
49         }
50     }
51 
52     @Test
53     @Ignore
54     public void testSchemaExport() {
55         SchemaExport se = new SchemaExport(new Configuration().configure());
56         se.create(true, true);
57     }
58 }
View Code

结果:

 

  XML配置方式:实体类和配置如下:

  大同小异,理解了注解的方式,xml就是书写的注意,主控方还是和之前一对一单向管理的写法一样:<many-to-one name="cardId" column="pid" unique="true"/>,被控方需要注意下,要写成<one-to-one name="stu" property-ref="cardId"/>,下面实现一个学生为主控方的例子:

<?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>
  <class name="net.nw.vo.bfk.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>

    <property name="sname" column="sname" type="string"/>
    <!-- 主控方,外键是身份证pid -->
    <many-to-one name="cardId" column="pid" unique="true"/>
  </class>
</hibernate-mapping>

-----------------------------------------------------------------------------
<?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>
  <class name="net.nw.vo.bfk.IdCard" table="idcard">
    <id name="pid" column="pid" type="string">
      <generator class="assigned"/>
    </id>

    <property name="province" column="province" type="string"/>
    <!-- 身份证是被控的,也需要配置,使用one-to-one属性
     property-ref意思是把控制交给学生类,让学生使用cardId这个身份证的引用去控制
     -->
    <one-to-one name="stu" property-ref="cardId"/>
  </class>
</hibernate-mapping>
View Code

  实体类不变,还是互相持有对方的引用,然后测试也是注意,先保存外键的实体类身份证,最后保存学生类:

            IdCard c = new IdCard();
            c.setPid("999999999");
            c.setProvince("beijing");
            Students s = new Students();
            s.setSname("wangwu");
            s.setCardId(c);
            
            //先保存身份证
            session.save(c);
            //再保存学生
            session.save(s);
            
            tx.commit();
View Code

 

  什么是联合主键?

  联合主键就是用多个字段组成的主键。用这个主键包含的字段作为主键,这个组合在数据表中是唯一,且加了主键索引。比如,订单表里有很多字段,一般情况只要有个订单号做主键就可以,但是现在可能会有补充订单,使用相同的订单号,那么这时单独使用订单号就不可以,因为会有重复。那么可以再使用个订单序列号作为区别。把订单号和订单序列号设成联合主键。即使订单号相同,订单序列号不同也是可以唯一的。

 

  一对一单向外键联合主键关联

  之前的主键都是一个字段,比如身份证的主键pid,现在在之前的身份证id上加一个血型字段作为联合主键,那么需要新建一个联合主键类,代码如下:

  注解方式:实体类和配置如下:

  先创建联合主键类,须实现serializable接口,进行序列化,还必须重写hashCode()和equals()方法。联合主键类的注解是@Embeddable,使能嵌入的意思,而实体类的主键使用@EmbeddedId标识,代码如下:

import javax.persistence.Embeddable;
import java.io.Serializable;

//联合主键类的注解是 Xxxxable 类型,嵌入的意思
@Embeddable
public class IdCardPK implements Serializable{
    /**
     * 必须实现序列化接口
     */
    private static final long serialVersionUID = 1L;

    private String pid;//身份证号码,主键字段

    private String bloodType;//新加的字段:血型,也作为主键字段

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public String getBloodType() {
        return bloodType;
    }

    public void setBloodType(String bloodType) {
        this.bloodType = bloodType;
    }

    /**
     * 必须重写equals和hashCode
     *
     * @param obj Object
     * @return boolean
     */
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }
}
View Code

  再创建对应的实体类:

 1 import javax.persistence.EmbeddedId;
 2 import javax.persistence.Entity;
 3 import javax.persistence.OneToOne;
 4 
 5 //身份证实体类
 6 @Entity
 7 public class IdCard {
 8     private IdCardPK pk;// 身份证的主键是联合主键 IdCardPK pk
 9 
10     private String province;// 省份
11 
12     private Students stu; // 身份证持有学生类的引用
13 
14 //    把控制权交给学生
15     @OneToOne(mappedBy="cardId")//只要是双向关联,就一定要指定mappedBy
16     public Students getStu() {
17         return stu;
18     }
19 
20     public void setStu(Students stu) {
21         this.stu = stu;
22     }
23 
24 //    联合主键的主键形式是Xxxxid类型,具体的主键设置在联合主键类里进行
25     @EmbeddedId
26     public IdCardPK getPk() {
27         return pk;
28     }
29 
30     public void setPk(IdCardPK pk) {
31         this.pk = pk;
32     }
33     
34     public String getProvince() {
35         return province;
36     }
37 
38     public void setProvince(String province) {
39         this.province = province;
40     }
41 }
42 
43 import javax.persistence.CascadeType;
44 import javax.persistence.Entity;
45 import javax.persistence.GeneratedValue;
46 import javax.persistence.Id;
47 import javax.persistence.JoinColumn;
48 import javax.persistence.JoinColumns;
49 import javax.persistence.OneToOne;
50 
51 //学生实体类
52 @Entity
53 public class Students {
54     private int sid;  //编号
55 
56     private String sname; //姓名
57 
58     private IdCard cardId; // 学生的外键,学生控制身份证
59 
60 //    主控类的外键设置比较复杂点,因为有多个主键字段了
61     @OneToOne(cascade=CascadeType.ALL)
62     @JoinColumns(
63             {
64 //                    referencedColumnName设置对应数据库的字段名字,name是类的字段名字
65                     @JoinColumn(name="pid",referencedColumnName="pid"),
66                     @JoinColumn(name="bloodType",referencedColumnName="bloodtype")
67             }
68     )
69     public IdCard getCardId() {
70         return cardId;
71     }
72 
73     public void setCardId(IdCard cardId) {
74         this.cardId = cardId;
75     }
76     
77     @Id
78     @GeneratedValue
79     public int getSid() {
80         return sid;
81     }
82 
83     public void setSid(int sid) {
84         this.sid = sid;
85     }
86 
87     public String getSname() {
88         return sname;
89     }
90 
91     public void setSname(String sname) {
92         this.sname = sname;
93     }
94 }
View Code

  注意千万别忘了配置hibernate主文件:

<mapping class="net.nw.vo.ufk.Students" />
<mapping class="net.nw.vo.ufk.IdCard" />
<mapping class="net.nw.vo.ufk.IdCardPK" />
View Code

  生成的数据库脚本:

create table IdCard (bloodType varchar(255) not null, pid varchar(255) not null, province varchar(255), primary key (bloodType, pid))
create table Students (sid integer not null auto_increment, sname varchar(255), bloodType varchar(255), pid varchar(255), primary key (sid))

  同样保存数据也是先保存受控类,在保存主控类。

 

  XML配置方式:实体类和配置如下:

   理解了上述主键的意思,xml也就很简单了:

  被控方:

<composite-id name="pk" class="联合主键的类">

  <key-property name="pid" column="pid" type="string"/>

  <key-property name="bloodType" type="string"/>

  <generator class="assigned"></generator>

</composite-id>
View Code

  主控方:

<many-to-one name="cardId">

   <column name="pid" unique="true"/>

   <column name="bloodid"/>

</many-to-one>
View Code

注意,联合主键类不需要单独的映射文件进行配置!代码如下:

<?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>
  <class name="net.nw.vo.ufk.IdCard" table="idcard">
      <!-- 联合主键的配置 -->
   <composite-id name="pk" class="net.nw.vo.ufk.IdCardPK">
      <key-property name="pid" column="pid" type="string"/>
      <key-property name="bloodType" type="string"/>
      <generator class="assigned"/>
   </composite-id>

   <property name="province" column="province" type="string"/>
  </class>
</hibernate-mapping>
------------------------------------------------------------------

<?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>
  <!-- 主控方-->
  <class name="net.nw.vo.ufk.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>
    <property name="sname" column="sname" type="string"/>

    <!-- 主控方的外键 -->
    <many-to-one name="cardId">
      <!-- 该外键的表又包含联合主键 -->
      <column name="pid" unique="true"/>
      <column name="bloodid"/>
    </many-to-one>
  </class>
</hibernate-mapping>
View Code

 

  一对一组件关联映射

  组件类就是一个POJO类,什么主键或者映射配置都不需要!因为实际上就是只有一张表,是最简单的,先不说理论,看例子:

  注解方式:实体类和配置如下:

  IdCard里什么主键都不写,当然配置也不需要,就是一个POJO类

//身份证类,就是一个POJO类
public class IdCard {
    private String pid;//身份证号码

    private String province;//省份

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }
}
View Code

  只有被组件注入的实体类需要配置,它的主键注解为@Embedded

import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

//学生实体类
@Entity
public class Students {
    private int sid;  //编号

    private String sname; //姓名

    private IdCard cardId;

    @Id
    @GeneratedValue
    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }
    
    @Embedded
    public IdCard getCardId() {
        return cardId;
    }

    public void setCardId(IdCard cardId) {
        this.cardId = cardId;
    }
}
View Code

  执行数据库脚本:

drop table if exists Students
create table Students (sid integer not null auto_increment, pid varchar(255), province varchar(255), sname varchar(255), primary key (sid))

  执行插入测试:

            IdCard c = new IdCard();
            c.setPid("1234567");
            c.setProvince("hebei");

            Students s = new Students();
            s.setSname("a");
            s.setCardId(c);

            session.save(s);
            
            tx.commit();
View Code

  

  开始扯理论:如何理解组件?
  组件是某个实体类的逻辑组成部分,这个组件与实体的本质区别在于组件没有oid(参考前面的例子,它没有任何配置),故可以理解为能把组件当做值对象。例子中把省份和身份证id单独提取抽象出来作为了一个组件类,这个类就叫做值对象,也就是所说的组件。

  采用组件映射的优点

  实现了对象细粒度的划分,层次更加分明,复用率高。上面的student类,分为了基本信息sid、sname,还将身份证分离了出来,此外,还可以将爱好,家庭成员等信息再作为一类分离出来,进行细粒度的划分。分离出来的组件,也可以作为其他对象(例如teacher、employer等)的组件,这样就为复用提供了方便。

 

  XML配置方式:实体类和配置如下:

  映射关系文件: 

<component name="cardId" class="net.nw.vo.component.IdCard">

  <property name="pid" column="pid" type="string"></property>

  <property name="province" type="string"></property>

</component>
View Code

映射文件中,通过<component>标签,将组件类的属性映射到学生表中,这样,student表中的字段就会是Student类中的sid、sname以及组件类中的pid和provincel。而在数据库中,身份证类不用单独创建一张表。代码如下:

<?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>
  <class name="net.nw.vo.bfk.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>

    <property name="sname" column="sname" type="string"/>

    <component name="cardId" class="net.nw.vo.component.IdCard">
     <property name="pid" column="pid" type="string"></property>
     <property name="province" type="string"></property>
    </component>
  </class>
</hibernate-mapping>
View Code

 

  一对一映射关系结:

  再看一个例子,比如web开发中最最常见的用户表,里面有联系方式和姓名:

  实体细粒度模型

public class Tusers {
    private   Integer  id;
    private   Name      name;        // 1.姓名
    private   Contact   contact;     // 2.联系方式
}

public class Name {
    private   String firstname;
    private   String lastname;
}

public class Contact {
     private   String address;
     private   String email;
     private   String telphone;
}
View Code

  粗粒度的数据库模型

  实体一对一关联:假如数据库中已经存在contact、name表,并且Contact、Name设计成实体类,则Tusers 类与Contact,Name类之间成为一对一关联关系。否则就是组件映射关系。

 

  问题小结:

  期间使用注解的时候出现了一些瑕疵:

  1. 忘记导入dom4j包
  2. 忘记导入hibernate注解需要用到的ejb3-persistence.jar(其余的两个是hibernate-annotations.jar,hibernate-commons-annotations.jar包)。
  3. org.hibernate.MappingException: Unknown entity: xxxx异常:
    1. 可能是因为使用注解的时候没有导入正确的包。Entity包是javax.persistence.Entity;而不是hibernate包下的annotation。
    2. 使用注解时没有在*.cfg.xml下配置<mapping class=""/>,从而造成org.hibernate.MappingException: Unknown entity异常的
  4. java.lang.NoClassDefFoundError: javax/persistence/Cacheable异常: javax.persistence.CacheableJPA 2.0 规范中的东西,需要导入jar包:hibernate-jpa-2.0-api-1.0.1.Final.jar
  5. 关于java.lang.NoSuchMethodError: javax.persistence.OneToOne.orphanRemoval()Z异常:JUnit报Caused by: java.lang.NoSuchMethodError: javax.persistence.OneToOne.orphanRemoval()Z,原因是ejb3-persistence.jar和hibernate-jpa-2.0-api-1.0.1.Final.jar有冲突,删掉ejb3-persistence.jar即可。
  6. 千万注意hibernate主配置文件里必须引入mapping——实体关系映射
  7. 注意区分注解和配置文件的实体映射关系在主配置文件的写法。
  8. 联合主键类必须实现序列化接口和重写equals和hashCode方法。
  9. Specified key was too long; max key length is 1024 bytes异常,这个是MySQL5.2.0以及之前的bug,升级数据库或者减少联合主键的字段长度就能解决,当MySQL数据库的字符集为latin1时,是没有问题的,如果用UTF8字符集,就会报这个错误,因为在设置联合主键时,如果采用UTF-8编码,那么只能存储1024/3=300多个单位长度,所以长度不能超过300.

 

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

 

posted @ 2016-03-21 02:09  dashuai的博客  阅读(3820)  评论(0编辑  收藏  举报
Flag Counter欢迎关注微信公众号