一次OSIV调试经历
2009-11-27 16:43 莫耶 阅读(2278) 评论(1) 编辑 收藏 举报使用nHibernate有一段时间了,但一直没有使用它的延迟加载机制,究其根本大概源于刚接触nHibernate的失败调试经历——因为总是看到诸如“Could not initialize proxy - the owning Session was closed.”或者线程没有绑定Session之类的异常:
实际上园子里提供了不少解决这类问题的方案,如李永京的 NHibernate之旅(13):初探立即加载机制 等, 但终归觉得不够理想——较之各种方案描述的单一环境,既有框架是构建在spring.net之上的,意味着对待Session不进行侵入式管理,更进一步,spring.net对于Session的请求响应要覆盖到所有View。
直到最近,重新拜读了 NHibernate之旅(9):探索父子关系(一对多关系) ,意识到关联对象的加载是个问题,决定还是得把延迟加载用起来,事关性能。
经 刘冬 先生的指点,知道了spring.net有一种管理Session以用于lazy-load的模式——Open Session In View(OSIV),原理大致是
spring.net提供了一个HttpModule,“就是一个filter,每次request进来,就打开一个session放到ThreadLocal里,以后用到session就拿出来用,filter结束的时候,再清空ThreadLocal,关闭session”
OSIV模式一旦开启,lazy-load所需Session的绑定生灭,便不再需要开发者操心,听着确实很爽,于是便开始了长达一周噩梦般的调试:
一、先更新一下spring.net和nHibernate版本,分别是1.3.0RC和2.1.1GA。
二、更新各层引用 编译报错:
错误 34 “NHibernate.Engine.ISessionFactoryImplementor”不包含“CloseConnection”的定义,并且找不到可接受类型为“NHibernate.Engine.ISessionFactoryImplementor”的第一个参数的扩展方法“CloseConnection”(是否缺少 using 指令或程序集引用?) ...\DaoTemplate.cs 833 21 woodigg.DAO
在nH 2.1中,ISessionFactoryImplementor接口不再包含CloseConnection方法,将其注释,并查找了一下DaoTemplate中所有关于Session.Close的代码,全部注释(在OSIV中,不再干预Session的生命周期)。编译通过。
三、修改配置文件中的HibernateSessionFactory对象,在HibernateProperties中添加这样一个条目:
appDaoConfig.xml<!--动态代理-->
<entry key="proxyfactory.factory_class"
value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" />同时引用nHibernate.Bytecode.Castle和Castle.DynamicProxy2程序集。
四、修改web.config,注册OSIV模块,并让其作用于HibernateSessionFactory对象:
web.config<appSettings>
<!--SessionFactory-->
<add key="Spring.Data.NHibernate.Support.OpenSessionInViewModule.SessionFactoryObjectName" value="HibernateSessionFactory"/>
</appSettings>
<httpModules>
<!--OpenSessionInView-->
<add name="OpenSessionInView" type="Spring.Data.NHibernate.Support.OpenSessionInViewModule, Spring.Data.NHibernate21"/>
</httpModules>
五、在Mvc2站点中添加一个View,取一个拥有关联集合子对象的数据列表(如下图,ARTIST对象有一个ALBUMS关联对象,它是ALBUM对象的集合,一对多):
此时使用的搜索代码来自DaoTemplate,依赖于spring.net提供的HibernateTemplate:
DaoTemplate.csIList alist = HibernateTemplate.Find(hql);
六、使用MvcContrib的Grid进行绑定:
Index.aspx<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<ARTIST>>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<fieldset>
<legend>List</legend>
<%= Html.Grid(Model).Columns(
column =>{
column.For(c => c.Id).Named("ID");
column.For(c => c.Name).Named("名儿");
column.For(c => c.Belong).Named("在哪儿");
column.For(c => c.ALBUMS.Count).Named("专辑数");
})
.Attributes(style => "width:90%")
.RowStart(c => string.Format("<tr class='{0}'>", c.IsAlternate ? "alt" : ""))
%>
</fieldset>
</asp:Content>
七、运行,出现异常“Could not initialize proxy - no Session.”
八、查看Log4net日志,有这么一段:
2009-11-20 10:03:44,343 [1] DEBUG NHibernate.Transaction.AdoTransaction [(null)] - IDbTransaction disposed.
2009-11-20 10:03:44,343 [1] DEBUG NHibernate.Impl.SessionImpl [(null)] - closing session
2009-11-20 10:03:44,343 [1] DEBUG NHibernate.AdoNet.AbstractBatcher [(null)] - running BatcherImpl.Dispose(true)
2009-11-20 10:03:47,140 [1] ERROR NHibernate.LazyInitializationException [(null)] - Initializing[Ic.Model.ALBUM#1]-Could not initialize proxy - no Session.这说明一个问题:HibernateTemplate把Session关闭了。
九、弃用HibernateTemplate,写一个抽象类,把SessionFactory植入后,只负责调用活动的Session:
HibernateDao.csusing NHibernate;
namespace woodigg.DAO
{
/// <summary>
/// Base class for data access operations.
/// </summary>
public abstract class c {
private ISessionFactory sessionFactory;
/// <summary>
/// Session factory for sub-classes.
/// </summary>
public ISessionFactory SessionFactory
{
protected get { return sessionFactory; }
set { sessionFactory = value; }
}
/// <summary>
/// Get's the current active session. Will retrieve session as managed by the
/// Open Session In View module if enabled.
/// </summary>
protected ISession CurrentSession
{
get
{
return sessionFactory.GetCurrentSession();
}
}
}
}
十、将DaoTemplate的父类由HibernateDaoSupport改为HibernateDao,配置关系不用变化:
appDaoConfig.xml<object id="DaoTemplate" type="woodigg.DAO.DaoTemplate, woodigg.DAO">
<property name="SessionFactory" ref="HibernateSessionFactory" />
</object>
十一、再次运行,这次抛出的异常是(图1):
“No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here. ”
异常来自于sessionFactory.GetCurrentSession()方法,它无法为当前请求提供Session。这意味着:OSIV在MVC中没有生效。
十二、从此时起,走上了弯路:
以为OSIV需要提供一个初始化的Session供其调用,遂将CurrentSession做成了单例,但其后发生了“access the same ISession in two concurrent thread” 这样的错误; 依旧使用sessionFactory.GetCurrentSession(),使用Spring.Transaction.Interceptor.TransactionInterceptor 将 事务管理器Spring.Data.NHibernate.HibernateTransactionManager 引入到 DaoTemplate中,这样做确实见效了,运行伊始就有一个Session,但lazy-load失败了,所有关联对象均无法加载,在运行时监视栏里查看它们,都形如“proxy223355124...”。 很明显,Session完成任务就撤了!
十三、冷静下来想想,其实从始至终,OSIV都没有生效过。那究竟是什么导致的呢?会不会是OpenSessionInViewModule没有被spring.net引入到view中?
所有的线索,指向了那个一直被信任的控制器工厂SpringControllerFactory ,看看它是怎么实现的:
SpringControllerFactory.cspublic class SpringControllerFactory : IControllerFactory
{
//..
private static DefaultControllerFactory defalutf = null;
public IController CreateController(RequestContext context, string name)
{
IApplicationContext configContext
= new XmlApplicationContext(ParameterFactory.CfgFilePath,
ParameterFactory.CfgBusinessFilePath, ParameterFactory.CfgControllersFilePath,
ParameterFactory.CfgServicesFilePath);
string controllName = GetFirstUpcaseName(name) + "Controller";
if (configContext.ContainsObject(controllName))
{
return (IController)configContext.GetObject(controllName);
}
else
{
if (defalutf == null)
{
defalutf = new DefaultControllerFactory();
}
return defalutf.CreateController(context, name);
}
}
//..
}杯具啊!这个一年前的产物,在使用IApplication这个问题上手法笨拙,依靠预定义的配置文件路径引入环境。也许在CS结构中会有用处,但在BS环境中,这样更直接更全面(文件引用,实际没把包含OSIV配置的web.config引入环境,view扑空):
SpringControllerFactory.csWebApplicationContext configContext =
ContextRegistry.GetContext() as WebApplicationContext;
十四、至此,OSIV成功覆盖整个mvc应用。凡事有利弊,在java社区会有这样的讨论:
“open session in view 在访问量很大的时候,容易造成页面假死现象”
“因为osiv会在每次请求的过程中占用一个session,如果这个请求过程太长,session就无法释放了。可以在tomcat前端加一个apache,讲网速太慢的请求隔离掉。”
也许在这样的时候,一个好的网络运维,比你吭哧更管用 :)
欢迎转载,但必须保留文章的署名老莫的帐本子
并保留此链接:http://moye.cnblogs.com/
如有疑问请发邮件:moyerock@gmail.com