Hibernate笔记

来源:互联网 发布:asics淘宝旗舰店 编辑:程序博客网 时间:2024/06/10 11:09

最近把项目从OJB移植到Hibernate, 收获很多。OJB已经被apache标记为过时了,所以我也没有太多关注。Hibernate作为当前最流行的ORM框架,也有很多缺陷。

以下是我遇到的问题及解决方法.

1. config file or annotation?

老的方式是把java类和表的映射关系写在配置文件里,好处是上手快,功能也比较全,但不直观,不易维护。标注的方式是hibernate文档提倡的方式,把mapping关系和java代码写在一起,比较直观,容易维护,唯一的缺点是,标注用的是JPA的统一标准,有一些hibernate特有的功能,不能用JPA的标准来实现,我的项目中最后是结合JPA的标注和Hibernate特有的标注一起用。标注真的很方便,我们项目实体类比较多,如果是用配置文件,那工作量比使用标注要大好几倍,另外标注由于是写在java文件里, eclipse会自动编译并检查是否有语法错误,配置文件的方式就不行了。

 

2. ID的问题

Hibernate中的ID当然是要用业务无关的字段最好了,但是我们项目由于是做migration, 很多表都没有单独的primary key, 用的是联合主键composit keys. 一种方法是创建独立的类,封装对应表的key, 比如User类有firstName和lastName作为联合主键,那么应该创建一个ID类UserKey的类封装firstName和lastName两个属性,hibernate会自动把UserKey映射到数据库[first_name, last_name]组合。由于我们的表很多,如果是用这种方式,要新建很多的类,这其实是文档推荐的方式。为了减少工作量,我们还是没有采用这种方式。

我们直接用了hibernate里的composit-key方式,即把多个字段标记为ID, hibernate实际上会创建一个与实体类相同的类来作为ID. 比如User类,有firstName, lastName, age等等属性,Hibernate会用User类作为ID类,只有firstName和lastName两个属性不为空,其他的属性都为null. 这样有一个缺陷就是在是用metadata的时候,ClassMetadata.getIdentifierPropertyName() 和ClassMetadata.getIdentifierType()时得不到想要的值或返回null. 你不能通过hibernate的metadata来找出类中哪些属性是用做ID的, 这给一些log或audit操作带来麻烦。我在项目用不得已使用反射来解决这个问题,只要是带有@ID标注的,都是用做ID的属性。

 

3. 一对多,多对一,多对多,一对一

hibernate在这些方面功能还挺全面的,配置关联还涉及到很多问题,lazy load, orphan removal, collection filter, 级联更新,级联删除等等。不得不说如果想省事,把所有的hibernate能做事情都交给hibernate来做,在关联复杂项目里,一定会造成性能问题。

4. Lazy load:

如果设置一对多和多对多,并且lazy为false的话,在一个树形的结构里,load一个父类对象,hibernate会把所有子孙都自动装载, 会有产生大量的join语句,造成性能问题。另外如果,lazy为true, 采用手动装载的方式当然更灵活,但是有一个问题,如果在事务外调用get方法,由于hibernate会尝试取数据库查找数据,此时会报异常。这个限制很容易被忽略。

5. orphan removal

在一对多的时候,在保存User对象时, 如果集合属性List<Adress> adresses中有任何元素被移除,那么Hibernate会自动同步到数据库,把相应的数据从数据库中删除,不用你显示调用session.delete(obj)来删除。正常情况这个功能还挺好用的,只要在设置orphanRemoval为true就可以了。但我们系统业务逻辑比较复杂,有时需要把整个集合属性替换为另一个集合,比如setAddresses(anotherAddesses), 这时orphanRemoval就会失效,我只好自己写了个类似集合snapshot的东西,每次整个替换的时候更新这个snapshot, 保存的时候把新的集合和snapshot做对比,然后用session.delete(obj)取更新数据库。总之orphanRemoval还是挺好用的,但是有限制,就是不能把整个集合替换为另外一个。

6. filter

hibernate的filter, 用途也很有限制,主要是设置的限制,文档只提供 >=, <=之类的比较的条件,比如minLength <= :minLength, 不能提供比如 type in ('type1', 'type2'...)的限制条件。而且带参数的时候配置比较复杂。除非是项目中在很多地方都需要同一种过滤条件,才可以考虑建一个filter。

7. NamedQuery and NamedNativeQuery

一种使用HQL, 一种使用SQL, HQL不如SQL灵活, HQL要求所有表和属性都是已经映射好的。

8. cascade

级联更新我没有。一个项目原因,一个是让hibernate做级联更新,不容易维护,不可控。另外一点就是,即使禁用级联更新,由于hibernate有dirty-check机制,有时容易让人迷惑,明明只调用hibernate保存了一个对象,但hibernate却把相关的更新全部提交到数据库。

9. dirty check

hibernate会在session关闭时把所有已做的更新提交到数据库。所以最好显示保存被改变了的对象,有不需要的保存而被被改变的对象,要调用session.evict(obj)移出session缓存。

10. flush

hibernate提供很多种flushmode, 设置flushmode会影响hibernate的性能。具体不多说。但是错误调用flush,还会导致错误,我就遇到hibernate发送duplicated sql到数据库导致异常。原因是我在调用了一次session.flush()后,比如要更新user对象,由于配置了PostUpdateEventListener, 在onPostUpdate方法中又调了一次session.flush(), 从方法调用的堆栈理可以看出,同一堆栈里有两个session.flush(), 结果导致同一更新提交了2次。解决方法是把onPostUpdate方法中的部分代码以及session.flush()移出。

11. 缺点一: hibernate总是按照insert, update, collection remove/update/create, delete的顺序更新数据库,而不是按照调用相应操作的时间顺序。

session flush之后,hibernate会按照下面的顺序依次执行,插入,更新,集合删除/更新/创建,最后是删除。这样会造成的问题是,比如一个表里有user id (1, 2, 3, 4, 5....), user id 不能重复。那么我先调用session.delete(old user 4)删除id 为4 的user, 然后session.save(new user 4) 插入一个新的user, id 也为4. 由于hibernate先执行插入再执行删除, 而不管调用session.delete和sassion.save的时间先后,程序就会在执行插入的时候报异常。Hibernate规定的以下这个顺序是有缺陷的。解决办法就是在调用session.delete(old)之后,再马上调用以下session.flush(), 之后在调用session.save(new).

不幸的是我们系统中有很多类似情况的,所以我只好在所有session.delete()后都调用了session.flush().

未完待续

原创粉丝点击