Hibernate使用Java 反射机制 而不是字节码增强程序来实现透明性
如果JDBC代码写的完美,优化做好,那么JDBC效率是最高的。但是,实际开发中非常不现实,对程序员要求太高。
一般情况下,hibernate内部进行了JDBC的优化处理,以及增加缓存机制,大大提高了运行效率。
SessionFactory使用要点如下:
1、负责创建Session对象,可以通过Configuration对象创建SessionFactory对象
2、SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。
3、SessionFactory还负责维护Hibernate的二级缓存。
4、SessionFactory对象的创建会有较大的开销,而且SessionFactory对象采取了线程安全的设计方式,因此在实际中SessionFactory对象可以尽量的共享,在大多数情况下,一个应用中针对一个数据库可以共享一个SessionFactory实例
SessionFactory只能有一个(单例模式或者变为static属性)
Session是持久化操作的基础。Session的设计是非线程安全的,因此,一个Session对象只可以由一个线程使用。
使用Hibernate进行操作时(增、删、改)必须显示的调用Transaction(默认:autoCommit=false)
Transaction transaction = session.beginTransaction();
transaction.commit();
处于持久化状态的对象,当发生值得改变时,hibernate能检测到,立刻修改数据库的数据。如果在save之后,执行sql语句,效率较低。建议对持久对象值进行修改在save之前最好
//若对应id=1的记录不存在,返回null;
//get方法先在session缓存(一级缓存)中查找,若无,再在sessionfacotory(二级缓存)中查找,若无,再去数据库中查找,还无,返回null.
User u = (User) s.get(User.class, 1);
//若对应id=1的记录不存在,则抛出异常。
//load一般用于我们可以保证这个记录一定存在的情况。该方法有懒加载!
User u = (User) s.load(User.class, 1);
一对多:
一般使用双向关联
cascade属性:级联操作。操作一个对象时将该对象相关属性对象也进行同样操作。
all:进行任何操作都级联。
save-update:保存和更新操作时
delete:删除操作时级联
all-delete-orpnan:当被关联对象失去宿主时,将其级联删除。
none(默认) :不级联
inverse属性:由哪一方维护外键的值。默认为false, 双方都可以维护关联关系; 为true, 则表示由“多方”维护;
建议:
为true, 由多方维护提高效率。调用必须调用“多方”(一般由多方维护)。
为false,写程序方便,运行效率低下。
Inverse=false,关联关系可以由双方维护!
但是我们一般将关联关系交给”多方”来维护。上面两次实验,第一次交给了多方维护,执行了3条SQL语句。第二交给了一方维护,执行了3条insert语句和2条update语句(假如增加200个雇员那么就会有200个update语句)。如果我们交给一方来维护,显然会有多余的SQL语句执行,从而降低了效率。因此,我们一般建议关联关系由多方维护!那么这时候,我们可以强制通过inverse=true,指定由多方维护!
一对一关系我们一般采用唯一外键关联!
多对多:
多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系型数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。
性能优化:
注意session.clear()的运用,尤其在不断分页循环的时候,会产生另外一种形式的内存泄露 ( //面试题:Java有内存泄漏吗?语法级别没有 但是可由java引起,例如:连接池不关闭,或io读取后不关闭)
面试题:
1、open session每次都是新的,需要手动close
getCurrentsession从上下文找,如果有,用旧的,如果没有,建新的
如果你正在查询,使用的openSession而没有手动关闭,多次之后会导致连接池溢出
2、get load的区别:
load返回的是代理对象,等到真正用到对象的内容时才发出sql语句
get则返回的是实体对象,直接从数据库加载,不会延迟
get请求的对象数据库中没有时返回null,load则抛异常。
总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
session.clear();:无论是load还是get,都会首先査找缓存(一级缓存),如果没有,才会去数据库査找,调用clear()方法可以强制清除session缓存
3、transient:内存中有一个新new的对象JavaBean,缓存(即session)中没有指向new对象的Map,跟数据库也没有关系(数据库没对应记录的存在)
persistent:save()完后为persistent状态,内存中有JavaBean(生成了Id),以map形式存入到session中 ,数据库中有对应记录
detached:session.close()后(但session.close()方法不会显示调用,在session.commit()时系统自动close),缓存中清空,跟session无关,数据库有对应记录,但是数据库和内存中的对象没有连接到一起,所以叫做detached托管的。
4、双向关联关系的两铁律:
凡是双向关联,必设mappedBy
在程序中也要A.set(B)以及B.set(A)
5、继承映射:
单表继承(整个类继承结构一个表)
优点:只有一张表,操作方便,效率高。
缺点:冗余数据多,增加子类要修改表结构,数据多时表非常大。
joined-subclass(父类映射成表,一个类一张表,互相关联,不独立)
优点:冗余字段少,表结构合理(关系模型设计上优良)
缺点:多态查询慢(一般不要用,最好直接指定类型。比如查询man,则会把man的子类表也查一遍)、查询需要外连接
union-subclass (父类不映射成表,每个子类一张表,互不关联,独立)
优点:表都独立利于移植,查询不用外连接速度快
缺点:数据库包含重复字段过多,包含了父类中的字段
注意:父类和子类id不能重复,用自动递增不行,可用increment/hilo/uuid等。
如何选用?(一般使用joined-subclass)
如果子类属性不多,总数据量不大,选用单表继承
如果子类属性较多,可用joined-subclass.
union-subclass用的很少
6、hibernate的树状结构:前面有介绍
7、1+N问题:
在一个对象中关联了另一个对象,同时FetchType为Eager,比如说最典型的ManyToOne(当然OneToMany也有这问题,当在Many这头设Eager),默认Many方是FetchType为Eager,当取Many里的对象时,会单独再发一条SQL语句将他关联的对象也取出来,即本来只要发一条SQL,结果发了1+N条, 解决方案:fetch=FetchType.LAZY,还可以用Join fetch(在1方,查找得到了n个对象, 那么又需要将n个对象关联的集合取出,于是本来的一条sql查询变成了n+1条。)
二级缓存
在对象更新,删除,添加相对于查询要少得多时, 二级缓存的应用将不怕n+1问题,因为即使第一次查询很慢,之后直接缓存命中也是很快的,刚好又利用了n+1。
8、三种缓存:
一级缓存( Session缓存)
一级缓存的管理 : 应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate() 时,如果在Session缓存中还不存在相应的对象,Hibernate 就会把该对象加入到第一级缓存中。 可以通过close/clear/evict清空缓存
一级缓存的作用 : 因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。Session内部缓 存的主要作用是保持Session内部数据状态同步。
二级缓存(SessionFactory缓存):需手动开启
开启方式:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
如何使用: 类定义前面:@cache,指该类的对象都会放入二级缓存。@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) //放入二级缓存中也可以被修改。一般用它。
什么内容时候放入二级缓存: 经常被访问、改动不频繁、数量有限。
get/load会使用二级缓存。
iterate也会使用二级缓存。
list默认会往二级缓存中存放数据,即通过list查出的结果会放入二级缓存。但是list本身查询时不会使用二级缓存。
查询缓存:
查询缓存只对query.list()起作用
查询缓存依赖于二级缓存,因此一定要打开二级缓存。
查询缓存实现机制:以查询语句为key,查到的对象的id为value
查询缓存的配置和使用:
开启二级缓存:
<property name="hibernate.cache.use_query_cache">true</property> //默认是fasle
在程序中必须手动启用查询缓存,如:query.setCacheable(true);
缓存算法问题:缓存满了后,将内存中哪个对象清掉。
LRU:Least Recently Used 最近最少被使用的。每个缓存对象都记录一个最后使用时间。
LFU:Least Frequently Used 最近使用频率最少。
FIFO:First In First Out
9、事务基本概念
ACID即是atomicity(原子性),consistency(一致性),isolation(隔离性)和durability(持久性)的首字母的缩写
原子性:表示一个事务内的所有操作是一个整体,要 么全部成功,要么全失败;
一致性:表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;
隔离性:事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
持久性:事务完成之后,它对于系统的影响是永久性的。
事务隔离级别从低到高:
读取未提交(Read Uncommitted)
读取已提交(Read Committed)
可重复读(Repeatable Read)
序列化(serializable)
read-commited(读取已提交的数据 项目中一般都使用这个)不会出现dirty read,因为只有另一个事务提交才会读出来结果,但仍然会出现 non-repeatable read 和 phantom-read
使用read-commited机制可用悲观锁 乐观锁来解决non-repeatable read 和 phantom-read问题
我们一般采用读取已提交,配合各种并发访问控制策略来达到并发事务控制的目的。
10、乐观锁与悲观锁
乐观锁Optimistic Locking:
顾名思义就是保持一种乐观的态度,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。
它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败。
常用实现方法:
在我们的实体中增加一个版本控制字段,每次事务更新后就将版本(Version)字段:版本字段的值加1.
在实体类中增加@Version, private int version;getset 即可。
悲观锁Pessimistic Locking:
基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
乐观锁和悲观锁的比较:
乐观锁:
优势:并发性好,性能较高。
缺点:用户体验不好,录入了半天,提交时被告知已经修改!
悲观锁:
优势:会锁住记录,一个用户修改完成前,其他用户不能操作该记录。
缺点:并发性不好,性能不高。
对于悲观锁是针对并发的可能性比较大,而一般在我们的应用中用乐观锁足以。
Hibernate实践总结
1、数据量巨大,性能要求高,hibernate由于在ORM映射中对系统资源消耗也比较高,所以不适合。
2、Hibernate适合:逻辑复杂,数据量不大。
3、sessionFactory的创建非常消耗资源,整个应用一般只要一个。
4、将所有的集合属性配置设置为懒加载。 Hibernate2.x默认是false, hibernate3.x默认是true
5、在定义关联关系时,集合首选Set,如果集合中的实体存在重复,则选择List,数组性能最差。
6、在一对多的双向关联中,一般将集合(多)的inverse设置为true,让集合的对方维护关联关系。
7、HQL子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。
8、对大数据量查询时,慎用list() 返回查询结果
9、在性能瓶颈的地方使用JDBC。
10、使用双向关联。在大型应用中,几乎所有的关联必须在查询中可以双向导航。
Hibernate工作原理及为什么要用?
原理:
1.读取并解析配置文件
2.读取并解析映射信息,创建SessionFactory
3.打开Sesssion
4.创建事务Transation
5.持久化操作
6.提交事务
7.关闭Session
8.关闭SesstionFactory
为什么要用:
1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
2. Hibernate是如何延迟加载?
1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection)
2. Hibernate3 提供了属性的延迟加载功能;当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。
3.Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系)
类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、
4. 说下Hibernate的缓存机制
1. 内部缓存存在Hibernate中又叫一级缓存,属于应用事物级缓存
2. 二级缓存:
a) 应用及缓存
b) 分布式缓存
条件:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非 关键数据
c) 第三方缓存的实现
5. Hibernate的查询方式
Sql、Criteria,object comptosition
Hql:
1、 属性查询
2、 参数查询、命名参数查询
3、 关联查询
4、 分页查询
5、 统计函数
6. 如何优化Hibernate?
1.使用双向一对多关联,不使用单向一对多
2.灵活使用单向一对多关联
3.不用一对一,用多对一取代
4.配置对象缓存,不使用集合缓存
5.一对多集合使用Bag,多对多集合使用Set
6. 继承类使用显式多态
7. 表字段要少,表关联不要怕多,有二级缓存撑腰
7. Struts工作机制?为什么要使用Struts?
工作机制:
Struts的工作流程:
在web应用启动时就会加载初始化ActionServlet,ActionServlet从
struts-config.xml文件中读取配置信息,把它们存放到各种配置对象
当ActionServlet接收到一个客户请求时,将执行如下流程.
-(1)检索和用户请求匹配的ActionMapping实例,如果不存在,就返回请求路径无效信息;
-(2)如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中;
-(3)根据配置信息决定是否需要表单验证.如果需要验证,就调用ActionForm的validate()方法;
-(4)如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActuibErrors对象, 就表示表单验证成功;
-(5)ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action,如果相应的 Action实例不存在,就先创建这个实例,然后调用Action的execute()方法;
-(6)Action的execute()方法返回一个ActionForward对象,ActionServlet在把客户请求转发给 ActionForward对象指向的JSP组件;
-(7)ActionForward对象指向JSP组件生成动态网页,返回给客户;
为什么要用:
JSP、Servlet、JavaBean技术的出现给我们构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,所以在此之上,我们需要一个规则、一个把这些技术组织起来的规则,这就是框架,Struts便应运而生。
基于Struts开发的应用由3类组件构成:控制器组件、模型组件、视图组件
8. Struts的validate框架是如何验证的?
在struts配置文件中配置具体的错误提示,再在FormBean中的validate()方法具体调用。
9. 说下Struts的设计模式
MVC 模式: web应用程序启动时就会加载并初始化ActionServler。用户提交表单时,一个配置好的ActionForm对象被创建,并被填入表单相应的数据,ActionServler根据Struts-config.xml文件配置好的设置决定是否需要表单验证,如果需要就调用ActionForm的 Validate()验证后选择将请求发送到哪个Action,如果Action不存在,ActionServlet会先创建这个对象,然后调用 Action的execute()方法。Execute()从ActionForm对象中获取数据,完成业务逻辑,返回一个ActionForward对象,ActionServlet再把客户请求转发给ActionForward对象指定的jsp组件,ActionForward对象指定的jsp生成动态的网页,返回给客户。
10. spring工作机制及为什么要用?
1.spring mvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作。
2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller.
3.DispatcherServlet请请求提交到目标Controller
4.Controller进行业务逻辑处理后,会返回一个ModelAndView
5.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
6.视图对象负责渲染返回给客户端。
为什么用:
{AOP 让开发人员可以创建非行为性的关注点,称为横切关注点,并将它们插入到应用程序代码中。使用 AOP 后,公共服务 (比 如日志、持久性、事务等)就可以分解成方面并应用到域对象上,同时不会增加域对象的对象模型的复杂性。
IOC 允许创建一个可以构造对象的应用环境,然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的,IOC 就像反 过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素(singleton)和直接构造(straight construction),每一个对象都是用其协作对象构造的。因此是由容器管理协作对象(collaborator)。
Spring即使一个AOP框架,也是一IOC容器。 Spring 最好的地方是它有助于您替换对象。有了 Spring,只要用 JavaBean 属性和配置文件加入依赖性(协作对象)。然后可以很容易地在需要时替换具有类似接口的协作对象。}