举最常见的员工与部门来举例子:
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