OpenSessionInViewFilter实践

这次做一个项目,用上了OpenSessionInViewFilter这个过滤器,以前就知道使用它可以把session一直绑定在整个request请求之上,以前也有试过,但是之前的项目没有使用到事务,所以一直会有问题,后来查了网上看到说这个使用过滤器一定要配置事务所以就放弃了。
这次做的项目,框架比较严谨,采用典型的三层结构,也在service层配置了spring注解式事务,接着就把OpenSessionInViewFilter用上去了,效果挺好的,显然比关闭延迟加载的效率会高很多。配置很简单:

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

但是做着做着就发现了一个问题,我们的框架模仿了struts2的paramsPrepareParams拦截器栈的执行方式,会在提交保存表单的请求时,从请求中取出ID值,然后根据这个ID查找数据库中的对象,接着再用请求中的参数覆盖这个对象属性,再把对象传到Service层中进行处理。当然在保存这个对象之前还必须进行一些逻辑校验,在通过后才保存到数据库。问题就出来,我们发现不管是否校验通过,这个对象都被修改了。代码大概是这样的:

Foo foo = ...;
if(check(foo)){
dao.saveOrUpdate(foo);
return 0;
}else{
log.error(...);
return 1;
}

排查的时候我们一想就猜到问题可能出在OpenSessionInViewFilter之上,由于从最早的去数据库中查询这个对象开始,session就一直没有关闭,即使到了service层,这个对象仍然是出于托管状态,那不管是否显示调用了saveOrUpdate方法,由于hibernate会进行脏检查,所以修改都会发生。后面我们想了几个方案:
[list]
[*]最土的一个,取出这个对象后,把这个对象给clone一份,然后操作这个clone的对象,还要注意修改的时候不能使用update方法而要改用merge方法,否则会报错,异常信息为"org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session",这个方法倒是让我学到了一种不用clone方法clone对象的方式~~
[*]关闭hibernate的脏检查,可以通过session.setReadOnly(data, true),但是要确实修改的时候必须调用session.setReadOnly(data, false),还要在这行调用之后做一次修改对象的方法(调用对象的某个属性的set方法)。
[*]在最早的数据库取出需要的持久化对象后,就手工脱管,这可以通过session.evit(data)。
[*]使用OpenSessionInViewFilter之前做个判断,让修改和保存的方法都不经过这个过滤器,通过附加参数来实现。
[/list]
最后我们选择了最后一种,因为对代码的改动量最少。过了一阵子,轻松下来后又想到这个问题,想看看这个过滤器到底是怎么实现的,于是乎,打开docjar,查了OpenSessionInViewFilter的源码,首先看到注释说这个过滤器可以用在没有事务的环境中:It is suitable for service layer transactions via org.springframework.orm.hibernate3.HibernateTransactionManager or org.springframework.transaction.jta.JtaTransactionManager as well as for non-transactional execution (if configured appropriately)。这里要说明一点,网上有的文章说OpenSessionInViewFilter不能用于非事务环境中,还贴出了部分源码,但是我比较了两份源码,发现不同,可能是版本的问题。
接着看下去,发现我配置的方式默认为single session mode,这时Hibernate FlushMode默认为NEVER(MANUAL),但是在事务之中会把它暂时性的修改为AUTO,我就在想会不会是由于这个原因造成的,查了资料,无关。倒是后来让我搞明白了该如何在非事务的环境中使用OpenSessionInViewFilter,应该这样配置:

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

有一天突然灵感闪现,想起来Spring的事务回滚原则,默认在抛出运行时异常的时候,事务就会被回滚。马上试了一下,创建个自定义异常类,在逻辑判断错误的地方,抛出这个异常,现在代码看起来就像这样:

Foo foo = ...;
if(check(foo)){
dao.saveOrUpdate(foo);
}else{
throw new CustomeException(...);
}

果然可以了!这同时让我想到以前有个朋友问我,他看到书上网上很多源码在检查错误的时候都抛出一个异常,搞不懂这样比返回一个错误代码好在什么地方?我那时也说不出个所以然来,只觉得如果有方法本身有返回值的时候就不好办了。现在我又找到一个原因了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值