Hibernate工作原理及为什么要用?
一 原理:
1.读取并解析配置文件2.读取并解析映射信息,创建SessionFactory3.打开Sesssion4.创建事务Transaction 5.持久化操作
6.提交事务7.关闭Session。8.关闭SessionFactory
为什么要用:
1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
二 Hibernate 的核心接口及其作用
1 Configuration类:配置Hibernate启动Hibernate创建SessionFactory对象
2 SessionFactory:初始化Hibernate创建Session对象线程安全—同一实例被多个线程共享重量级:代表一个数据库 内部维护一个连接池
2.1 openSession():总是创建新的session,需要手动close()
2.2 getCurrentSession() : 必须在hibernate.cfg.xml设置session 上下文事务自动提交并且自动关闭session.从上下文环境中获得session,如果当时环境中不存就创建新的.如果环境中存在就使用环境中的,而且每次得到的都是同一个session (在session提交之前,提交之后就是新的了) 应用在一个session中有多个不同DAO操作处于一个事务时
3 Session:负责保存、更新、删除、加载和查询对象 轻量级--可以经常创建或销毁
3.1 Load与get方法的区别:简单理解:load是懒加载,get是立即加载.load方法当使用查出来的对象时并且session未关闭,才会向数据库发sql, get会立即向数据库发sql返回对象
3.3 merge(); 合并对象 更新前会先select 再更新
3.4clear()清空缓存,flush()将session中的数据同步到数据库两者组合使用于批量数据处理
3.4Transaction commit() rollback()
JPA: java persistence API 提供了一组操作实体bean的注解和API规范
SchemaExport
hiberante的生成数据库表(及其他ddl)的工具类
可以通过这个工具类完成一些ddl
四Hibernate查询
查询语言主要有:
HQL 、QBC (Query By Criteria条件查询) 、 Native SQL
Hql:
1、 属性查询
2、 参数查询、命名参数查询
3、 关联查询
4、 分页查询
5、 统计函数
五 优化
抓取策略
连接抓取(Join fetching) 使用 OUTER JOIN(外连接)来获得对象的关联实例或者关联集合
查询抓取(Select fetching) 另外发送一条 SELECT 语句抓取当前对象的关联实体或集合
另外可以配置hibernate抓取数量 限制批量抓取(Batch fetching)
另外可以通过集合过滤来限制集合中的数据量 使用
session.createFilter(topic.getReplies(),queryString).list();
检索策略延迟检索和立即检索 (优先考虑延迟检索)
N+1问题 指hibernate在查询当前对象时查询相关联的对象查询一端时 会查询关联的多端集合对象
解决方案:延迟加载 连接抓取策略 二级缓存 集合过滤 BatchSize限制记录数量
映射建议
使用双向一对多关联,不使用单向一对多
灵活使用单向一对多关联
不用一对一,用多对一取代
配置对象缓存,不使用集合缓存
一对多集合使用Bag,多对多集合使用Set
继承类使用显式多态
表字段要少,表关联不要怕多,有二级缓存撑腰
Hibernbate缓存机制 性能提升的主要手段
Hibernate进行查询时总是先在缓存中进行查询,
如缓存中没有所需数据才进行数据库的查询.
Hibernbate缓存:
一级缓存 (Session级别)
二级缓存 (SessionFactory级别)
查询缓存 (基于二级缓存 存储相同参数的sql查询结果集)
? 一级缓存 (session缓存)
Session缓存可以理解为session中的一个map成员, key为OID ,value为持久化对象的引用
在session关闭前,如果要获取记录,hiberntae先在session缓存中查找,
找到后直接返回,缓存中没有才向数据库发送sql
三种状态的区别在于:
对象在内存、数据库、session缓存三者中是否有OID
临时状态
内存中的对象没有OID, 缓存中没有OID,数据库中也没有OID 执行new或delete()后
持久化状态
内存中的对象有OID, 缓存中有OID,数据库中有OID
save() load() get() update() saveOrUpdate() Query对象返回的集合
游离(脱管)状态
内存中的对象有OID, 缓存中没有OID,数据库中可能有OID
flush() close()后
使用session缓存涉及三个操作:
1 将数据放入缓存
2 从缓存中获取数据
3 缓存的数据清理
4 二级缓存 SessionFactory级别
SessionFactory级别的缓存,它允许多个Session间共享缓存
一般需要使用第三方的缓存组件,如: Ehcache Oscache、JbossCache等
二级缓存的工作原理:
在执行各种条件查询时,如果所获得的结果集为实体对象的集合,
那么就会把所有的数据对象根据OID放入到二级缓存中。
当Hibernate根据OID访问数据对象的时候,
首先会从Session一级缓存中查找,
如果查不到并且配置了二级缓存,那么会从二级缓存中查找,
如果还查不到,就会查询数据库,把结果按照OID放入到缓存中
使用二级缓存有专门的提供商(get\find方法不会使用二级缓存)
查询缓存 基于二级缓存 存储相同参数的sql查询结果集(主要针对Query、Criteria)
下面蓝色字体属于深入优化,看不懂直接跳过
理解集合性能:影响到性能的主要原因大多数是集合引起
Hibernate定义了三种基本类型的集合: 值数据集合 一对多关联 多对多关联
这个分类是区分了不同的表和外键关系类型,但是它没有告诉我们关系模型的所有内容。 要完全理解他们的关系结构和性能特点,我们必须同时考虑“用于Hibernate更新或删除集合行数据的主键的结构”。 因此得到了如下的分类:
有序集合类 集合(sets) 包(bags)
所有的有序集合类(maps, lists, arrays)都拥有一个由<key>和 <index>组成的主键。 这种情况下集合类的更新是非常高效的——主键已经被有效的索引,因此当Hibernate试图更新或删除一行时,可以迅速找到该行数据。
<idbag>映射定义了代理键,因此它总是可以很高效的被更新。
有序集合类比集合(set)有一个好处。因为Set的内在结构, 如果“改变”了一个元素,Hibernate并不会更新(UPDATE)这一行。 对于Set来说,只有在插入(INSERT)和删除(DELETE) 操作时“改变”才有效(对于一对多无效 )
list, map和idbags是最高效的(非反向)集合类型,set则紧随其后。 在Hibernate中,set应该时最通用的集合类型,这时因为“set”的语义在关系模型中是最自然的。
但是,在设计良好的Hibernate领域模型中,我们通常可以看到更多的集合事实上是带有inverse="true" 的一对多的关联。对于这些关联,更新操作将会在多对一的这一端进行处理。因此对于此类情况,无需考虑其集合的更新性能
Bag和list是反向集合类中效率最高的
在把bag扔进水沟之前,你必须了解,在一种情况下,bag的性能(包括list)要比set高得多: 对于指明了inverse="true"的集合类(比如说,标准的双向的一对多关联), 我们可以在未初始化(fetch)包元素的情况下直接向bag或list添加新元素! 这是因为Collection.add())或者Collection.addAll() 方法 对bag或者List总是返回true(这点与与Set不同)。 一次性删除 逐个删除集合类中的元素是相当低效的 如果你想要把整个集合都删除(比如说调用list.clear()),
一次性删除并不适用于被映射为inverse="true"的集合。
没有监测和性能参数而进行优化是毫无意义的
你可以有两种方式访问SessionFactory的数据记录,第一种就是自己直接调用 sessionFactory.getStatistics()方法读取、显示统计数据。
此外,如果你打开StatisticsService MBean选项,那么Hibernate则可以使用JMX技术 发布其数据记录。你可以让应用中所有的SessionFactory同时共享一个MBean,也可以每个 SessionFactory分配一个MBean。下面的代码即是其演示代码: // MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server
数据记录(Metrics)
Hibernate提供了一系列数据记录,其记录的内容包括从最基本的信息到与具体场景的特殊信息。所有的测量值都可以由 Statistics接口进行访问,主要分为三类:
使用Session的普通数据记录,例如打开的Session的个数、取得的JDBC的连接数等;
? 实体、集合、查询、缓存等内容的统一数据记录
? 和具体实体、集合、查询、缓存相关的详细数据记录 ?
例如:你可以检查缓存的命中成功次数,缓存的命中失败次数,实体、集合和查询的使用概率,查询的平均时间等。请注意 Java中时间的近似精度是毫秒。Hibernate的数据精度和具体的JVM有关,在有些平台上其精度甚至只能精确到10秒。
你可以直接使用getter方法得到全局数据记录(例如,和具体的实体、集合、缓存区无关的数据),你也可以在具体查询中通过标记实体名、 或HQL、SQL语句得到某实体的数据记录。请参考Statistics、EntityStatistics、 CollectionStatistics、SecondLevelCacheStatistics、 和QueryStatistics的API文档以抓取更多信息。下面的代码则是个简单的例子: Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
六Hibernate事务
事务指作为单个逻辑工作单元执行的一系列操作,要么成功 要么失败
数据库事务的四个特性 (ACID)
原子性(Atomicity)
事务不可再分,事务的一系列操作都成功或都失败
一致性(Consistency)
保证系统的任何事务最后都处于有效状态来实现,失败回滚到开始状态
隔离性(Isolation)
事务与事务之间互不影响
持久性(Durability)
事务执行成功,在系统中产生的所有变化将是永久的
事务的并发问题:
第一类更新丢失
后提交的事务回滚覆盖了前面提交成功的事务(不支持事务的数据库可能发生)
脏读
读取了另外的事务没有提交的数据(脏数据)
不可重复读
后提交的事务在对同一条数据前后两次读取时由于前面的已提交的事务进行了对这条数据更新操作,
而使数据前后不一致
第二类更新丢失(不可重复读的特殊情况)
后提交的事务成功提交覆盖了前面的成功提交事务
虚读(幻读)
后一个事务在前后两次读取数据时由于前面已提交的事务进行了插入操作,
而使数据统计前后不一致(针对插入 删除)
事务控制中主要解决脏读、不可重复读、虚读这三个问题
在程序里考虑并发的效率一般设置隔离级别为2 (解决脏读问题)
其余两个问题自己通过程序解决
在事务管理层,Hibernate将其委托给底层的JDBC或者JTA,以实现事务管理和调度功能
Hibernate事务管理注意事项:
1. 避免一个事务使用多个Session
2. 避免多线程中访问同一个session
3 一个session可以对应多个事务 可以重用session缓存
不可重复读问题hibernate由悲观锁和乐观锁机制来解决
锁机制
为解决事务的并发问题,采用锁的机制来实现事务的隔离性
锁的主要类型:
共享锁
更新锁
独占锁
Hibernate支持两种锁机制:
悲观锁
依赖数据库的锁机制 利用 select ?. from ? for update 锁定记录
是对数据被外界修改持保守态度 在整个事务处理过程中,将数据处于锁定状态
能够保证操作最大程度的独占性,独占锁
缺点是数据库性能开销过大,对长事务,这样的开销往往无法承受;
乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制Hibernate乐观锁 使用版本检查实现乐观锁 在表中添加一个版本字段 根据StaleObjectStateException处理回滚
七 Hibernate最佳践(附加):
设计细颗粒度的持久类并且使用<component>来实现映射。
使用一个Address持久类来封装 street, suburb, state, postcode. 这将有利于代码重用和简化代码重构(refactoring)的工作。
对持久类声明标识符属性( identifier properties)。
Hibernate中标识符属性是可选的,不过有很多原因来说明你应该使用标识符属性。我们建议标识符应该是“人造”的(自动生成,不涉及业务含义)。
使用自然键(natural keys)标识
对所有的实体都标识出自然键,用<natural-id>进行映射。实现equals()和hashCode(),在其中用组成自然键的属性进行比较。
为每个持久类写一个映射文件
不要把所有的持久类映射都写到一个大文件中。把 com.eg.Foo 映射到com/eg/Foo.hbm.xml中, 在团队开发环境中,这一点显得特别有意义。
把映射文件作为资源加载
把映射文件和他们的映射类放在一起进行部署。
考虑把查询字符串放在程序外面
如果你的查询中调用了非ANSI标准的SQL函数,那么这条实践经验对你适用。把查询字符串放在映射文件中可以让程序具有更好的可移植性。
使用绑定变量
就像在JDBC编程中一样,应该总是用占位符"?"来替换非常量值,不要在查询中用字符串值来构造非常量值!更好的办法是在查询中使用命名参数。
不要自己来管理JDBC connections
Hibernate允许应用程序自己来管理JDBC connections,但是应该作为最后没有办法的办法。如果你不能使用Hibernate内建的connections providers,那么考虑实现自己来实现org.hibernate.connection.ConnectionProvider 考虑使用用户自定义类型(custom type)
假设你有一个Java类型,来自某些类库,需要被持久化,但是该类没有提供映射操作需要的存取方法。那么你应该考虑实现org.hibernate.UserType接口。这种办法使程序代码写起来更加自如,不再需要考虑类与Hibernate type之间的相互转换。
在性能瓶颈的地方使用硬编码的JDBC
In performance-critical areas of the system, some kinds of operations might benefit from direct JDBC. But please, wait until you know something is a bottleneck. And don't assume that direct JDBC is necessarily faster. If you need to use direct JDBC, it might be worth opening a Hibernate Session and using that JDBC connection. That way you can still use the same transaction strategy and underlying connection provider. 在系统中对性能要求很严格的一些部分,某些操作也许直接使用JDBC会更好。但是请先确认这的确是一个瓶颈,并且不要想当然认为JDBC一定会更快。如果确实需要直接使用JDBC,那么最好打开一个 Hibernate Session 然后从 Session获得connection,按照这种办法你仍然可以使用同样的transaction策略和底层的connection provider。
理解Session清洗( flushing)
Session会不时的向数据库同步持久化状态,如果这种操作进行的过于频繁,性能会受到一定的影响。有时候你可以通过禁止自动flushing,尽量最小化非必要的flushing操作,或者更进一步,在一个特定的transaction中改变查询和其它操作的顺序。
在三层结构中,考虑使用托管对象(detached object)
当使用一个servlet / session bean 类型的架构的时候, 你可以把已加载的持久对象在session bean层和servlet / JSP 层之间来回传递。使用新的session来为每个请求服务,使用 Session.merge() 或者Session.saveOrUpdate()来与数据库同步。
在两层结构中,考虑使用长持久上下文(long persistence contexts).
为了得到最佳的可伸缩性,数据库事务(Database Transaction)应该尽可能的短。但是,程序常常需要实现长时间运行的“应用程序事务(Application Transaction)”,包含一个从用户的观点来看的原子操作。这个应用程序事务可能跨越多次从用户请求到得到反馈的循环。用脱管对象(与session脱离的对象)来实现应用程序事务是常见的。或者,尤其在两层结构中,把Hibernate Session从JDBC连接中脱离开,下次需要用的时候再连接上。绝不要把一个Session用在多个应用程序事务(Application Transaction)中,否则你的数据可能会过期失效。
不要把异常看成可恢复的
这一点甚至比“最佳实践”还要重要,这是“必备常识”。当异常发生的时候,必须要回滚 Transaction ,关闭Session。如果你不这样做的话,Hibernate无法保证内存状态精确的反应持久状态。尤其不要使用Session.load()来判断一个给定标识符的对象实例在数据库中是否存在,应该使用Session.get()或者进行一次查询.
对于关联优先考虑lazy fetching
谨慎的使用主动抓取(eager fetching)。对于关联来说,若其目标是无法在第二级缓存中完全缓存所有实例的类,应该使用代理(proxies)与/或具有延迟加载属性的集合(lazy collections)。若目标是可以被缓存的,尤其是缓存的命中率非常高的情况下,应该使用lazy="false",明确的禁止掉eager fetching。如果那些特殊的确实适合使用join fetch 的场合,请在查询中使用left join fetch。
使用open session in view模式,或者执行严格的装配期(assembly phase)策略来避免再次抓取数据带来的问题
Hibernate让开发者们摆脱了繁琐的Data Transfer Objects (DTO)。在传统的EJB结构中,DTO有双重作用:首先,他们解决了entity bean无法序列化的问题;其次,他们隐含地定义了一个装配期,在此期间,所有在view层需要用到的数据,都被抓取、集中到了DTO中,然后控制才被装到表示层。Hibernate终结了第一个作用。然而,除非你做好了在整个渲染过程中都维护一个打开的持久化上下文(session)的准备,你仍然需要一个装配期(想象一下,你的业务方法与你的表示层有严格的契约,数据总是被放置到托管对象中)。这并非是Hibernate的限制!这是实现安全的事务化数据访问的基本需求。
考虑把Hibernate代码从业务逻辑代码中抽象出来
把Hibernate的数据存取代码隐藏到接口(interface)的后面,组合使用DAO和Thread Local Session模式。通过
Hibernate的UserType,你甚至可以用硬编码的JDBC来持久化那些本该被Hibernate持久化的类。 (该建议更适用于规模足够大应用软件中,对于那些只有5张表的应用程序并不适合。)
不要用怪异的连接映射
多对多连接用得好的例子实际上相当少见。大多数时候你在“连接表”中需要保存额外的信息。这种情况下,用两个指向中介类的一对多的连接比较好。实际上,我们认为绝大多数的连接是一对多和多对一的,你应该谨慎使用其它连接风格,用之前问自己一句,是否真的必须这么做。
偏爱双向关联
单向关联更加难于查询。在大型应用中,几乎所有的关联必须在查询中可以双向导航。
3.Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系)
类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、
Spring
一IOC
spring的两种注入方式:byType ,byName
spring 的生命范围:prototype ,single
spring 的生命周期:lazy-init=true 延迟加载当使用时才会new 为false(默认)时立即加载,服务器关闭则消毁。 AOP
好处:可以动态的添加删除切面上的业务逻辑代码,而不影响原代码
概念:
AOP :面向切面编程,与OOP互补
Join point:切点集合 由很多切点组成
Pointcut:切点 将新的业务逻辑插入现有的业务逻辑代码时,两者之间的交接点
Aspect:切面 一套要动业务逻辑,如:日志,事务等
Advice:通知 由切点和切面组成
Target 目标 被添加业务逻辑的对象
Weave 织入 将 A 添加给B ,Advice被应用至目标对象之上的过程
AOP核心思想
使用代理对象将应用程序中的业务逻辑同对其提供的通用服务
进行分离,
降低各部分之间的耦合度
可以动态添加和删除在切面上的通用服务而不影响原来的执行代码
AspectJ 一个AOP框架
Spring AOP 采用jdk的动态代理
OpenSessionInViewFilter的使用
Web应用中一个请求一个Session的模式
可以处理Hibernate懒加载问题还可以控制事务边界
需要使用ContextLoaderListener加载spring上下文
缺点:
1. Session的存活期放大 session缓存占用时间过长
2. 事务的会话周期过长 对事务并发性造成影响
? Spring中的事务管理
Spring的事务管理通过AOP代理来实现
根据事务属性,对每个代理对象的每个方法进行拦截,
在方法执行前启动事务,
方法执行完毕后根据是否有异常和异常种类进行提交或回滚
spring 默认通过捕获运行时异常实现事务提交或回滚
? 事务属性:
TransactionDefinition接口中定义事务属性
事务属性通常由事务的传播行为,
事务的隔离级别,
事务的超时值
事务只读标志等组成
Spring事务传播方式
主要控制当前的事务如何传播到另外的事务中
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。
如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。
外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_REQUIRED
支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(默认)
PROPAGATION_REQUIREDS_NEW
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
Spring事务隔离级别:
主要定义事务与事务之间在数据库读写方面的控制范围
主要解决脏读、不可重复读、虚读三个问题
ISOLATION_DEFAULT 默认级别
ISOLATION_READ_UNCOMMITED
事务最低的隔离级别,充许别外一个事务可以看到这个事务未提交的数据,
会产生脏读,不可重复读和幻像读
ISOLATION_COMMITED
保证一个事务修改的数据提交后才能被另外一个事务读取,可以避免脏读出现,
但是可能会出现不可重复读和幻像读
ISOLATION_REPEATABLE_READ
保证一个事务不能读取另一个事务未提交的数据外 可以防止脏读,不可重复读
但是可能出现幻像读
ISOLATION_SERIALIZABLE
花费最高代价但是最可靠的事务隔离级别。
事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读
Spring中的事务管理方式:
编程式和声明式(常用)
注意事项:
1. 事务边界设置在service层
2. 需要使用SessionFactory.getCurrentSession
(hibernate3.6)不需要配置Session上下文 hibernate.current_session_context_class
3. Spring默认通过捕获运行时异常实现事务回滚,
非运行期异常不会触发rollback
可以通过配置rollback-for 指定异常类
事务中不要catch异常,否则spring放弃事务管理s
基础题
HashSet_hashCode_equals
*
* HashSet 采取哈希算法 内部按哈希吗分组划分对象的存储区域 * Object定义的hashCode() 返回对象的哈希吗 * 当HashSet查找对象时 先获取哈希吗 然后根据哈希吗到对应区域查找 * 最后取出这个区域的对象使用equals比较 这样就提高了检索效益 * * 所以只重写equals() 不重写hashCode() * 那么equals比较相等的对象可能不等 因为相等的对象可能被放置在不同区域