Hibernate中的多对一映射

举最常见的员工与部门来举例子:

Emp:

 1 package org.ninth.entity;
 2
 3 import java.math.BigDecimal;
 4
 5 public class Emp  implements java.io.Serializable {
 6
 7      private Integer id;
 8
 9      private Dept dept;
10
11      private String name;
12
13      private BigDecimal salary;
14
15
16
17     public Emp() {
18
19     }
20
21     public Emp(String name, BigDecimal salary) {
22
23         this.name = name;
24
25         this.salary = salary;
26
27     }
28
29     public Emp(Dept dept, String name, BigDecimal salary) {
30
31        this.dept = dept;
32
33        this.name = name;
34
35        this.salary = salary;
36
37     }
38
39
40
41     public Integer getId() {
42
43         return this.id;
44
45     }
46
47
48
49     public void setId(Integer id) {
50
51         this.id = id;
52
53     }
54
55     public Dept getDept() {
56
57         return this.dept;
58
59     }
60
61
62
63     public void setDept(Dept dept) {
64
65         this.dept = dept;
66
67     }
68
69     public String getName() {
70
71         return this.name;
72
73     }
74
75
76
77     public void setName(String name) {
78
79         this.name = name;
80
81     }
82
83     public BigDecimal getSalary() {
84
85         return this.salary;
86
87     }
88
89
90
91     public void setSalary(BigDecimal salary) {
92
93         this.salary = salary;
94
95     }
96
97 }

Dept:

 1 package org.ninth.entity;
 2
 3
 4
 5
 6
 7 public class Dept  implements java.io.Serializable {
 8
 9
10
11
12
13      private Integer id;
14
15      private String name;
16
17      private String location;
18
19     public Dept() {
20
21     }
22
23
24
25     public Dept(String name, String location) {
26
27        this.name = name;
28
29        this.location = location;
30
31     }
32
33
34
35     public Integer getId() {
36
37         return this.id;
38
39     }
40
41
42
43     public void setId(Integer id) {
44
45         this.id = id;
46
47     }
48
49     public String getName() {
50
51         return this.name;
52
53     }
54
55
56
57     public void setName(String name) {
58
59         this.name = name;
60
61     }
62
63     public String getLocation() {
64
65         return this.location;
66
67     }
68
69
70
71     public void setLocation(String location) {
72
73         this.location = location;
74
75     }

配置文件如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2
 3 <!DOCTYPE hibernate-mapping PUBLIC
 4
 5     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 6
 7     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 8
 9 <hibernate-mapping package="org.ninth.entity" >
10
11     <class name="Dept" table="t_dept">
12
13         <id name="id" type="integer" column="id">
14
15             <generator class="native" />
16
17         </id>
18
19         <property name="name" type="string">
20
21             <column name="dept_name" not-null="true" length="50" />
22
23         </property>
24
25         <property name="location" type="string">
26
27             <column name="dept_loc" not-null="true" length="200" />
28
29         </property>
30
31     </class>
32
33
34
35     <class name="Emp" table="t_emp" lazy="true">
36
37         <id name="id" type="integer">
38
39             <generator class="native" />
40
41         </id>
42
43         <many-to-one name="dept" class="Dept" column="dept_id"
44
45             foreign-key="fk_emp_dept"/>
46
47         <property name="name" type="string">
48
49             <column name="emp_name" not-null="true" length="50" />
50
51         </property>
52
53         <property name="salary" type="big_decimal">
54
55             <column name="emp_sal" not-null="true" precision="7"
56
57                 scale="2" />
58
59         </property>
60
61     </class>
62
63
64
65 </hibernate-mapping>

映射表如下:

t_emp:

 1 t_emp | CREATE TABLE `t_emp` (
 2
 3  `id` int(11) NOT NULL auto_increment,
 4
 5  `dept_id` int(11) default NULL,
 6
 7  `emp_name` varchar(50) NOT NULL,
 8
 9  `emp_sal` decimal(7,2) NOT NULL,
10
11  PRIMARY KEY  (`id`),
12
13  KEY `fk_emp_dept` (`dept_id`),
14
15  CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dept_id`) REFERENCES `t_dept` (`id`)
16
17 ) ENGINE=InnoDB DEFAULT CHARSET=gbk |

t_dept:

 1 t_dept | CREATE TABLE `t_dept` (
 2
 3  `id` int(11) NOT NULL auto_incremen
 4
 5  `dept_name` varchar(50) NOT NULL,
 6
 7  `dept_loc` varchar(200) NOT NULL,
 8
 9  PRIMARY KEY  (`id`)
10
11  )ENGINE=InnoDB DEFAULT CHARSET=gbk 

接下来就来进行对emp的各项操作:

首先,来进行最简单的插入操作:

在main方法中,有如下语句:

 1     Dept dept=new Dept();
 2
 3        dept.setName("项目部");
 4
 5        dept.setLocation("中国北京");
 6
 7
 8
 9        Session s=HbnUtil.getSession();
10
11        Transaction tx=s.beginTransaction();
12
13        s.save(dept);
14
15        tx.commit();
16
17        s.close();

很明显,成功运行。

 1 mysql> select * from t_dept;
 2
 3 +----+-----------+----------+
 4
 5 | id | dept_name  | dept_loc |
 6
 7 +----+-----------+----------+
 8
 9 |  1 | 项目部    | 中国北京 |
10
11 +----+-----------+----------+
12
13 1 row in set (0.00 sec)

删除dept表中的数据,如果插入一个员工呢?

 1     Emp emp=new Emp();
 2
 3        emp.setName("张三");
 4
 5        emp.setSalary(new BigDecimal("4000"));
 6
 7        Session s=HbnUtil.getSession();
 8
 9        Transaction tx=s.beginTransaction();
10
11        s.save(emp);
12
13        tx.commit();
14
15        s.close();

可以发现也没有任何问题,但是因为没有给员工指定部门,故其部门ID为NULL。

 1 mysql> select * from t_emp;
 2
 3 +----+---------+----------+---------+
 4
 5 | id | dept_id   | emp_name | emp_sal |
 6
 7 +----+---------+----------+---------+
 8
 9 |  1 |    NULL | 张三     | 4000.00 |
10
11 +----+---------+----------+---------+
12
13 1 row in set (0.00 sec)

再有如下代码,如果同时给定部门与员工,并且为员工指定部门,那么又会有什么效果呢?(以后的操作如果没有特别说明都会将先前的数据全部删除)

 1        Dept dept=new Dept();
 2
 3        dept.setName("项目部");
 4
 5        dept.setLocation("中国北京");
 6
 7        Emp emp=new Emp();
 8
 9        emp.setName("张三");
10
11        emp.setSalary(new BigDecimal("5000"));
12
13        emp.setDept(dept);
14
15        Session s=HbnUtil.getSession();
16
17        Transaction tx=s.beginTransaction();
18
19        s.save(dept);
20
21        s.save(emp);
22
23        tx.commit();
24
25        s.close();

可以发现数据库中也成功的进行了插入操作,需要注意的是这里先持久化部门,再持久化员工。从而可以发现在控制台的SQL语句只有两条:

 1 Hibernate:
 2
 3     insert
 4
 5     into
 6
 7         t_dept
 8
 9         (dept_name, dept_loc)
10
11     values
12
13         (?, ?)
14
15 Hibernate:
16
17     insert
18
19     into
20
21         t_emp
22
23         (dept_id, emp_name, emp_sal)
24
25     values
26
27         (?, ?, ?)

Sql结果:

 1 mysql> select * from t_emp;
 2
 3 +----+---------+----------+---------+
 4
 5 | id | dept_id | emp_name | emp_sal |
 6
 7 +----+---------+----------+---------+
 8
 9 |  1 |       1 | 张三     | 5000.00 |
10
11 +----+---------+----------+---------+
12
13 1 row in set (0.01 sec)
14
15
16
17 mysql> select * from t_dept;
18
19 +----+-----------+----------+
20
21 | id | dept_name | dept_loc |
22
23 +----+-----------+----------+
24
25 |  1 | 项目部    | 中国北京 |
26
27 +----+-----------+----------+
28
29 1 row in set (0.00 sec)

两条SQL语句,同时保证了部门与员工的联系。且手动插入也是如此的写法。但是,是否可以在程序中更简单呢?

试着将save(dept)与save(emp)换位再执行:

即:

 1 Dept dept=new Dept();
 2
 3        dept.setName("项目部");
 4
 5        dept.setLocation("中国北京");
 6
 7        Emp emp=new Emp();
 8
 9        emp.setName("张三");
10
11        emp.setSalary(new BigDecimal("5000"));
12
13        emp.setDept(dept);
14
15        Session s=HbnUtil.getSession();
16
17        Transaction tx=s.beginTransaction();
18
19        s.save(emp);s.save(dept);
20
21        tx.commit();
22
23        s.close();

程序依旧能够正常运行,查看DB也可以发现是同样的结果,但是在控制台却输出了如下的语句:

 1 Hibernate:
 2
 3     insert
 4
 5     into
 6
 7         t_emp
 8
 9         (dept_id, emp_name, emp_sal)
10
11     values
12
13         (?, ?, ?)
14
15 Hibernate:
16
17     insert
18
19     into
20
21         t_dept
22
23         (dept_name, dept_loc)
24
25     values
26
27         (?, ?)
28
29 Hibernate:
30
31     update
32
33         t_emp
34
35     set
36
37         dept_id=?,
38
39         emp_name=?,
40
41         emp_sal=?
42
43     where
44
45         id=?

可以发现,与上面的先持久化Dept再持久Emp相比。此外多了一条update语句。对于程序而言,对数据库的访问开销是很大的,此处多了一条update语句,当数据量大时,就要多出大量的update,就会造成性能较低。因此一般应该先持久化Dept,再持久化Emp。即在实际中应当先持久化一的一方,再持久化多的一方。那么以上又为什么会多一条update语句呢?

可以这样认为,hibernate在执行过程中,执行到save(emp)时,则去查找他所对应的Dept,在此处,很幸运地是当前的emp找到了他所对应的dept,那么则保存自己的dept_id时就会引用dept的id.但是很不幸地是,这个emp对应的dept没有id.因为在save()之前,emp与dept都是属于暂态的对象,hibernate都没有为他们生成主键id.所以保存此emp时,则置其dept_id为空。然后接下来再运行save(dept).到此时两个对象都持久化了吗?没有。因为真正持久化是在tx.commit()提交时才有效,提交之前,hibernate再次检查两个对象,发现emp所对应的dept又有了id.那么自己的引用的dept_id自然就不能再为空,而应当update将其设置为与对应的dept相同的id.于是。就有了三条语句。

程序执行的结果与先持久化emp再持久化dept是相同的。此处不再列出。

接下来试着将映射文件改正一下:

1 <many-to-one name="dept" class="Dept" column="dept_id"
2
3            foreign-key="fk_emp_dept" cascade="save-update"/>

再试着执行上述代码:

 1 Hibernate:
 2
 3     insert
 4
 5     into
 6
 7         t_dept
 8
 9         (dept_name, dept_loc)
10
11     values
12
13         (?, ?)
14
15 Hibernate:
16
17     insert
18
19     into
20
21         t_emp
22
23         (dept_id, emp_name, emp_sal)
24
25     values
26
27         (?, ?, ?)

可以发现此时却只有两条SQL语句.在程序中同样是先持久化emp,再持久化dept为何此处只有两条SQL呢?

cascade的意思是:是否级联持久化,该如何级联.此处在映射文件中,我们为此属性配置的值为:cascade=”save-update”.这就是 ,在保存many-to-one的many所对应的这个类时,此处即为emp,会自动地去查找他是否有对应的一的一方,即为dept.如果有,并且该对象没有持久化,那么,hibernate将自动地帮助我们持久化一的一方:dept.可以这样理解上述程序过程:
         hibernate在持久化emp时,发现emp有其所对应的dept.因为我们设置了

emp.saveDept(dept).同上面,此处会去找寻这个dept的id.明显,dept仍旧是暂态的,故他还是没有id.但是因为我们设置了cascade=”save-update”,在此处发现dept为暂态,则会自动地级联地将dept先进行保存.然后再执行s.save(dept)但是也和上面的一样,真正的持久是在tx.commit()时发生的,那么在此时来讲,已经对dept持久化了,则后面的一句s.save(dept)就没有效果了.

这样就可以发现,其实后面一句s.save(dept)可以不要.事实也确实如此.因此

在保存多对一时,如果设置了cascade=”save-update”,则只要设置好多对一的关联,然后持久化多的一方即可.当然此处是在数据库中一的一方不存在的情况,但如果在相同的配置下,重新执行以下代码:(此处DB中数据不进行删除)

 1      Dept dept = new Dept();
 2
 3        dept.setId(1);
 4
 5        dept.setName("项目部");
 6
 7        dept.setLocation("中国北京");
 8
 9
10
11        Emp emp = new Emp();
12
13        emp.setName("李四");
14
15        emp.setSalary(new BigDecimal("4000"));
16
17        emp.setDept(dept);
18
19
20
21        Session s = HbnUtil.getSession();
22
23        Transaction tx = s.beginTransaction();
24
25
26
27        s.save(emp);
28
29
30
31        tx.commit();
32
33        s.close();

执行生成的SQL如下:

 1 Hibernate:
 2
 3     insert
 4
 5     into
 6
 7         t_emp
 8
 9         (dept_id, emp_name, emp_sal)
10
11     values
12
13         (?, ?, ?)
14
15 Hibernate:
16
17     update
18
19         t_dept
20
21     set
22
23         dept_name=?,
24
25         dept_loc=?
26
27     where
28
29         id=?

两次执行完毕后,DB中的数据如下:

 1 mysql> select * from t_dept;
 2
 3 +----+-----------+----------+
 4
 5 | id | dept_name | dept_loc |
 6
 7 +----+-----------+----------+
 8
 9 |  1 | 项目部    | 中国北京 |
10
11 +----+-----------+----------+
12
13 1 row in set (0.00 sec)
14
15
16
17 mysql> select * from t_emp;
18
19 +----+---------+----------+---------+
20
21 | id | dept_id | emp_name | emp_sal |
22
23 +----+---------+----------+---------+
24
25 |  1 |       1 | 张三     | 5000.00 |
26
27 |  2 |       1 | 李四     | 4000.00 |
28
29 +----+---------+----------+---------+
30
31 2 rows in set (0.00 sec)

此处就有一个问题了,虽然对DB中进行了正确的插入操作,结果也是对的,但是在生成的SQL中,update语句似乎是多余的.因为如果是我们手动插入一条emp记录.则只要有上面的那条insert语句,因为我们并不想更改相关联的dept记录.这里是只插入一条emp,如果有一组emp,也许是一百个,一千个,或者是一万个.那么对DB的访问就会多出一百次,一千次,继而一万次无用的update语句.这样就大大的降低了程序的效率.

此处就是cascade=”save-update”在作怪了.对于这种情况,Hibernate认为对于外部给定的一个暂态对象,既然有级联,那就是要进行持久化的,但是因为dept的id在数据库中已经存在,那么他就只能是update,而不能save了.然后再将上述代码进行更改:

 1 Emp emp = new Emp();
 2
 3        emp.setName("王五");
 4
 5        emp.setSalary(new BigDecimal("6000"));
 6
 7
 8
 9
10
11        Session s = HbnUtil.getSession();
12
13        Transaction tx = s.beginTransaction();
14
15
16
17        Dept dept=(Dept);s.load(Dept.class, 1);
18
19        emp.setDept(dept);
20
21        s.save(emp);
22
23
24
25        tx.commit();
26
27        s.close();

此时生成的就只有如下的SQL:

 1 Hibernate:
 2
 3     insert
 4
 5     into
 6
 7         t_emp
 8
 9         (dept_id, emp_name, emp_sal)
10
11     values
12
13         (?, ?, ?)

程序运行结果如下:

 1 mysql> select * from t_emp;
 2
 3 +----+---------+----------+---------+
 4
 5 | id | dept_id | emp_name | emp_sal |
 6
 7 +----+---------+----------+---------+
 8
 9 |  1 |       1 | 张三     | 5000.00 |
10
11 |  2 |       1 | 李四     | 4000.00 |
12
13 |  3 |       1 | 王五     | 6000.00 |
14
15 +----+---------+----------+---------+
16
17 3 rows in set (0.00 sec)
18
19
20
21 mysql> select * from t_dept;
22
23 +----+-----------+----------+
24
25 | id | dept_name | dept_loc |
26
27 +----+-----------+----------+
28
29 |  1 | 项目部    | 中国北京 |
30
31 +----+-----------+----------+

因为此处的dept是从load出来的,也许他不一定存在(一会再讨论),但是他已经不是暂态对象,故不再对其进行update.然后在插入的时候,根据dept的id,对emp进行dept_id的填充。因为id为1的dept确实存在,那么在插入emp的时候就成功地进行了插入。引用外键成功。然后如果给定一个没有的id.如:

1       Dept dept=(Dept)s.load(Dept.class, 2);
2
3        emp.setDept(dept);
4
5        s.save(emp);

由于在DB中并没有id为2的dept对象的存在,故在执行此段代码时将会报告异常:

1 Cannot add or update a child row: a foreign key constraint fails (`tarena/t_emp`, CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dept_id`) REFERENCES `t_dept` (`id`))

开始的时候hibernate并不知道这个对象不存在,故其继续插入emp,但是当执行插入语句时,发现所引用的dept_id=2并不存在,则报告异常。

将load方法换成get方法,将会多出一条select,这是load与get的区别,暂时不讨论.

此时同时也可以发现,通过load出来的对象,其实并没有到DB中进行查询,即此时只保证了dept的id存在,其它值并没有保证,是否可以这样:

 1 Dept dept = new Dept();
 2
 3        dept.setId(1);
 4
 5
 6
 7        Emp emp = new Emp();
 8
 9        emp.setName("赵六");
10
11        emp.setSalary(new BigDecimal("6000"));
12
13
14
15        Session s = HbnUtil.getSession();
16
17        Transaction tx = s.beginTransaction();
18
19
20
21        emp.setDept(dept);
22
23        s.save(emp);
24
25
26
27        tx.commit();
28
29        s.close();

其实可以很明显地想到前面已经有过类似的代码,此处如果一运行,那么肯定会导致id为1的Dept只有一个id.因为会update 将其它两项置为空。又因为dept表中的数据都是not-null的,因此程序会报异常.

但是,如果保证update不会执行呢?

如果要保证update不会执行,就需要知道为什么会执行update语句.它是从何而来的.知道当映射文件中没有做任何修改时,就不会有任何的插入dept的语句,因此,试着删除新增cascade=”save-update”,看下有什么效果.

1 <many-to-one name="dept" class="Dept" column="dept_id"
2
3            foreign-key="fk_emp_dept"/>

程序正常运行,且只有一条insert语句.并且结果也没有任何错误.

 1 mysql> select * from t_emp;
 2
 3 +----+---------+----------+---------+
 4
 5 | id | dept_id | emp_name | emp_sal |
 6
 7 +----+---------+----------+---------+
 8
 9 |  1 |       1 | 张三     | 5000.00 |
10
11 |  2 |       1 | 李四     | 4000.00 |
12
13 |  3 |       1 | 王五     | 6000.00 |
14
15 |  5 |       1 | 赵六     | 6000.00 |
16
17 +----+---------+----------+---------+
18
19 4 rows in set (0.00 sec)
20
21
22
23 mysql> select * from t_dept;
24
25 +----+-----------+----------+
26
27 | id | dept_name | dept_loc |
28
29 +----+-----------+----------+
30
31 |  1 | 项目部    | 中国北京 |
32
33 +----+-----------+----------+

可以看到编号为6的员工很正常地显示在了数据库中,并且其所关联的部门也正确.因此在存储一个员工,确认有这个部门的时候只要给定部门id就可以对员工进行插入.但一般会在映射文件中给定配置如下:

1 <many-to-one name="dept" class="Dept" column="dept_id"
2
3            foreign-key="fk_emp_dept" cascade="none"/>

而不是不设置 cascade

由此可以发现:

在映射文件中,可以设定many-to-one的属性cascade为none或者是save-update.

当为none时,存多的一方时,一的一方只要有id即可,但必须id在数据库中有,因为不能违反唯一性约束。也不能为空。即当要再次给多的一方存数据,而一的一方在数据库中有值时,只要给一的一方给定一个ID,而不必GET或者LOAD出来。如下:

dept.setId(2);

emp.setDept(dept);

s.save(emp)

因为dept.id为2这条数据在数据库中已有。故只要这样插入即可,而不必

dept=s.get(Dept.class,2);

emp.setDept(dept);s.save(emp)

这样就减少了一次DB的访问,其实只要保证Emp所对应的dept的id在数据库中存在,用load方法也可以得到相同的结果。

当为save-update时,首先会先保存或者更新一的一方。取决于一的一方是否在数据库中有对象。一的一方没有数据时,就会插入,否则就会更新。

查询:

首先给定再多几个部门与员工。

 1 | id | dept_name | dept_loc |
 2
 3 +----+-----------+----------+
 4
 5 |  1 | 项目部    | 中国北京 |
 6
 7 |  2 | 财务部    | 中国北京 |
 8
 9 |  3 | 市场部    | 中国北京 |
10
11 |  4 | 项目部    | 中国北京 |
12
13 +----+-----------+----------+
14
15 4 rows in set (0.00 sec)
16
17
18
19 mysql> select * from t_emp;
20
21 +----+---------+----------+---------+
22
23 | id | dept_id | emp_name | emp_sal |
24
25 +----+---------+----------+---------+
26
27 |  1 |       1 | 张三     | 5000.00 |
28
29 |  2 |       1 | 李四     | 4000.00 |
30
31 |  3 |       1 | 王五     | 6000.00 |
32
33 |  5 |       1 | 赵六     | 6000.00 |
34
35 |  7 |       2 | 王二麻子 | 8000.00 |
36
37 |  8 |       3 | 凤凰于飞 | 8000.00 |
38
39 |  9 |       3 | 九天玄女 | 8000.00 |
40
41 | 10 |       4 | 九天玄武 | 8000.00 |
42
43 | 11 |       4 | 九天玄凤 | 8000.00 |
44
45 | 12 |       2 | 九天玄烨 | 8000.00 |
46
47 | 13 |       3 | 九天玄烨 | 8000.00 |
48
49 +----+---------+----------+---------+

因为是查询,因此此时不理会cascade的值。

先试最简单的查询方式,get()与load()方法。

1 Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.get(Emp.class, 1);
4
5        System.out.println(e.getName());
6
7        s.close();

运行结果为:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 张三

没有错误,如果我想得到这个人所在部门名称呢,是否可以直接使用getter方法取出来呢?

1        Session s = HbnUtil.getSession();
2        Emp e = (Emp)s.get(Emp.class, 1);
3        System.out.println(e.getDept().getName());
4        s.close();

再看执行结果:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 Hibernate:
22
23     select
24
25         dept0_.id as id0_0_,
26
27         dept0_.dept_name as dept2_0_0_,
28
29         dept0_.dept_loc as dept3_0_0_
30
31     from
32
33         t_dept dept0_
34
35     where
36
37         dept0_.id=?
38
39 项目部

发现可以得到,但是却有两条语句,如果是手动去查找的话只要做一个表连接就可以得到了,而且此处如果只想知道部门名称,如果查询出这个员工所有的其它信息是没有意义的。是否可以做到用一个表连接就查询出来,而还是使用这个方法呢。在解决这个问题之前,先将程序改成如下:

1        Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.get(Emp.class, 1);
4
5        s.close();
6
7        System.out.println(e.getDept().getName());

程序运行后,发现如果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session

发现程序在执行到取出员工部门名称时抛出了如上的异常,原因是no session因为session在上面已经被我关掉了。再改为如下:

1 Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.get(Emp.class, 1);
4
5        System.out.println(e.getClass());
6
7        System.out.println(e.getDept().getClass());
8
9        s.close();

程序结果发现:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 class org.ninth.entity.Emp
22
23 class org.ninth.entity.Dept$$EnhancerByCGLIB$$710400bf

此处可以发现Emp为自己定义的实体类,但Dept却变为了一个怪怪的类,其实这是因为懒加载的原因而造成的。并且也发现并没有使用到Emp类的任何方法,但是却执行了SQL语句。再将上述程序全部的get()方法改为load()方法再试一遍:

1 Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.load(Emp.class, 1);
4
5        System.out.println(e.getClass());
6
7        System.out.println(e.getDept().getClass());
8
9        s.close();

结果如下:

 1 class org.ninth.entity.Emp$$EnhancerByCGLIB$$f2036ff9
 2
 3 Hibernate:
 4
 5     select
 6
 7         emp0_.id as id1_0_,
 8
 9         emp0_.dept_id as dept2_1_0_,
10
11         emp0_.emp_name as emp3_1_0_,
12
13         emp0_.emp_sal as emp4_1_0_
14
15     from
16
17         t_emp emp0_
18
19     where
20
21         emp0_.id=?
22
23 class org.ninth.entity.Dept$$EnhancerByCGLIB$$fefd442a

可以发现一个问题:在此处,在还未执行emp.getDept().getClass()之前,并没有执行SQL语句,但是在一调用这个方法后,就立刻出现了SQL,并且前后Emp与Dept类都是怪怪的类,称之为代理对象。

再运行如下:

1        Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.load(Emp.class, 1);
4
5        System.out.println(e.getDept().getName());
6
7        s.close();

结果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 Hibernate:
22
23     select
24
25         dept0_.id as id0_0_,
26
27         dept0_.dept_name as dept2_0_0_,
28
29         dept0_.dept_loc as dept3_0_0_
30
31     from
32
33         t_dept dept0_
34
35     where
36
37         dept0_.id=?
38
39 项目部

发现此处与上面用get()方法得到的结果是一样的。看似一样,其实不一样。因为get会立刻将数据查询出来,而load不会。看以下两个的结果:

1 Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.load(Emp.class, 1);
4
5        s.close();
6
7        System.out.println(e.getName());

结果:

1 Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session

再看用get方法:

1 Session s = HbnUtil.getSession();
2
3        Emp e = (Emp)s.get(Emp.class, 1);
4
5        s.close();
6
7        System.out.println(e.getName());

结果:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 张三

在这里可以发现,同样在session关闭后,使用load方法不能得到其中的属性,而使用get方法却可以得到属性。对于get与load方法:一般认为:

  • get()方法:先到缓存中找对象,如果找到了,就返回对象。如果没找到,则进入数据库中去查找,如果再找到了,则返回,否则返回null。
  • load()方法:先到缓存中找对象,如果找到了,则返回对象。如果没找到,则动态创建一个代理对象放到缓存中,然后返回代理对象。

在下一次使用load()方法返回的对象时,如果是真实对象,则使用之。如果是代理对象,则到DB中去查找,找到了,返回真实对象

填满代理对象,并使用之,如果查找不到,则抛出异常。

        代理对象类 extends 真实对象类。因此,真实对象不能定义为final类型的。

可以试着输出如下:

 1 Session s = HbnUtil.getSession();
 2
 3        Emp e = (Emp)s.get(Emp.class, 50);
 4
 5        System.out.println(e);
 6
 7        Emp e2=(Emp)s.load(Emp.class, 50);
 8
 9        System.out.println(e2);
10
11        s.close();

对于id为50的数据,数据库中是没有的,因此,可以查看结果:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 null
22
23 Hibernate:
24
25     select
26
27         emp0_.id as id1_0_,
28
29         emp0_.dept_id as dept2_1_0_,
30
31         emp0_.emp_name as emp3_1_0_,
32
33         emp0_.emp_sal as emp4_1_0_
34
35     from
36
37         t_emp emp0_
38
39     where
40
41         emp0_.id=?
42
43 Exception in thread "main" org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [org.ninth.entity.Emp#50]

发现,使用get方法返回的确实是null,而load则抛出一个异常。由此也可以发现:

load的对象永远不为空,因为会返回一个被继承的代理对象。故如果

Object obj=s.load(obj.class,id);

if(obj!=null){}

if块中的语句是永远不会被执行的。

这就是延迟加载(懒加载)

总体来说:load效率更高。因此确定有某一个对象时,用load()更快。

如果load()一个对象,返回一个代理,再get()一下,则返回了代理,再使用,则去数据库中查找,没找到,抛异常。

因此,在这种情况下时get()也会抛出异常来。但一般是不会这么做的,除了测试。

当然,这些都是针对get()与load()方法对当前对象的结果.同样在上面的例子中也可以看到,无论是get()还是load()出来的对象,其所关联而得到的对象都是代理对象,而不是真实对象.如果想要关联得到的对象也是真实对象呢?那么就可以如下配置映射文件:

1 <many-to-one name="dept" class="Dept" column="dept_id"
2
3            foreign-key="fk_emp_dept" lazy="false"/>

再一次用两种方法得到Dept对象:

get()

1 Session s = HbnUtil.getSession();
2
3        Emp e=(Emp)s.get(Emp.class, 1);
4
5        System.out.println(e.getClass());
6
7        System.out.println(e.getDept().getClass());
8
9        s.close();

得到的结果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         emp0_.dept_id as dept2_1_0_,
 8
 9         emp0_.emp_name as emp3_1_0_,
10
11         emp0_.emp_sal as emp4_1_0_
12
13     from
14
15         t_emp emp0_
16
17     where
18
19         emp0_.id=?
20
21 Hibernate:
22
23     select
24
25         dept0_.id as id0_0_,
26
27         dept0_.dept_name as dept2_0_0_,
28
29         dept0_.dept_loc as dept3_0_0_
30
31     from
32
33         t_dept dept0_
34
35     where
36
37         dept0_.id=?
38
39 class org.ninth.entity.Emp
40
41 class org.ninth.entity.Dept

可以发现,程序最终得到的结果是两个对象都为真实对象,而非代理。并且是在s.get(Emp.class,1);时就一次性产生两条SQL语句得到了数据,这样才保证了后面的Dept不为代理对象。再看使用load()方法的效果:

1 Session s = HbnUtil.getSession();
2
3        Emp e=(Emp)s.load(Emp.class, 1);
4
5        System.out.println(e.getClass());
6
7        System.out.println(e.getDept().getClass());
8
9        s.close();

程序只是在原先的基础上将get()方法替换成了load()方法,结果如下:

 1 class org.ninth.entity.Emp$$EnhancerByCGLIB$$7ea598a0
 2
 3 Hibernate:
 4
 5     select
 6
 7         emp0_.id as id1_0_,
 8
 9         emp0_.dept_id as dept2_1_0_,
10
11         emp0_.emp_name as emp3_1_0_,
12
13         emp0_.emp_sal as emp4_1_0_
14
15     from
16
17         t_emp emp0_
18
19     where
20
21         emp0_.id=?
22
23 Hibernate:
24
25     select
26
27         dept0_.id as id0_0_,
28
29         dept0_.dept_name as dept2_0_0_,
30
31         dept0_.dept_loc as dept3_0_0_
32
33     from
34
35         t_dept dept0_
36
37     where
38
39         dept0_.id=?
40
41 class org.ninth.entity.Dept

这里可以发现差异,即在load()时并没有真的去访问数据库,而仅仅是返回了一个Emp的代理。而当在使用了Emp类中的方法getDept()时,才真正地去访问数据库。然后得到了Dept的真实体对象。那么,就可以知道:

get()或者load()时,对直接用方法获得的对象,通过配置映射文件是不会有任何影响的,就是说,配置lazy=”false”前后。他们(Emp)返回的都是相同的结果,而对于Emp所关联的对象则有区别,配置前,关联对象都是代理的,配置后,关联对象都是实体的。

但是此处仍有问题,那就是产生了SQL语句,其实如果只需要得到某人的部门号,若用SQL语句,只需要做一个表连接即可,如果产生两条SQL,那么从性能上来说是多余的。自然,强大的Hibernate能够实现这一点。只要将映射文件修改成如下:

1 many-to-one name="dept" class="Dept" column="dept_id"
2
3            foreign-key="fk_emp_dept" fetch="join"/>

修改完成后,同样可以做测试来验证,程序不变仍用上面的load()方法的一段程序,结果如下:

 1 class org.ninth.entity.Emp$$EnhancerByCGLIB$$7ea598a0
 2
 3 Hibernate:
 4
 5     select
 6
 7         emp0_.id as id1_1_,
 8
 9         emp0_.dept_id as dept2_1_1_,
10
11         emp0_.emp_name as emp3_1_1_,
12
13         emp0_.emp_sal as emp4_1_1_,
14
15         dept1_.id as id0_0_,
16
17         dept1_.dept_name as dept2_0_0_,
18
19         dept1_.dept_loc as dept3_0_0_
20
21     from
22
23         t_emp emp0_
24
25     left outer join
26
27         t_dept dept1_
28
29             on emp0_.dept_id=dept1_.id
30
31     where
32
33         emp0_.id=?
34
35 class org.ninth.entity.Dept

可以发现,程序得到了相同的结果,但是却只对数据库访问了一次,这样,在数据量大的情况下,效率的提升是很明显的。同时此处也应该注意到,映射文件中并没有再对lazy=”false/true”进行任何设置。如果使用get()方法,也将得到上面的get()方法相同的结果,但查询也是变成一次。

fetch=”join”,此属性默认为select。因此通过上述各个实例,可以得到结果:

在使用get()或者load()方法时,映射文件中应该配置为fetch=”join”,此处默认为left outer join (查看生成的SQL可知),而不再需要设置lazy=”false”这一选项。这样就能够保证高效的Hibernate的运行。但是这仅仅是针对get()与load()方法而言的。如果是HQL语句呢?又会是什么效果?

保持上面的映射文件不变,执行如下代码:

 1 Session s = HbnUtil.getSession();
 2
 3        List<Emp> emps=s.createQuery("from Emp").list();
 4
 5        System.out.println(emps.size());
 6
 7        System.out.println(emps.get(1).getName());
 8
 9        System.out.println(emps.get(1).getDept().getClass());
10
11        s.close();

得到的结果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_,
 6
 7         emp0_.dept_id as dept2_1_,
 8
 9         emp0_.emp_name as emp3_1_,
10
11         emp0_.emp_sal as emp4_1_
12
13     from
14
15         t_emp emp0_
16
17 11
18
19 李四
20
21 class org.ninth.entity.Dept$$EnhancerByCGLIB$$b1605103

可以发现,取出的员工没有问题,是一个实体对象,而对于Dept对象,他仍旧是一个代理。是否在此处应用时仍旧需要配置映射文件的lazy属性呢?配置后,再执行上述代码,结果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_,
 6
 7         emp0_.dept_id as dept2_1_,
 8
 9         emp0_.emp_name as emp3_1_,
10
11         emp0_.emp_sal as emp4_1_
12
13     from
14
15         t_emp emp0_
16
17 Hibernate:
18
19     select
20
21         dept0_.id as id0_0_,
22
23         dept0_.dept_name as dept2_0_0_,
24
25         dept0_.dept_loc as dept3_0_0_
26
27     from
28
29         t_dept dept0_
30
31     where
32
33         dept0_.id=?
34
35 Hibernate:
36
37     select
38
39         dept0_.id as id0_0_,
40
41         dept0_.dept_name as dept2_0_0_,
42
43         dept0_.dept_loc as dept3_0_0_
44
45     from
46
47         t_dept dept0_
48
49     where
50
51         dept0_.id=?
52
53 Hibernate:
54
55     select
56
57         dept0_.id as id0_0_,
58
59         dept0_.dept_name as dept2_0_0_,
60
61         dept0_.dept_loc as dept3_0_0_
62
63     from
64
65         t_dept dept0_
66
67     where
68
69         dept0_.id=?
70
71 Hibernate:
72
73     select
74
75         dept0_.id as id0_0_,
76
77         dept0_.dept_name as dept2_0_0_,
78
79         dept0_.dept_loc as dept3_0_0_
80
81     from
82
83         t_dept dept0_
84
85     where
86
87         dept0_.id=?
88
89 11
90
91 李四
92
93 class org.ninth.entity.Dept

程序结果果然得到了正确的,实体Dept,但是此处的代码显示我只是需要下标为2所对应的员工部门,但hibernate却将所有的部门全部查询了出来,明显这又是一个很大的浪费。那么也应该想到,这样一改映射文件,肯定是不合理的做法,此处是只有四个部门信息,但是如果是有40个部门信息呢?那么将会多出39条无用的查询Dept语句。当算法慢到一定程度的时候,就是错误的算法。这里,就是一个错误的选择。那么可以从另外一方面来考虑,上面设置了fetch属性后,一次性查询出了所需要的实体信息,此处的信息fetch=”join”已经明显地呈现了,没有效果,但是否也可以通过修改HQL语句呢?试着删除lazy=”false”,并将HQL语句修改如下:

 1 Session s = HbnUtil.getSession();
 2
 3        List<Emp> emps=s.createQuery("from Emp e left outer join fetch e.dept").list();
 4
 5        System.out.println(emps.size());
 6
 7        System.out.println(emps.get(1).getName());
 8
 9        System.out.println(emps.get(1).getDept().getClass());
10
11        s.close();

再运行程序,得到结果如下:

 1 Hibernate:
 2
 3     select
 4
 5         emp0_.id as id1_0_,
 6
 7         dept1_.id as id0_1_,
 8
 9         emp0_.dept_id as dept2_1_0_,
10
11         emp0_.emp_name as emp3_1_0_,
12
13         emp0_.emp_sal as emp4_1_0_,
14
15         dept1_.dept_name as dept2_0_1_,
16
17         dept1_.dept_loc as dept3_0_1_
18
19     from
20
21         t_emp emp0_
22
23     left outer join
24
25         t_dept dept1_
26
27             on emp0_.dept_id=dept1_.id
28
29 11
30
31 李四
32
33 class org.ninth.entity.Dept

程序运行良好,且只有一条查询语句,查询到的也是Emp所对应的Dept信息,没有冗余。

1 from Emp e left outer join fetch e.dept

此处e表示为为类Emp取的别名, left outer join fetch即为左外连接,用左外连接的原因是需要在某种情况下的员工也能被查询出来,这种情况就是员工没有部门的时候.

所以,综合上述所有的查询来看,可以得到如下结论:

在多对一的情况时,在映射文件中,应该不设置lazy而只设置fetch="join".且默认为left outer join.这样在get()或者load()某个对象时,就可以高效的查出多方与一方的信息;并且在使用HQL语句时,也应该在查询条件中加上left outer join fetch,从而保证hibernate高效地工作。

原文地址:https://www.cnblogs.com/yehhuang/p/9465143.html

时间: 2024-10-16 01:05:10

Hibernate中的多对一映射的相关文章

Hibernate中的多对多映射

1.需求 项目与开发员工 一个项目,有多个开发人员 一个开发人员,参与多个项目 [多对多] 2.实体bean设计 Project: public class Project { private int prj_id; private String prj_name; private Set<Developer> devs = new HashSet<Developer>(); set... get... } Developer: public class Developer { p

【Hibernate步步为营】--多对多映射具体解释

上篇文章具体讨论了一对多映射,在一对多映射中单向的关联映射会有非常多问题,所以不建议使用假设非要採用一对多的映射的话能够考虑使用双向关联来优化之间的关系,一对多的映射事实上质上是在一的一端使用<many-to-one>标签来标明它们之间的关系,另外还须要在一的一端的对象中使用set标明集合映射. 一.单向多对多 仍然依照前几篇的文章格式来讨论.首先来看对象之间的关系,单向的多对多关系是两个对象之间发生的,比方在人和职位之间,一个人能够有多个职位,并且一个职位也能够由多人来负责,所以它们之间就形

【Hibernate步步为营】--多对多映射详解

上篇文章详细讨论了一对多映射,在一对多映射中单向的关联映射会有很多问题,所以不建议使用如果非要采用一对多的映射的话可以考虑使用双向关联来优化之间的关系,一对多的映射其实质上是在一的一端使用<many-to-one>标签来标明它们之间的关系,另外还需要在一的一端的对象中使用set标明集合映射. 一.单向多对多 仍然按照前几篇的文章格式来讨论,首先来看对象之间的关系,单向的多对多关系是两个对象之间发生的,比如在人和职位之间,一个人可以有多个职位,而且一个职位也可以由多人来负责,所以它们之间就形成了

hibernate(四) 双向多对多映射关系

序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感觉还是比较容易的,需要理清楚其数据库关系图,那么你就拿下了它.映射文件的配置还是那么些死东西. --WH 一.小疑问的解答 问题一:到这里,有很多学习者会感到困惑,因为他不知道使用hibernate是不是需要自己去创建表,还是hibernate全自动,如果需要自己创建表,那么主外键这种设置也是自己设

hibernate中基于主键映射1-1关联关系和基于外键映射1-1关联关系的不同

基于主键映射1-1关联关系和基于外键映射1-1关联关系的不同,主要区别是在配置映射文件上会有区别 两个持久化类为Manager和Department 1:基于主键映射1-1关联关系 1)使用其他持久化类的主键生成主键的实体的映射文件 首先需要指定主键生成方式为foreigner 格式为: <id name="departmentId" type="java.lang.Integer"> <column name="department_i

--------------Hibernate学习(四) 多对一映射 和 一对多映射

现实中有很多场景需要用到多对一或者一对多,比如上面这两个类图所展现出来的,一般情况下,一个部门会有多名员工,一名员工只在一个部门任职. 多对一关联映射 在上面的场景中,对于Employee来说,它跟Department的关系就是多对一. 先写实体类 Employee.java package entity; public class Employee { public int id; public String name; public Department department; public

Hibernate中的多对多关系详解(3)?

前面两节我们讲到了一对一的关系,一对多,多对一的关系,相对来说,是比较简单的,但有时,我们也会遇到多对多的关系,比如说:角色与权限的关系,就是典型的多对多的关系,因此,我有必要对这种关系详解,以便大家一起学习.下面来看例子: 首先我们必须建立二者的vo: public class Role implements Serializable {//这是role对象 private Integer rid; private String rdesc; private String rname; pri

Hibernate 中 联合主键映射 组合关系映射 大对象映射(或者说文本大对象,二进制数据大对象)

Clob:文本大对象,最长4G Blob:二进制数据大对象,最长4G util: public class HibUtil { private static SessionFactory sessionFactory; static{ //获取配置信息 hibernate.cfg.xml Configuration configuration = new Configuration().configure(); //创建一个 ServiceRegistry的实例 //首先获得其标准建造器,此处用

Hibernate中双向多对多的两种配置方式

1.建立多对多双向关联关系 1 package cn.happy.entitys; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 public class Employee { 7 private Integer empid; 8 private String empname; 9 private Set<ProEmp> emps = new HashSet<ProEmp>(); 10 11 public Set