`
kyo100900
  • 浏览: 639432 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

如何解决一些项目开发和维护中的问题——Hibernate实战篇

阅读更多

本文想将我的一些开发维护心得与问题得与大家分享,也许文中的内容你已经遇到过,并解决了,但可以当时并没有想为什么要这么做?要分享的内容还是围绕经典的webwork, spring Hibernate ,没办法绝大多数做 Java 的人还得靠这几个吃饭.开始吧.

 

 

Hibernate 部分

 

Hibernate 是这三个框架最好入门,但最难掌握的.用不好,随随便便一个请求,就查询几百条 SQL 语句是很容易发生的.这样我们通过分析生成出来的 SQL 语句,看看哪里有优化的必要.

 

场景一:

考虑以下 Pojo 实体之间的联系:

World 一对多  Country 一对多  Province  一对多 

City 这样就形成了一棵树.每一个 World 实体是树的根部, City 是叶子结点.

好,我现在将所有实体的一对多配置中配置 lazy=”false” ,例如

World.java 中配置:

<bag name="countrys" lazy="false" cascade="all">
        <key column="fk_world_id" />
	<one-to-many class="com.leo.domain.query.Country" />
</bag>
 

 

其它关联配置类似,现在我要查询 World 实体,代价是非常大的.

比如说:

 

 

List<World> lists = session .createQuery( "from World" ).list();

<!---->1.    <!---->发送一条 SQL 语句取出所有 World select *  from tbl_world

<!---->2.    <!---->根据上面的查询结果,分别发送一条语句得到每个 World 所持有的 Country 对象:

select * from tbl_country where fk_world_id=?

select * from tbl_country where fk_world_id=?

...省略

<!---->3.    <!---->接下的查询重复上面的步骤,直接最后的叶子结点.

 

如果记录很多,你可以欣赏到像骇客帝国那样的 SQL 语句刷屏效果了.不要以为这样的情况很少见,几张表关联在稍微复杂一点的业务逻辑上就能反映出来,我上面是为了描述清楚所以举了一个简单的例子,如果直接将系统代码关系弄出来,估计大家也没耐心看蜘蛛网了.

 

你也许马上会说,干吗不让 lazy=”true” ,这样只会执行一条语句.没错! 那是因为我现在只是单独的把 Hibernate 部分抽出来看了,因为他们无法解决当 lazy=”true” 后的更多问题,所以,只好设置 lazy=”false” 一了百了,肯定不出错,但性能其差.

 

解决办法

<!---->1.      <!---->打断关联,即去掉 < bag name = "countrys" lazy = "false" cascade = "all" > 部分,如果这些要关联的表不经常用来显示,查询,那么这一招不错.

<!---->2.      <!---->设置 lazy= true ”,但会出现很多新的问题,待会我在 Spring 部分会提到.

心得:

在使用一多对时,要慎重考虑关联的跨度,间接关联也是关联,尤其在当只能 lazy=”false” 的场合下,一定要考虑是直接取消一对多.这样情况,就算用缓存,代价也是相当大的.

 

 

场景二:

注意此问题在 Hibernate+Spring 中才会出现,如果只是单纯的 Web framework+Hibernate 不会出现此问题.如果你开启了 OpenSessionInViewFilter 模式,那么在编写代码时,容易出现很多问题.下面是我发现的一个问题,首先不知出于什么原因,我们底层的 dao 在封装 delete 方法时出现不同的版本 :

 

版本一

public void delete(Object obj) throws DAOException {
		hibernateTemplate.delete(obj);
	}

 

 

版本二

public void deleteByKey(final Class cls, final String key,
			final String value) throws DAOException {
		hibernateTemplate.execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				String deleteClassName = cls.toString().substring(
						cls.toString().lastIndexOf(".") + 1);
				Transaction tx = session.beginTransaction();
				int deletedEntities = session.createQuery(
						"delete " + deleteClassName + " where " + key + "='"
								+ value + "'").executeUpdate();
				tx.commit();
				session.close();
				return null;
			}
		});
	}

 

 

本质上没有多大区别,一个是直接采用 Spring HibernateTemplate ,另一个是采用 Spring hibernateTemplate.execute() 方法来回调,但是请注意,我们在回调代码里既有 tx.commit() 操作,又有 session.close() 操作, 这对于使用了 OpenSessionInViewFilter 模式来说,就会出问题.

 

来看一段错误的调用代码吧:                          

假设以下代码封装某个 Service txSave() 方法中,并且我已经在 Spring applicationContext.xml 配置了, tx* 为前缀的方法都需要事务支持.

 

//功能,先删除所有记录
dao.deleteByKey(UserInfo.class, "userId", "superleo");

//根据页面选择的checkbox,循环生成一次
for(int i=0; i<list.size(); i++){
	UserInfo info = new UserInfo();
	info.setUserId(userId);
...
dao.save(info);
}	

 

 

这样执行时,会报 Hibernate Session 的事务启动不成功,原因就是咱们上面的 deleteByKey() 方法将原本由 Spring OpenSessionInViewFilter 来提交的事务以及要关闭的 Session 自作主张的由我们来做了 .

 

解决办法

直接采用 hibernateTemplate 来删除,比如说现在改成:     

List userInfos = dao.find("from UserInfo info where info.userId = ?"
                       , parameters);
int size = userInfos.size();
for (int i = size - 1; i >= 0; i--) {
dao.delete(conRoles.get(i));
}

 

 

其它 Hibernate 问题:

 

  •    hibernate 事务嵌套异常问题

 

这个问题发生时,我开始很困惑,一开始明明是好好的,只是将

< bag name = "**" lazy = "false" cascade = "all" >

改成了

< bag name = "**" lazy = "true" cascade = "all" >

就出问题了.

 

如果你遇到了类似的问题,请先不要急着改代码,上述配置文件改成

 

< bag name = "**" lazy = "true" cascade = "delete-orphan" >

< bag name = "**" lazy = "true" cascade = "none" >

试试.                  

 

  • <!---->    <!---->Hibernate 实体应该是细粒度的吗?

 

对于这个问题,我一直深信不疑,但在实际开发中,你会发现大量的冗余字段,而这些冗余字段的初衷可能仅仅是因为不愿意建 “双向关联” 上面我们已经看到“一对多”如果滥用存在很多问题,但反过来“多对一 ”却是一个非常好的最佳实践,对 Hibernate 来说,先看下面代码:

 

user.getGroup() user.getGroup().getId()  

 

上面不会实际上立即发送一条 sql 取出 group ( 当然可以配置成 eager fetch ) ,而且这种情况用的还真不少.

 

而现在我们的 User 实体却是下面这种情况:

 

User.class 下有                         

 

groupName, groupId

departmentName, departmentId

.....

 

Group 实体的 name 属性改变时, User groupName 属性非常可能被忽略掉,从而信息更新不及时. departmentName 同理也是如此,如果到处都是这样的设计的话,你就祈祷用户永远不要改组名或部门名称吧.

 

 

  • <!----> <!---->Hibernate get() load() 方法.看到这里,请不要误会,我不是想说 get load 的区别,但如果知道 get load 区别的开发人员,似乎心里都感觉 load get 好,因为 Hibernat Session 缓存起了作用.但其实不是这样的, load() 有时候就像一块毒瘤,因为如果记录不存时,他会抛出可恶的异常.所以,有人即想享受 Hibernat Session 缓存,又想摆平 load() 异常,于是有了下面的“妙笔”:
Object o = null;
		try{
			o = hibernateTemplate.load(cls, id);
		}catch(Exception e){
			o = null;
		}
		return o;

 

 

其实简简单单一句:

       return hibernateTemplate .get(cls, id); 就可以搞定的问题,弄得如此复杂,要记住:异常本身也会造成性能下降.所以,在 Web 实际开发过程中,极力推荐使用 get

 

 

 

  • <!----> 关于使用 Hibernate 缓存.

使用 Hibernate 缓存很容易使用,如果系统可以使用的话,尽可能的使用.但遗憾的是,我们系统要求任何操作都要反应出最新值来,而且有频繁的插入,删除操作,在使用缓存时,记录无法即时的反应出来,总要间隔一个时间段,才能正确的同步显示在页面上.我打算如用上线一段时间,各项都基础信息都相对稳定时,可以考虑再次使用缓存.

 

 

  • <!---->  <!---->如何管理 Hibernate *.hbm.xml 文件

 

一般人都喜欢直接打开后,手工编写或修改,这样做最快最直接.我早期开发 Hibernate 的时候,也一直这么做.但现在似乎不行:实体已经快 100 个了.维护期间数据库新增加字段频繁,时间一长,手工编写不仅又慢又枯燥,还容易出问题.这样有两个比较好的解决办法:

<!---->1.    <!---->使用 Ant + XDoclet

<!---->2.    <!---->使用 Hibernate Annoation

 

实体越多,表越多,关联越多,优势都明显;毕竟同时维护代码和 *.hbm.xml 真的辛苦还不讨好,映射弄错了,马上就有人抱怨了.

 

 

 

 

 

接下来的 Spring,Webwork 部分正在编写中.

8
7
分享到:
评论
7 楼 打倒小日本 2008-05-30  
楼主在我心中的地位一落千丈啊
6 楼 spiritfrog 2008-05-27  
明明第一个例子中的多层树形菜单, 就是常会碰到表的关联查询, 怎么说如果不常碰到,就将lazy=false;又说lazy=true有如何如何问题。
我看这里不如去搞定lazy=true的问题, 折腾lazy=false造成整个树的查找, 明明就是有问题的, 还可以视而不见。
5 楼 spiritfrog 2008-05-27  
感觉楼主的很多问题都是分析和使用不正确造成的
而且很多时候不是在给出正确方法,而是胡乱改配置尝试, 然后像得到真理一样又写了出来,误人子弟啊。
readonly已经指出了一个,
我也来指出几个:
1.
user.getGroup() 或 user.getGroup().getId(),说不会发出sql取出groupid,这个是当然的,怎么是错误呢?hibernate的书里面都说明了这个情况,你怎么就没发现?
2.
Hibernate 的 get() 和 load() , 难道get()就不会从session缓存拿了吗?不是的
3.
hibernate 事务嵌套异常问题
 

这个问题发生时,我开始很困惑,一开始明明是好好的,只是将

< bag name = "**" lazy = "false" cascade = "all" >

改成了

< bag name = "**" lazy = "true" cascade = "all" >

就出问题了.

  这到底跟事务嵌套什么关系了?                



4 楼 bruce.peng 2008-05-27  
引用

Readonly 昨天
文章错误太多,看了第一条就看不下去了:
不要在HibernateCallback()里面调用任何transaction相关,session close/commit等代码,因为这些工作spring会帮你完成的

既然使用了HibernateCallback的匿名内部类,就让Spring来管理。你可以看看Spring这处的源码,就很清楚啦。
3 楼 lzmhehe 2008-05-26  
我觉得 这样在事务控制方面有点不方便
2 楼 kyo100900 2008-05-26  
Readonly 老大,能不能再多说一些啊,我在实际开发中遇到的问题远不止这些,如果概念错了,就不好办了。
1 楼 Readonly 2008-05-26  
文章错误太多,看了第一条就看不下去了:
不要在HibernateCallback()里面调用任何transaction相关,session close/commit等代码,因为这些工作spring会帮你完成的

相关推荐

Global site tag (gtag.js) - Google Analytics