Hibernate4Step2

hardwork

本着应用的目的快速学习Hibernate4框架

  • Hibernate映射

前面的话

本节主要介绍:

  1. 一对多和多对一单向与双向映射
  2. 实际开发中需要注意的地方

学生和班级是典型的多对一,一对多关系,如图:
studentAndClass

编写Student类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package cn.codeforgod.model;


public class Student
{

private long id;
private String name;
private Class c;

public long getId()
{

return id;
}

public void setId(long id)
{

this.id = id;
}

public String getName()
{

return name;
}

public void setName(String name)
{

this.name = name;
}

public Class getC()
{

return c;
}

public void setC(Class c)
{

this.c = c;
}

@Override
public String toString()
{

return "Student [id=" + id + ", name=" + name + ", c=" + c + "]";
}

}

编写Class类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package cn.codeforgod.model;


import java.util.HashSet;
import java.util.Set;

public class Class
{

private long id;
private String name;
private Set<Student> students = new HashSet<Student>();

public long getId()
{

return id;
}
public void setId(long id)
{

this.id = id;
}
public String getName()
{

return name;
}
public void setName(String name)
{

this.name = name;
}
public Set<Student> getStudents()
{

return students;
}
public void setStudents(Set<Student> students)
{

this.students = students;
}

}

编写Student.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0"?>
<!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.codeforgod.model">

<class name="Student" table="t_student">
<id name="id" column="studentId">
<generator class="native"></generator>
</id>

<property name="name" column="studentName"></property>

<many-to-one name="c" column="classId" class="cn.codeforgod.model.Class" cascade="save-update"></many-to-one>
</class>

</hibernate-mapping>

说明:
<many-to-one name="c" column="classId" class="cn.codeforgod.model.Class" cascade="save-update"></many-to-one>
这句代码说明Student对象中的字段c对应一个Class对象,多个Student对象对应一个Class对象,外键为classId。

cascade="save-update"是级联保存更新,这样当保存或更新一个Student对象时,如果Student对象中包含Class对象,那么自动保存更新Student内的Class对象。

如果此时不编写Class.hbm.xml中的一对多关系便是单向多对一映射。单向映射一般是指单向多对一映射。

编写Class.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0"?>
<!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.codeforgod.model">

<class name="Class" table="t_class">
<id name="id" column="classId">
<generator class="native"></generator>
</id>

<property name="name" column="className"></property>

<set name="students" cascade="save-update" inverse="true">
<key column="classId"></key>
<one-to-many class="cn.codeforgod.model.Student"/>
</set>
</class>

</hibernate-mapping>

说明:

<set name="students" cascade="save-update" inverse="true">
    <key column="classId"></key>
    <one-to-many class="cn.codeforgod.model.Student"/>
</set>

以上代码说明Class对象中的students集合对应多个Student对象,一个Class对象对应多个Student对象,该字段在数据库中的名字是classId作为Student的外键。

cascade="save-update"是级联保存更新,这样当保存或更新一个Class对象时,如果Class对象中包含Student对象,那么自动保存更新Class内的Student对象。

inverse="true"表示该字段由”多”的一端来维护关系,这样的话cascade="save-update"便不起作用了。

Tip:*实际开发中大多也是在”多”端去维护”一”端的关系,因为业务等原因,你想啊,操作Student肯定比操作Class多,当同时在”多”端和”一”端维护关系时便会出现SQL冗余,Oh,What a pity!*

编写hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!--数据库连接设置 -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql:///db_hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</property>


        <!-- 方言 -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- 控制台显示SQL -->
        <property name="show_sql">true</property>

        <!-- 自动更新表结构 -->
        <property name="hbm2ddl.auto">update</property>

          <mapping resource="cn/codeforgod/model/Student.hbm.xml"/>
          <mapping resource="cn/codeforgod/model/Class.hbm.xml"/>
          <mapping resource="cn/codeforgod/model/Node.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

JUnit测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package cn.codeforgod.test;

import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import cn.codeforgod.model.Class;
import cn.codeforgod.model.Student;
import cn.codeforgod.util.HibernateUtil;

public class TestStudentAndClass
{

public SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
public Session session;

@Before
public void before()
{

session = sessionFactory.openSession();
session.beginTransaction();
}

@Test
public void testSaveClass()
{

Class c = new Class();
c.setName("12计科");

Student s1 = new Student();
s1.setName("zhangSan");

Student s2 = new Student();
s2.setName("liSi");

c.getStudents().add(s1);
c.getStudents().add(s2);

System.out.println(c.getStudents());

// 保存Class的同时级联保存Class中的Student
// 需要将Class.hbm.xml中的inverse属性置为false
session.save(c);
}

@Test
public void testSaveStudents()
{

Class c = new Class();
c.setName("11计科");

Student s1 = new Student();
s1.setName("11zhangSan");
s1.setC(c);

Student s2 = new Student();
s2.setName("11liSi");
s2.setC(c);

// 保存Student的同时保存级联保存Class
session.save(s1);
session.save(s2);
}

// 双向映射时可以通过获取“一”端同时获取“多”端的数据
@Test
public void getStudentsByClass()
{

Class c = (Class) session.get(Class.class, Long.valueOf(1));
Set set = c.getStudents();
System.out.println(set);
}

// 在“一”端设置inverse="true"属性使得在“多”端维护关系避免SQL冗余
@Test
public void testInverse()
{

Class c = new Class();
c.setName("10计科");

Student s1 = new Student();
s1.setName("10zhangSan");

session.save(c);
session.save(s1);

s1.setC(c);

// 当在“一”端配置inverse属性时,只能在“多”端进行关系设置
// 以下代码失效
c.getStudents().add(s1);
}

@After
public void after()
{

session.getTransaction().commit();
session.close();
sessionFactory.close();
}
}

这里不再说明了,代码注释里写的很清楚了。

Tips:*关于关系映射要熟练掌握,仔细思考其中的原理才能运用得好~*

级联操作

级联操作是指操作设置级联方式的对象时,级联操作对应关系的其它对象,通过cascade属性配置,常用的值有:none,all,save-update,delete。
Tips:级联删除在开发过程中很少用,因为这样可能会导致业务数据丢失等问题。

关于级联操作更多请参考如下博文:

hibernate级联操作详解

Hibernate关系映射

关于Hibernate关系映射我就不想多说了,一起来看看人家别人的见解:

以下内容摘自:Hibernate 实体关联关系映射—-总结

Hibernate映射关系错综复杂,在实际中真的都能用到吗?不用行吗?

在我看来,Hibernate提供这些映射关系,常用就是一对一和多对一,并且在能不用连接表的时候尽量不要用连接表。多对多会用到,如果用到了,应该首先考虑底层数据库设计是否合理。

在实际开发中,在Hibernate关联关系之外常常还有另外一种选择方案,表各自作为单表映射,业务逻辑控制外键关系(有时候就是一个相关联的列,但不一定要加外键约束),这样更加灵活,并且数据的完整性同样有保证。

当然,“单表映射,业务控制外键关系”并不是说Hibernate的实体关联功能是多余的,Hibernate的实体关联的优点很多,随便拿本书都是讲优点,用好了会让开发人员感觉更方便,现在我也是两种方案结合使用。比如对于不很确定的两个实体,常常选用单表关联。

以前在初学Hibernate还没有完全搞清楚这些关联关系的时候,就是用单表映射,业务控制外键关系做的,发现没有任何问题,程序同样运行得很好。

看了这些是不是后悔浪费时间学习映射关系了?呵呵,Hibernate的OR Mapping是Hibernate的灵魂,我相信Hibernate的创始人比我们一般人的理解更深刻。只有学会了这些才能体会Hibernate设计者的思想。学一个东西,不光自己写代码,还应该能看懂别人的代码才行。因此系统学习这些关联映射还是大有必要的。

然后关于深入理解关系映射我推荐一篇博文,写的真叫一个赞…赞…赞不绝口!

链接如下:Hibernate映射解析——七种映射关系

看完这篇博文,我感觉我有一种爱上这位博主的感觉了~(呵呵,开玩笑的啦^_^)

自身一对多、多对一介绍

应用举例:XML节点既可以是父节点也可以是子节点,一个父节点有多个子节点(一对多),多个子节点有共同的一个父节点(多对一),然后它们都是同一类型(都是节点),这就是典型的自身一对多、多对一的关系。

应用场景:在菜单(Menu)中,菜单项(MenuItem)既可以是父菜单也可以是子菜单,一个父菜单可以有多个子菜单,多个子菜单有同一个父菜单。

文字不如图片:

node

其实通过以上知识,完全可以编写此类应用,不过这个就当作检测自己的作业吧~(需要深刻理解一对多和多对一,要不连个放一起可能会晕+_+)

编写Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package cn.codeforgod.model;


import java.util.HashSet;
import java.util.Set;

public class Node
{

private long id;
private String name;
private Node parentNode;
private Set<Node> childNodes = new HashSet<Node>();

public long getId()
{

return id;
}
public void setId(long id)
{

this.id = id;
}
public String getName()
{

return name;
}
public void setName(String name)
{

this.name = name;
}
public Node getParentNode()
{

return parentNode;
}
public void setParentNode(Node parentNode)
{

this.parentNode = parentNode;
}
public Set<Node> getChildNodes()
{

return childNodes;
}
public void setChildNodes(Set<Node> childNodes)
{

this.childNodes = childNodes;
}

}

编写Node.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0"?>
<!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.codeforgod.model">

<class name="Node" table="t_node">
<id name="id" column="nodeId">
<generator class="native"></generator>
</id>

<property name="name" column="nodeName"></property>

<many-to-one name="parentNode" column="parentId" class="cn.codeforgod.model.Node" cascade="save-update"></many-to-one>

<set name="childNodes" cascade="save-update" inverse="true">
<key column="parentId"></key>
<one-to-many class="cn.codeforgod.model.Node"/>
</set>

</class>

</hibernate-mapping>

我想,我还是把原理再讲一遍吧,我怕有人看不懂–!

<many-to-one name="parentNode" column="parentId" class="cn.codeforgod.model.Node" cascade="save-update"></many-to-one>

这句代码是说Node(自己)中的字段parentNode是Node类型,多个Node(自己)对应一个Node,外键是parentId。

<set name="childNodes" cascade="save-update" inverse="true">
    <key column="parentId"></key>
    <one-to-many class="cn.codeforgod.model.Node"/>
</set>

以上代码是说Node(自己)中的字段childNodes集合对应多个Node对象,一个Node(自己)对应多个Node,被参考的外键是parentId。

紧接着将<mapping resource="cn/codeforgod/model/Node.hbm.xml"/>写在Hibernate.cfg.xml中

JUnitTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package cn.codeforgod.test;


import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import cn.codeforgod.model.Node;
import cn.codeforgod.util.HibernateUtil;

public class NodeTest
{

public SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
public Session session;

@Before
public void setUp() throws Exception
{

session = sessionFactory.openSession();
session.beginTransaction();
}

@After
public void tearDown() throws Exception
{

session.getTransaction().commit();
session.close();
sessionFactory.close();
}

@Test
public void test()
{

Node parentNode = new Node();
parentNode.setName("根节点");

Node subNode1 = new Node();
subNode1.setName("子节点1");

Node subNode2 = new Node();
subNode2.setName("子节点2");

subNode1.setParentNode(parentNode);
subNode2.setParentNode(parentNode);

session.save(subNode1);
session.save(subNode2);

}

}

末尾的话

本文采用hbm.xml方式配置映射规则,然而Hibernate官方推荐使用Annotation方式,然后据我了解,一流公司用Annotation,二流公司用xml,这两种方式优缺点参半,Annotation肯定要好一点的,个人建议先学xml(方式)再学Annotation(方式)。先会用,再深入。

Tips:


关于一对一和多对多的关系映射,请参考本站的Hibernate4Step2_2。

坚持原创技术分享,您的支持将鼓励我继续创作