【Hibernate】总结(二)(转)
Hibernate是目前最流行的开源对象关系映射(ORM)框架。Hibernate采用低侵入式的设计,也即完全采用普通的Java对象(POJO),而不必继承Hibernate的某个基类,或实现Hibernate的某个接口。Hibernate是面向对象的程序设计语言和关系数据库之间的桥梁,Hibernate允许程序开发者采用面向对象的方式来操作关系数据库。
因为我们知道hibernate它能支持透明持久化从这个角度来看它没有侵入性 所谓侵入性它没有侵入hibernate任何的API所以它叫轻量级框架,轻量级框架的好处是没有侵入性 另外的一个好处是为测试带来了好处,测试非常简单 测试就行我们写普通的java应用程序一样不需要什么环境只需要几个jar包就可以了写个main函数一侧就可以了 它没有侵入性和测试非常简单 这是它流行的一个原因。
hibernate的优缺点
优点:1、程序更加面向对象;
2、提高了生产率;
3、方便移植(修改配置文件);
4、无侵入性。
缺点:
1、效率比JDBC略差;
2、不适合批量操作。
3、只能配置一种关联关系
Hibernate有四种查询方案
1、get,load方法,根据id查找对象
2、HQL--hibernate query language(查询对象:Query)
3、Criteria--标准查询语言(查询对象:Criteria,查询条件:Criterion)
4、通过sql来查(查询对象:SQLQuery)
具体
Query/Criteria:
1.Query接口封装了Hibernate强大的对象查询能力,同时也支持数据库的更新操作
2.提供了动态查询的参数绑定功能
3.提供list(),iterator(),scroll()等对象导航方法
4.提供uiqueResult()方法获取单独的对象
5.提供executeUpdate()方法来执行DML语句
6.提供了可移植的分页查询方法
Session:
1、save,presist保存数据,persist在事务外不会产生insert语句。
2、delete,删除对象
3、update,更新对象,如果数据库中没有记录,会出现异常,脱管对象如属性改变需要保存,则调用update方法
4、get,根据ID查,会立刻访问数据库
5、load,根据ID查,(返回的是代理,不会立即访问数据库)
6、saveOrUpdate,merge(根据ID和version的值来确定是否是save或update),调用merge你的对象还是托管的。当不知该对象的状态时可调用该方法
小贴士:瞬时对象id无值,脱管对象有值,hibernate通过该方式判断对象的状态
7、lock(把对象变成持久对象,但不会同步对象的状态)
hibernate的工作原理
1.配置好hibernate的配置文件和与类对应的配置文件后,启动服务器
2.服务器通过实例化Configuration对象,读取hibernate.cfg.xml文件的配置内容,并根据相关的需求建好表或者和表建立好映射关系
3.通过实例化的Configuration对象就可以建立sessionFactory实例,进一步,通过sessionFactory实例可以创建 session对象
4.得到session之后,便可以对数据库进行增删改查操作了,除了比较复杂的全文搜索外,简单的操作都可以通过hibernate封装好的 session内置方法来实现
5.此外,还可以通过事物管理,表的关联来实现较为复杂的数据库设计
优点:hibernate相当于java类和数据库表之间沟通的桥梁,通过这座桥我们就可以做很多事情了
Hibernate 的初始化.
1)创建Configeration类的实例。
它的构造方法:将配置信息(Hibernate config.xml)读入到内存。
一个Configeration 实例代表Hibernate 所有Java类到Sql数据库映射的集合。
2)创建SessionFactory实例
把Configeration 对象中的所有配置信息拷贝到SessionFactory的缓存中。
SessionFactory的实例代表一个数据库存储员源,创建后不再与Configeration 对象关联。
3)调用SessionFactory创建Session的方法
1】用户自行提供JDBC连接。
Connection con=dataSource.getConnection();
Session s=sessionFactory.openSession(con);
2】让SessionFactory提供连接
Session s=sessionFactory.openSession();
状态转换
临时状态 (transient)
1】不处于Session 缓存中
2】数据库中没有对象记录
Java如何进入临时状态
1】通过new语句刚创建一个对象时
2】当调用Session 的delete()方法,从Session 缓存中删除一个对象时。
持久化状态(persisted)
1】处于Session 缓存中
2】持久化对象数据库中有对象记录
3】Session 在特定时刻会保持二者同步
Java如何进入持久化状态
1】Session 的save()把临时-》持久化状态
2】Session 的load(),get()方法返回的对象
3】Session 的find()返回的list集合中存放的对象
4】Session 的update(),saveOrupdate()使游离-》持久化
游离状态(detached)
1】不再位于Session 缓存中
2】游离对象由持久化状态转变而来,数据库中可能还有对应记录。
Java如何进入持久化状态-》游离状态
1】Session 的close()方法
2】Session 的evict()方法,从缓存中删除一个对象。提高性能。少用。
具体如图:
测试工具Juit。
测试类需要继承TestCase,编写单元测试方法,方法名称必须为test开头,方法没有参数没有返回值,采用public修饰。其中在测试中,查询对象时,使用get或者load两种方法进行加载,这种方法的区别:get不支持延迟加载,而load默认情况下是支持延迟加载。并且get查询对象不存在时,返回null;而load查询对象不存在时,则抛出ObjectNotFoundException异常。
悲观锁和乐观锁解释。
悲观锁为了解决并发性,跟操作系统中的进程中添加锁的概念一样。就是在整个过程中在事务提交之前或回滚之前,其他的进程是无法访问这个资源的。悲观锁的实现方式有两种:一种使用数据库中的独占锁;另一种是在数据库添加一个锁的字段。hibernate中声明锁如下:
Account account = (Account)session.get(Account.class, 1, LockMode.UPGRADE);而net.sf.hibernate.LockMode类表示锁模式,当取值LockMode.UPGRADE时,则表示使用悲观锁for update;而乐观锁是为了解决版本冲突的问题。就是在数据库中添加version字段,每次更新时,则把自己的version与数据库中的version进行比较,若是版本相比较低,则不允许进行修改更新。
H ibernate中的缓存机制。
缓存是什么呢?缓存是应用程序和数据库之间的内存的一片区域。主要的目的是:为了减少对数据库读取的时间。当查询数据时,首先在缓存中查询,若存在,则直接取出,若不存在,然后再向数据库中查询。所以应该把经常访问数据库的数据放到缓存中,至于缓存中的数据如何不断的置换,这也需要涉及一种淘汰数据的算法。
谈到这个hibernate中的缓存,你想到了什么呢?刚才叙述缓存时,是否感觉很熟悉,感觉从哪也听过似的。嗯呢,是呢,是很熟悉,写着写着就很熟悉,这个刚才的缓存以及缓存的置换算法就和计算机组成中的cache类似。
好吧,来回到我们hibernate中的缓存。
hibernate中的缓存可以分为两种:一级缓存,也称session缓存;二级缓存,是由sessionFactory管理。
那一级缓存和二级缓存有什么区别呢?区别的关键关于:缓存的生命周期,也就是缓存的范围不同。
那首先介绍一下缓存的生命周期,也就是缓存的范围。
1.事务缓存,每个事务都有自己的缓存,当事务结束,则缓存的生命周期同样结束,正如上篇博客中我们提到,对数据库的操作,增删改查都是放到事务中的,和事务保持同步,若是事务提交完毕,一般是不允许是再次对数据库进行操作。所以session是属于事务缓存的。
2.应用缓存,一个应用程序中的缓存,也就是应用程序中的所有事务的缓存。只有当应用程序结束时,此时的缓存的额生命周期结束。二级缓存就是应用缓存。
3.集群缓存,被一台机器或多台机器的进程共享。
这下明白了一级缓存和二级缓存的区别了吧。那一级缓存和二级缓存的共同点是:都是缓存实体属性,
二级缓存一般情况都是由第三方插件实现的。第三方插件如:
EHCache,JbossCache(是由Jboss开源组织提供的),osCache(open symphony),swarmCache。前三种对hibernate中的查询缓存是支持的,后一种是不支持hibernate查询缓存。
那什么是hibernate查询缓存呢?
查询缓存是用来缓存普通属性的,对于实体对象而言,是缓存实体对象的id。
主要有session缓存(一级缓存)和sessionfactory缓存(内置缓存和外置缓存(也叫二级缓存))
Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存(Session的一些集合属性包含的数据)。在第一级缓存中,持久化类的每个实例都具有唯一的OID。
SessionFactory的缓存又可以分为两类:
内置缓存(存放SessionFactory对象的一些集合属性包含的数据,SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。)
外置缓存。SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型、读写型、非严格读写型、只读型
什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据
2 不是很重要的数据,允许出现偶尔并发的数据
3 不会被并发访问的数据
4 参考数据
不适合存放到第二级缓存的数据?
1 经常被修改的数据
2 财务数据,绝对不允许出现并发
3 与其他应用共享的数据。
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
注意几个关键字的含义
inverse="true"表示此表不维护表之间的关系,由另外的表维护。
inverse属性默认是false的,就是说关系的两端都来维护关系。
false代表由己方来维护关系,true代表由对方来维护关系。在一个关系中,只能由一方来维护关系,否则会出问题(解疑中会讲到);同时也必须由一方来维护关系,否则会出现双方互相推卸责任,谁也不管。
可以这样理解,cascade定义的是关系两端对象到对象的级联关系;而inverse定义的是关系和对象的级联关系。
Cascade属性的可能值有
all: 所有情况下均进行关联操作,即save-update和delete。
none: 所有情况下均不进行关联操作。这是默认值。
save-update: 在执行save/update/saveOrUpdate时进行关联操作。
delete: 在执行delete 时进行关联操作。
all-delete-orphan: 当一个节点在对象图中成为孤儿节点时,删除该节点。比如在一个一对多的关系中,Student包含多个book,当在对象关系中删除一个book时,此book即成为孤儿节点。
hql查询。
Hibernate query language。hql查询中关键字不区分大小写,但是类和属性都是区分大小写的。
1.简单属性查询。
单一属性查询,返回属性结果集列表,元素类型和实体类的相应的类型一致。
- List students = session.createQuery("select name from Student").list();
- for (Iterator iter=students.iterator(); iter.hasNext();) {
- String name = (String)iter.next();
- System.out.println(name);
- }
//返回结果集属性列表,元素类型和实体类中的属性类型一致
多个属性查询,多个属性查询返回数组对象,对象数组的长度取决于属性的个数,对象数组中的元素类型与实体类中属性一致。
- List students = session.createQuery("select id, name from Student").list();
- for (Iterator iter=students.iterator(); iter.hasNext();) {
- Object[] obj = (Object[])iter.next();
- System.out.println(obj[0] + ", " + obj[1]);
- }
2.实体对象查询
List students = session.createQuery("from Student").list();
当然这种hql语句,可以使用别名,as可以省去,如:from Student as s,若是使用select关键字,则必须使用别名。如:select s from Student as s.但是不支持select * from Student格式。
查询中使用list和Iterate区别:
list查询是直接运行查询的结果,所以只有一句sql语句。而iterate方法则有可能会产生N+1条sql语句。这是怎么回事呢?要理解N+1条语句,首先得弄明白iterate是如何执行查询的?
首先发出一条查询对象ID的语句,然后根据对象的ID到缓存(缓存的概念上篇博客已经提到)中查找,若是存在查询出此对象的其他的属性,否则会发出N条语句,此时的N语句,是刚才第一次查询的记录条数。这种现象就是N+1sql语句。
其中list是默认情况下都发出sql语句,查询出的结果会放到缓存中,但是它不会利用缓存,即使放进去,下次执行时,仍然继续发出sql语句。
而:iterate默认情况下会利用缓存,若是缓存中有则不会发出N+1条语句。
3.条件查询。
这种方式就是传入参数,使用参数占位符“?”。也可以使用“:参数名”
java代码如下:
- List students = session.createQuery("select s.id, s.name from Student s where s.name like ?")
- .setParameter(0, "%0%")
- .list();
- List students = session.createQuery("select s.id, s.name from Student s where s.name like :myname")
- .setParameter("myname", "%0%")
- .list();
4.使用原生sql语句。
和咱们原先写入的sql语句一样。在此不介绍了。
5.外置命名查询。
这个听起来有点晦涩,怎么理解呢?其实通俗的说就是把hql语句写在外面,写在映射文件中。使用标签:
- <query name="queryStudent">
- <![CDATA[
- select s from Student s where s.id <?
- ]]>
- </query>
那在程序中是如何使用此标签的呢?使用session.getNameQuery(),并进行赋值,代码如下:
- List students = session.getNamedQuery("queryStudent")
- .setParameter(0, 10)
- .list();
查询过滤器。
这个是什么意思呢?过滤器大家很熟悉吧,不熟悉的可以参考我的以前博客<>.原来我们接触过编码过滤器,编码过滤器就是为了避免当时每个页面需要设置编码格式而提出的。这个查询过滤器其实也是这个意思。若是代码都需要某一句sql语句的话,可以考虑使用它。这样可以避免每次都写查询语句。
使用如下:首先在映射文件中配置标签:
- <filter-def name="testFilter">
- <filter-param type="integer" name="myid"/>
- </filter-def>
然后程序中如下使用并进行赋值:
- session.enableFilter("testFilter")
- .setParameter("myid", 10);
7.分页查询。
分页查询,这个肯定不陌生,因为在做drp项目时,做的最多的是分页,当时使用oracle数据库,分页查询涉及到三层嵌套。直接传入的参数为:每页的大小(记录数),页号。
Hibernate中给我们已经封装好了,只要设置开始的页号以及每页的大小即可,不用亲自动手写嵌套的sql语句。
代码如下:
- List students = session.createQuery("from Student")
- .setFirstResult(1)
- .setMaxResults(2)
- .list();
8.对象导航查询。
这个什么意思呢?这个只要是用于一个类的属性是另一个类的引用。比如:student类中有一个classes属性。其中的classes也是一个类Class的引用。
当我们查询的时候可以这样使用:
- List students = session.createQuery("from Student s where s.classes.name like '%t%'")
- .list();
相当于:s.getClasses.getName(),直接使用get后面的属性,然后首字母小写。
这种语法,是不是很熟悉?想想我们在哪是不是也用过?想起来了吗?估计你猜出来啦,呵呵,是JSTL(jsp standard tag library)中。若是想进一步了解,可以参考我的博客哈,当时是转载滴貌似。
9.连接查询。
连接分为:内连接和外连接,其中外连接分为左连接,右连接,完全连接。这个跟数据库中的左右连接其实是一样的。我们通俗解释一下:
左连接:以左边为准,右边即使没有匹配的,也要把这条记录查询出来,此时没有匹配的右边以null填充。
右连接:以右边为准,左边即使没有匹配的,也要把这条记录查询出来,此时没有匹配的左边以null填充。
完全连接:只要一方存在即可。
内连接:必须两方都存在才可以查询提取此记录。
10.统计查询。
其实就是查询count的记录数。其中查询出来的额count是long类型。
11.DML风格的操作。
DML?其实DML=Data Manipulate Language(数据操作语言),举个例子:
- session.createQuery("update Student s set s.name=? where s.id<?")
- .setParameter(0, "王斌")
- .setParameter(1, 2)
- .executeUpdate();
假若原来的名字是:李四,更新完数据库后变成王斌,若是我们此时取出数据,其姓名是李四还是王斌?按照道理应该是王斌,但是结果确实李四,若不信,可以自己去实践一下。
这个原因,是因为更新了数据库,但是缓存中没有更新,才会造成这种数据库和缓存不同步的问题。
所以,我们应该尽量不使用这种形式。扬其长避其短嘛。