hibernate 回顾一
开发流程:
1. 由Domain object -> mapping -> db(官方推荐)
2. 由DB开始,用工具生成mapping和Domain object。(使用较多)。
3. 由映射文件开始。
Domain Object限制
1. 默认的构造方法(必须的)
2. 有无意义的标识符id(主键)(可选)
3. 非final的,对懒加载有影响(可选)
persist与save的区别:
当开启事务时,二者效果是一样的。
当不开启事务时,save会发出增加语句,只是未提交(即回滚)
当不开启事务时,persist方法不会发出增加语句插入记录。
load()与get()的区别:
load返回的是代理,不会立即访问数据库,在用到属性时才发出查询语句。
get直接发出查询语句
对象的状态:
瞬时(transient):数据库中没有数据与之对应,超过作用域会被JVM垃圾回收器回收,一般是new出来且与session没有关联的对象。
持久(persistent):数据库中有数据与之对应,当前与session有关联,并且相关联的session并没有关闭,事务没有提交:持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)。
脱管(detached):数据库中有数据与之想对应,但当前没有session与之关联,托管对象状态发生改变,hibernate不能检测到。
saveOrUpdate,merge(根据ID和version的值来确定是save或是update,ID是0或者是null时,是瞬时的),调用merge你的对象还是托管的。
处于persistent的对象会在事务提交时,进行同步。
HQL(Hibernate Query Language)
面向对象的查询语言,与SQL不同,HQL的对象名是区分大小写的(除了JAVA类和属性其他部分不区分大小写);HQL中查的是对象而不是表,并且支持多态:HQL主要通过Query接口来操作,Query的创建方式:
Query q = session.createQuery(hql);
from Person;
from User user where user.name=:name
from User user where user.name=:name and user.birthday <:birthday.
Criteria是一种比HQL更面向对象的查询方式,Criteria的创建方式:
Creteria crit = session.createCriteria(DomainClass.class);
crit.add(Restrictions.eq(“name”, name));
crit.add(Restrictions.);
hibernate中类与属性与数据库中关键字冲突的解决办法:
1. 修改表名或字段名
2. 把表名或字段名加反引号
hibernate实现分页:
query.setFirstResult();设置查询开始的记录数,从0开始
query.setMaxResults();设置查询出的记录数
hibernate属性文件和xml文件可以同时存在,但xml文件中的属性会覆盖属性文件中的属性。也可以通过编码方式实现。
hibernate默认不会提交数据
关联关系映射通常情况下是最难配置正确的。包括单向关系映射,和双向关系映射。在传统的数据建模中,允许为Null的值的外键被认为是一种不好的实践。因此Hibernate建议外键设置为不允许为Null。
多对一关联映射:
<many-to-one name="user" class="User" not-null="false" lazy="false" >
<column name="user_id" ></column>
</many-to-one>
<set name="pens" lazy="false">
<key column="user_id" >
</key>
<one-to-many class="Pen"/>
</set>
一对一外键关联映射:
<many-to-one name="user" class="User" not-null="false" lazy="false" unique=”true”>
<column name="user_id" ></column>
</many-to-one>
<one-to-one name="pen" class="Pen" property-ref="user" ></one-to-one>
基于主键的one-to-one(person的映射文件)
<id name="id">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="name" column="pen_name" not-null="true"/>
<one-to-one name="user" constrained="true" lazy="false" />
<one-to-one name="pen" lazy="false"/>
注意保存从对象时,一定要先保存主对象。
注意此时,当查询主对象时,无论是否懒加载,用连接表查询,
当查询从对象时,先查询从对象,当用到主对象时,然后连接表查询
多对多关联:
多对多在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;hibernate会为我们创建中间关联表,转换称两个一对多。
<set name="pens" table="users_pens">
<key column="user_id" />
<many-to-many class="Pen" column="pen_id"></many-to-many>
</set>
<set name="users" table="users_pens">
<key column="pen_id" />
<many-to-many class="User" column="user_id"></many-to-many>
</set>
多对多中,应该只有一方维护关系,如果双方都维护关系,会出现在中间表中重复插入数据的现象。
注意:实际设计中,我们一般并不会在少的一端建立存放多的集合,因为如果多的一端如果很多的话,查询全部效率是很低的,而且存储集合需要占用大量的内存空间,一般我们不会建立,而只是单端关联,手动分页查询多的一段的数据。
组合键:
<component name=”name”>
<property name=”” value=””>
</component>
注:当两个实体有关联,如果要在一张表里表示所有的属性,则用组合键,如果在两张表里表示可用关联。
hibernate一般情况是对于关联的查询,是通过两条select语句实现的,默认情况下也都是懒加载的,是通过cglib生成代理对象,而对于通过主键实现的一对一关联,如果是查询主对象是个例外,它只是发一条左连接查询。
hibernate 映射list集合
<list name=”emps”>
<key column=”depart_id”/>
<list-index column=”order_col” />
<one-to-many class=”Employee” />
</list>
</class>
hibernate用下面这列来记录插入的顺序,这样在数据库表中就会多加一列
<list-index column=”order_col” />
<bag name=”emps”>
<key column=”depart_id”/>
<one-to-many class=”Employee” />
</bag>
</class>
这是依旧映射java中的list,但是它不保存顺序,即数据库表中不会有记录插入顺序的一列。
此外hibernate还可以映射list, array, map
一般情况下,选择set就可以了,list(bag), array, map实际上用的较少
注意,持久化中的集合类不应该是具体的集合类,应该是接口。
级联和关系维护
cascade和inverse
Cascade用来说明当对主对象进行某种操作时,是否对其关联的从对象也作类似的操作,常用的cascade:
none, all, save-update, delete, lock, refresh, evict, repliace, persisit, merge, delete-orphan。一般对many-to-one, many-to-many 不设置级联,在<one-to-one>和<one-to-many>中设置级联。
inverse表“是否放弃维护关联关系”(在java里两个对象的产生关联时,对数据库表的影响),在one-to-many和many-to-many的集合定义中使用,inverse=”true”表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置称false(注意hibernate的缺省值默认呢为false)
one-to-many维护关联关系就是更新外键,many-to-many维护关联关系就是在中间表增加记录。
注:配置称one-to-one的对象不维护关联关系。
Session刷出(flush)
每间隔一段时间,Session会执行一些必需的SQL语句来把内存中的对象的状态同步到JDBC连接中。这个过程被称为刷出(flush),默认会在下面的时间点执行:
在某些查询执行之前
在调用org.hibernate.Transaction.commit()的时候
在调用Session.flush()的时候
涉及到的SQL语句会按照下面的顺序发出执行。
所有对实体进行插入的语句,其顺序按照对象执行Session.save()的时间顺序
所有对实体进行更新的语句
所有进行集合删除的语句
所有对集合元素进行删除,更新或者插入的语句
所有进行集合插入的语句
所有对实体进行删除的语句,其顺序按照对象执行Session.delete()的时间顺序
(有一个例外是,如果对象使用native方式生成ID(持久化标识))的话,它们一执行save就会被插入。
除非你明确地发出了flush()指令,关于Session何时会执行这些JDBC调用是完全无法保证的,只能保证它们的执行的前后顺序,当然,Hibernate保证,Query.list(..)绝对不会返回已经失效的数据,也不会返回错误数据。
Hibernate传播性持久化(transitive persistence)
对每一个对象都要执行保存,删除或重关联操作让人感觉有点麻烦,尤其是在处理许多彼此关联的对象的时候。一个常见的例子是父子关系。
异常处理: 不应该强迫应用程序开发人员,在底层捕获无法恢复的异常。在大多数软件系统中,非检查期异常和致命异常都是在相应方法调用的堆栈的顶层被处理的。(也就是说,在软件上面的逻辑层),并且提供一个错误信息给应用软件的用户(或者采取其他某些相应的操作)。
懒加载: load的使用相对较少。Hibernate.initialze(Object object);初始化懒加载。
一对一懒加载(系统影响不大):
查询主对象不会懒加载,因为查询主对象时,不能确定从对象是否存在,从对象才会懒加载而且抓取方式必须是feach=’select’,contrained=true。
lazy标识是否使用代理,feach采用何种抓去策略,二者可能相互牵扯。
一对多懒加载(影响较大) ,如果查询发费的代价较大,不如直接赋予代理,节省效率。lazy=true fetch=select
多对一赖加载(影响不大)与一对一大致相同
能够懒加载的对象都是被Hibernate该写过的代理。
解决方案:可以在调用时,传入是否懒加载(属性)
或者使用openSessionInvew
数据类型:
<property name=”name” type=”java.lang.String” />
type可以是hibernate,java类型,或者你自己的类型(需要实现hibernate的一个接口)
基本类型一般不需要在映射文件(hbm.xml)中说明,只有在一个JAVA类型和多个数据库数据类型相对应时,并且你想要的和hibernate缺省映射不一致时,需要在映射文件中指明类型(如:java.util.Date,数据库DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成为DATATIME型),而如果你想映射成TIME,则你必须在映射文件中指定类型。
Session是非线程安全的,生命周期较短,代表一个和数据库的连接,在B/S系统中,一般不会超过一个请求;内部维护一级缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接。
缓存的作用主要用来提高性能,可以简单的理解成一个Map;使用缓存涉及到三个操作:把数据放入缓存,从缓存中获取数据,删除缓存中的无效数据。
一级缓存,Session级共享
save, update, saveOrUpdate,load,get list,iterator,lock这些方法都会将对象放在一级缓存中,一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用evict,clear方法清除缓存中的内容。但是能从缓冲中拿数据的不多,get, load, iterator.
二级缓存,SessionFactory级共享,实现为可插拔,通过修改cache.provider_class参数来改变;
hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入对Hibernate不支持的缓存的实现。
分布式缓存和中央缓存。
使用缓存的条件:
读取大于修改
数据量不能超过内存容量。
对数据要有独享的控制
可以容忍出现无效数据
JTATransaction
可以简单的理解成垮数据库的事务,由应用JTA容器实现;使用JTATransaction需要配置hibernate.transaction.factory_class参数,该参数缺省值是org.hibernate.transaction.JDBCTransactionFactory,当使用JTATransaction时,需要将该参数改成org.hibernate.transaxtion.JTATransactionFactory,并配置jta.UserTransaction参数JNDI名(Hibernate在启动JTATransaction时要用该值到JNDI的上下文Context中去找javax.transaction.UserTransaction).
javax.transaction.UserTransaction tx = context.lookup(“jndiName”);
session context和事务边界
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
1. thread:ThreadLocal用来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界,此时session不能调用close当commit或rollback的时候session会自动关闭。
2. jta:有jta事务管理器技术来管理事务。
悲观锁和乐观锁
悲观锁有数据库来实现,乐观锁由hibernate用version和timestam来实现。
session.fllush()是同步session一级缓存和数据库。
大批量处理
大量操作数据时可能造成内存溢出,解决办法如下:
1. 清除session中的数据
for (int i=0;i<10000000;i++) session.save(obj);
for(int i=0;i<10000000;i++) {
session.save(obj);
if (i%50 == 0) {session.flush(); session.clear();}
}
2. 用StaelessSession接口:它不和一级缓存、二级缓存交互,也不触发任何事件,监听器,拦截器,通过该接口的操作会立刻发给数据库,与JDBC的功能一样。
StatelessSession ss = sessionFactory.openStatelessSession();该接口的方法与Session类似。
Query.executeUpdate()执行批量更新,会清除相关联的类二级缓存(sessionFactory.evict(class)),也可能会造成级联,和乐观锁定出现的问题(不能更新乐观缩版本号)。
Query q = s.createQuery(from User);
List<User> users = q.list();
for(User user : users) {
u.setBirthday(new Date());
}
批量更新的方法(3.0之后提供的方法):
Query q1 = s.createQuery(update u set birthday=:bd from user as u);
q1.executeUpdate():
Hibernate不适合的场景:
不适合OLAP(On-Line Analytical Processing联机分析处理),以查询分析数据为主的系统;适合OLTP(On-line transaction proceesing联机事务处理)。
对于些关系模式设计不合理的老系统,也不能发挥hibernate优势,数据量巨大,性能要求苛刻的系统,hibernate也很难达到要求,批量操作数据的效率也不高。
拦截器与事件都是hibernate的扩展机制,Interceptor接口是老的实现机制,现在改成事件监听器机制;他们都是hibernate回调接口,hiberante在save,delete,update…等会回调这些类。
事件系统(Event system):
如果需要响应持久层的某些特殊事件,你也可以使用Hibernate3的事件框架。该事件系统可以用来替代拦截器,也可以作为拦截器的补充来使用。
基本上,Session接口的每个方法都有相对应的事件。比如LoadEvent,FlushEvent,等等。当某个方法被调用时,Hibernate Session会生成一个相对应的事件并激活所有配置好的事件监听器。
本文详细介绍了Hibernate框架的核心技术,包括开发流程、对象状态管理、查询语言HQL、关联关系映射、缓存机制等内容,并探讨了Hibernate在不同场景下的适用性和限制。
1万+

被折叠的 条评论
为什么被折叠?



