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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值