Hibernate课堂笔记.docx
《Hibernate课堂笔记.docx》由会员分享,可在线阅读,更多相关《Hibernate课堂笔记.docx(22页珍藏版)》请在冰点文库上搜索。
Hibernate课堂笔记
Hibernate课堂笔记
一、Hibernate环境搭建
1)Hibernate3最后一个版本Hibernate3.6.10的环境搭建
1、导入项目所需要的包
hibernate3.jar,lib/required下的所有包和lib/jpa下的包
2、编写配置文件和映射文件
参考documentation\manual\zh-CN\html_single
1.1.4.Hibernate配置
1.1.3.映射文件
3、创建SessionFactory和Session
publicclassHibernateUtil{
privatestaticSessionFactorysessionFactory=null;
static{
sessionFactory=newConfiguration().configure().buildSessionFactory();
}
publicSessiongetSession(){
returnsessionFactory.openSession();
}
}
2)截至目前为止,Hibernate4最新版本Hibernate4.3.8的环境搭建
1、导入项目所需要的包
lib/required下的所有包
2、编写配置文件和映射文件
参考documentation\manual\en-US\html_single
1.1.4.Hibernateconfiguration
1.1.3.Themappingfile
3、创建SessionFactory和Session
publicclassHibernateUtil{
privatestaticSessionFactorysessionFactory=null;
static{
Configurationcfg=newConfiguration().configure();
ServiceRegistrysr=newStandardServiceRegistryBuilder().applySettings(cfg.getProperties()).build();
sessionFactory=cfg.buildSessionFactory(serviceRegistry);
}
publicSessiongetSession(){
returnsessionFactory.openSession();
}
}
二、Hibernate对象三种状态
Hibernate中的对象有三种状态:
瞬时状态(Transient),持久状态(Persistent),脱管状态(Detached)
1.瞬时状态(Transient)
由new命令开辟内存空间的Java对象,也就是平时所熟悉的普通Java对象。
如:
Studentstu=newStudent();
瞬时对象特点:
(1)不和Session实例关联
(2)在数据库中没有和瞬时对象关联的记录
2.持久状态(Persistent)
持久的实例在数据库中有对应的记录,并拥有一个持久化标识(identifier).
持久对象总是与Session和Transaction相关联,在一个Session中,对持久对象的改变不会马上对数据库进行变更,
而必须在Transaction终止,也就是执行commit()之后,才在数据库中真正运行SQL进行变更,持久对象的状态才会与数据库进行同步。
在同步之前的持久对象称为脏(dirty)对象。
瞬时对象转为持久对象:
(1)通过Session的save()和saveOrUpdate()方法把一个瞬时对象与数据库相关联,这个瞬时对象就成为持久化对象。
(2)使用get(),load()待方法查询到的数据对象,将成为持久化对象。
持久化对象的特点:
(1)和Session实例关联
(2)在数据库中有和持久对象关联的记录
3.脱管状态(Detached)
与持久对象关联的Session被关闭后,对象就变为脱管对象。
对脱管对象的引用依然有效,对象可继续被修改。
脱管对象特点:
(1)本质上和瞬时对象相同
(2)只是比瞬时对象多了一个数据库记录标识值id.
持久对象转为脱管对象:
当执行close()或clear(),evict()之后,持久对象会变为脱管对象。
瞬时对象转为持久对象:
通过Session的update(),saveOrUpdate()和lock()等方法,把脱管对象变为持久对象。
4.结合save(),update(),saveOrUpdate()方法说明对象的状态
(1)Save()方法将瞬时对象保存到数据库,对象的临时状态将变为持久化状态。
当对象在持久化状态时,它一直位于Session的缓存中,
对它的任何操作在事务提交时都将同步到数据库,因此,对一个已经持久的对象调用save()或update()方法是没有意义的。
如:
Studentstu=newStrudnet();
stu.setCarId(“200234567”);
stu.setId(“100”);
//打开Session,开启事务
session.save(stu);
stu.setCardId(“20076548”);
session.save(stu);//无效
session.update(stu);//无效
//提交事务,关闭Session
(2)update()方法两种用途重新关联脱管对象为持久化状态对象,显示调用update()以更新对象。
调用update()只为了关联一个脱管对象到持久状态,
当对象已经是持久状态时,调用update()就没有多大意义了。
如:
//打开session,开启事务
stu=(Student)session.get(Student.class,”123456”);
stu.setName(“Body”);
session.update(stu);//由于stu是持久对象,必然位于Session缓冲中,
对stu所做的变更将//被同步到数据库中。
所以update()是没有意义的,可以不要这句效果一样的。
//提交事务,关闭Session
Hibernate总是执行update语句,不管这个脱管对象在离开Session之后有没有更改过,在清理缓存时Hibernate总是发送一条update语句,
以确保脱管对象和数据库记录的数据一致,如:
Studentstu=newStrudnet();
stu.setCarId(“1234”);
//打开Session1,开启事务
session1.save(stu);
//提交事务,关闭Session1
stu.set(“4567”);//对脱管对象进行更改
//打开Session2,开启事务
session2.update(stu);
//提交事务,关闭Session2
如果希望只有脱管对象改变了,Hibernate才生成update语句,可以把映射文件中标签的select-before-update设为true,
这种会先发送一条select语句取得数据库中的值,判断值是否相同,如果相同就不执行update语句。
不过这种做法有一定的缺点,
每次update语句之前总是要发送一条多余的select语句,影响性能。
对于偶尔更改的类,设置才是有效的,对于经常要更改的类这样做是影响效率的。
(3)saveOrUpdate()方法兼具save()和update()方法的功能,对于传入的对象,saveOrUpdate()首先判断其是脱管对象还是临时对象,然后调用合适的方法。
三、load和get的区别
hibernate延迟加载(get和load的区别)
1.load加载方式
当使用load方法来得到一个对象时,此时hibernate会使用延迟加载的机制来加载这个对象,
即:
当我们使用session.load()方法来加载一个对象时,此时并不会发出sql语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,
只有当我们要使用这个对象,得到其它属性时,这个时候才会发出sql语句,从数据库中去查询我们的对象。
session=HibernateUtil.openSession();
/*
*通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
*/
Useruser=(User)session.load(User.class,2);
我们看到,如果我们仅仅是通过load来加载我们的User对象,此时从控制台我们会发现并不会从数据库中查询出该对象,即并不会发出sql语句,但如果我们要使用该对象时:
session=HibernateUtil.openSession();
Useruser=(User)session.load(User.class,2);
System.out.println(user);
此时我们看到控制台会发出了sql查询语句,会将该对象从数据库中查询出来
这个时候我们可能会想,那么既然调用load方法时,并不会发出sql语句去从数据库中查出该对象,那么这个User对象到底是个什么对象呢?
其实这个User对象是我们的一个代理对象,这个代理对象仅仅保存了id这个属性:
session=HibernateUtil.openSession();
/*
*通过load的方式加载对象时,会使用延迟加载机制,此时得到的User对象其实是一个
*代理对象,该代理对象里面仅仅只有id这个属性
*/
Useruser=(User)session.load(User.class,2);
System.out.println(user.getId());
我们看到,如果我们只打印出这个user对象的id值时,此时控制台会打印出该id值,但是同样不会发出sql语句去从数据库中去查询。
这就印证了我们的这个user对象仅仅是一个保存了id的代理对象,但如果我需要打印出user对象的其他属性值时,这个时候会不会发出sql语句呢?
答案是肯定的:
session=HibernateUtil.openSession();
/*
*通过load的方式加载对象时,会使用延迟加载机制,此时得到的User对象其实是一个
*代理对象,该代理对象里面仅仅只有id这个属性
*/
Useruser=(User)session.load(User.class,2);
System.out.println(user.getId());
//如果此时要得到user其他属性,则会从数据库中查询
System.out.println(user.getUsername());
相信通过上述的几个例子,大家应该很好的了解了load的这种加载对象的方式了吧。
2、get加载方式
相对于load的延迟加载方式,get就直接的多,当我们使用session.get()方法来得到一个对象时,不管我们使不使用这个对象,
此时都会发出sql语句去从数据库中查询出来:
session=HibernateUtil.openSession();
/*
*通过get方法来加载对象时,不管使不使用该对象,都会发出sql语句,从数据库中查询
*/
Useruser=(User)session.get(User.class,2);
此时我们通过get方式来得到user对象,但是我们并没有使用它,但是我们发现控制台会输出sql的查询语句
因此我们可以看到,使用load的加载方式比get的加载方式性能要好一些,因为load加载时,得到的只是一个代理对象,当真正需要使用这个对象时再去从数据库中查询。
3、使用get和load时的一些小问题
当了解了load和get的加载机制以后,我们此时来看看这两种方式会出现的一些小问题:
①如果使用get方式来加载对象,当我们试图得到一个id不存在的对象时,此时会报NullPointException的异常
session=HibernateUtil.openSession();
/*
*当通过get方式试图得到一个id不存在的user对象时,此时会报NullPointException异常
*/
Useruser=(User)session.get(User.class,20);
System.out.println(user.getUsername());
此时我们看控制台的输出信息,会报空指针的异常
这是因为通过get方式我们会去数据库中查询出该对象,但是这个id值不存在,所以此时user对象是null,所以就会报NullPointException的异常了。
②如果使用load方式来加载对象,当我们试图得到一个id不存在的对象时,此时会报ObjectNotFoundException异常:
复制代码
session=HibernateUtil.openSession();
/*
*当通过get方式试图得到一个id不存在的user对象时,此时会报ObjectNotFoundException异常
*/
Useruser=(User)session.load(User.class,20);
System.out.println(user.getId());
System.out.println(user.getUsername());
为什么使用load的方式和get的方式来得到一个不存在的对象报的异常不同呢?
?
其原因还是因为load的延迟加载机制,
使用load时,此时的user对象是一个代理对象,仅仅保存了当前的这个id值,当我们试图得到该对象的username属性时,
这个属性其实是不存在的,所以就会报出ObjectNotFoundException这个异常了。
③org.hibernate.LazyInitializationException异常
接下来我们再来看一个例子:
publicclassUserDAO
{
publicUserloadUser(intid)
{
Sessionsession=null;
Transactiontx=null;
Useruser=null;
try
{
session=HibernateUtil.openSession();
tx=session.beginTransaction();
user=(User)session.load(User.class,1);
mit();
}
catch(Exceptione)
{
e.printStackTrace();
tx.rollback();
}
finally
{
HibernateUtil.close(session);
}
returnuser;
}
}
@Test
publicvoidtestLazy06()
{
UserDAOuserDAO=newUserDAO();
Useruser=userDAO.loadUser
(2);
System.out.println(user);
}
模拟了一个UserDAO这样的对象,然后我们在测试用例里面来通过load加载一个对象,此时我们发现控制台会报LazyInitializationException异常
org.hibernate.LazyInitializationException:
couldnotinitializeproxy-noSession .............
这个异常是什么原因呢?
?
还是因为load的延迟加载机制,当我们通过load()方法来加载一个对象时,此时并没有发出sql语句去从数据库中查询出该对象,
当前这个对象仅仅是一个只有id的代理对象,我们还并没有使用该对象,但是此时我们的session已经关闭了,
所以当我们在测试用例中使用该对象时就会报LazyInitializationException这个异常了。
所以以后我们只要看到控制台报LazyInitializationException这种异常,就知道是使用了load的方式延迟加载一个对象了,
解决这个的方法有两种,一种是将load改成get的方式来得到该对象,另一种是在表示层来开启我们的session和关闭session。
总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,
如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
四、Hibernate主键生成策略
Hibernate各种主键生成策略与配置详解
1、assigned
主键由外部程序负责生成,在save()之前必须指定一个。
Hibernate不负责维护主键生成。
与Hibernate和底层数据库都无关,可以跨数据库。
在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免。
特点:
可以跨数据库,人为控制主键生成,应尽量避免。
2、increment
由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的数据库,因此可以跨数据库。
Hibernate调用org.hibernate.id.IncrementGenerator类里面的generate()方法,使用selectmax(idColumnName)fromtableName语句获取主键最大值。
该方法被声明成了synchronized,所以在一个独立的Java虚拟机内部是没有问题的,然而,在多个JVM同时并发访问数据库selectmax时就可能取出相同的值,
再insert就会发生Dumplicateentry的错误。
所以只能有一个Hibernate应用进程访问数据库,否则就可能产生主键冲突,
所以不适合多进程并发更新数据库,适合单一进程访问数据库,不能用于群集环境。
官方文档:
只有在没有其他进程往同一张表中插入数据时才能使用,在集群下不要使用。
特点:
跨数据库,不适合多进程并发更新数据库,适合单一进程访问数据库,不能用于群集环境。
3、hilo
hilo(高低位方式highlow)是hibernate中最常用的一种生成方式,需要一张额外的表保存hi的值。
保存hi值的表至少有一条记录(只与第一条记录有关),否则会出现错误。
可以跨数据库。
hibernate_hilo
next_hi
100
hibernate_hilo指定保存hi值的表名
next_hi指定保存hi值的列名
100指定低位的最大值
也可以省略table和column配置,其默认的表为hibernate_unique_key,列为next_hi
100
hilo生成器生成主键的过程(以hibernate_unique_key表,next_hi列为例):
1.获得hi值:
读取并记录数据库的hibernate_unique_key表中next_hi字段的值,数据库中此字段值加1保存。
2.获得lo值:
从0到max_lo循环取值,差值为1,当值为max_lo值时,重新获取hi值,然后lo值继续从0到max_lo循环。
3.根据公式hi*(max_lo+1)+lo计算生成主键值。
注意:
当hi值是0的时候,那么第一个值不是0*(max_lo+1)+0=0,而是lo跳过0从1开始,直接是1、2、3……
那max_lo配置多大合适呢?
这要根据具体情况而定,如果系统一般不重启,而且需要用此表建立大量的主键,可以吧max_lo配置大一点,这样可以减少读取数据表的次数,提高效率;
反之,如果服务器经常重启,可以吧max_lo配置小一点,可以避免每次重启主键之间的间隔太大,造成主键值主键不连贯。
特点:
跨数据库,hilo算法生成的标志只能在一个数据库中保证唯一。
4、seqhilo
与hilo类似,通过hi/lo算法实现的主键生成机制,只是将hilo中的数据表换成了序列sequence,需要数据库中先创建sequence,适用于支持sequence的数据库,如Oracle。
hibernate_seq
100
特点:
与hilo类似,只能在支持序列的数据库中使用。
5、sequence
采用数据库提供的sequence机制生成主键,需要数据库支持sequence。
如oralce、DB、SAPDB、PostgerSQL、McKoi中的sequence。
MySQL这种不支持sequence的数据库则不行(可以使用identity)。
hibe