上一篇花了很长时间梳理了下SessionImplementor接口与Hibernate里其它关键类和接口的关系并画出类图,最后得出"集万千宠爱于一身的SessionImpl"标题.本篇将接着往下走,下面先看第二句 EntityPersister persister;
声明了一个变量persister.(写到这有两种选择,一是像对SessionImplementor一样对这个EntityPersister接口好好研究一番,这样的话研究的很细,不过一个突出的问题就是由get方法牵连出很多点,分散精力;另一选择是把这个EntityPersister放在当前DefaultLoadEventListener类里结合它的具体应用来有个初步的认识. ... 现在尝试第二种选择,由于对SessionImplementor的研究是用第一种方法来展开的,看看这第二种选择有什么优缺点,再进一步来看如何与第一种结合起来以达到优势互补.)
有了上面的研究策略,这样把DefaultLoadEventListener类里的第62行至第76行拿出来做为一个意群(借用高考语文阅读训练中的一个术语)整体研究.
先看persister这个变量的声明方式,这里在第62行并没有对其赋值,结合项目中的一些经验,这样的声明是有一定的风险的,处理不好就报NullPointerException了.
先说说项目中遇到的类似情况及是怎么报异常的:
这样的一个异常容易发生在MVC端,
假设在C端有如下的处理逻辑:
Result result;
if(parameterConditionOne){
result = conditionOneProgram.getResult();
}else if (parameterConditionTwo){
result = conditionTwoProgram.getResult();
}
.................
result.setXXX(); // --- A
看上面的代码,若两个条件都不满足的话,在A处就会报NullPointerException,原因就在于分析情况时没有考虑全就造成现在这样的漏网之鱼.若在else if后再加一个else分枝呢?这样也是有值得注意的地方的,是否在非parameterConditionOne非parameterConditionTwo时就一定是else里的情况呢?
看Hibernate是怎么处理类似的麻烦事的:
在源码里71至76行的判断及异常处理.也就是说在经过if...else的赋值后再看这个persister是否这空,若为空就抛出一个HibernateException,这样把NullPointerException这样很唐突很底层的异常转为更为specific地异常.
现在看了Hibernate这里的处理方法后,佩服Hibernate的同时,也感觉到自己的成长,这样也就更加坚定了自己研究源码.
上面这段让我想起了自己项目中的一个Bug,现在再回到Hibernate的处理逻辑中.
第一个分支,即:event.getInstanceToLoad() != null 也就是说若event在初始化时传入并设置了instanceToLoad属性,这当然不是get方法时的调用情况了,BTW:Session接口中的load(Object object, Serializable id)方法就是这样的情况:在初始化LoadEvent事例时把load方法传入的Object对象再次传给LoadEvent的构造方法,从而就赋给了instanceToLoad属性.
若是这样的已初始化了instanceToLoad属性,persister的初始化就不同了,即通过source.getEntityPersister来返回,这样好像也能说的过去,毕竟那个instance已经在persistenceContext里存在了,这个instance也已经绑定了与之对应的EntityPersister了,也就不用再new一个EntityPersister的实现类对象了.在这里需要做的就是把event里的EntityClassName赋下值,现在的猜想是若传入了instanceToLoad就没有再传相应类的名字.这也很好理解,毕竟一个instanceToLoad本身就已经带了很丰富的内容.
source.getEntityPersiter()
与event.getInstanceToLoad() != null相反的是没有传入instanceToLoad对象,这种情况下我猜想是Hibernate在此时利用如下语句new出来一个与当前EntityClassName对应的EntityPersister实现类对象.
persister = source.getFactory().getEntityPersister( event.getEntityClassName());
追着源码看,并没有找到我猜想的那个new,那两种getEntityPersister有什么区别?也就是说若instanceToLoad不为null时的source.getEntityPersister与instanceToLoad为null时的source.getFactory().getEntityPersister两种有什么不同?这样就落在getFactory有什么用?
...............
从表面上来看是落在getFactory的作用上,但若往深入地看时发现,source.getEntityPersister实质上也是调用getFactory,而真正不同的是,这个方法中又调用了SessionImpl类里的guessEntityName方法,顾名思义,就是根据传入的Object对象来猜出这个Object在Hibernate里的Entity名字,不过还想往下问的是这样guess出来的名字与利用Object的反射得到的className有什么不同?毕竟LoadEvent里的getEntityClassName就是通过Class类的getName()方法获得的.这个问题先放这吧.
接着往下走,就是看ID是用什么类来表示的?是Long?是Integer?是String?并把这个类型与xxx.hbm.xml中的配置的类型做比较,若不符的话就抛出TypeMismatchException,我觉得这样的比较意义倒不是很大,或者说我不大理解为什么要这样比较?像一个Long型的id若用一个int型的值来表达时也不是不可以的吧?当然只要那个实际的值别超过int类型的范围,不过不理解归不理解,在使用时还是一定注意这个问题的.
这里还有一个问题:
persister.getIdentifierType().isComponentType() && EntityMode.DOM4J == event.getSession().getEntityMode()
怎么上面这个条件成立时就不用来比较了呢?当然若是componentType的话就没有明显的一个className了,这倒是一个原因,但这与EntityMode.DOM4J又有什么关系呢?这个问题还是只能先放这了.
下面又有一个新类EntityKey,不禁要问这个类是干啥的?为什么叫EntityKey呢?这样这个onLoad方法在每次调用时都new出来一个这样的对象是不是很是浪费?好像是JVM在每new一个对象时很耗资源的.能不能像Spring所提倡的那样最好用单例?又是什么因素制约着不能用单例呢?带着这些问题看EntityKey这个类的源码.
看这个类的源码发现与其它的类并没什么特殊的联系.自身除了三个方法外,也没有特别的.
这三个方法分别为private的generateHashCode与访问修饰为默认的serialize还有一个static且返回值为Entity的deserialize方法.这个deserialize方法没什么可说的,它自身就是一个工厂方法,返回一个EntityKey对象.
先看generateHashCode方法,这个方法也只是在EntityKey的构造方法中调用一次,对传入构造方法里的各个参数分别调用它们的hashCode方法并最终拼出一值赋给EntityKey的属性hashCode,这样也就能多多少少地了为什么叫key了.那这个key又具体怎么用呢?只能在它的具体应用中来看了.这让我想起了现在项目中通过字符串的方式拼出来的值作为自制缓存中key来使用了,为什么不用类的hasCode呢?那样的话显得的更专业.
再看serialize这个方法,它将EntityKey里的属性通过writeObject方法写进传入的ObjectOutputStream参数中?为什么要这样呢?这个serialize方法在什么情况下调用?想达到什么效果?是要写入本地文件中吗?追着看了看没看出什么门道,就先放在这吧.
从EntityKey返回到DefaultLoadEventListener类里的onLoad方法.下面将根据loadType是否为isNakedEntityReturned与LockMode是否为LockMode.NONE来调用不同的load方法,这里有三个不同的方法,依次是load,proxyOrLoad,lockAndLoad.这三个方法中除了第三个方法lockAndLoad多一个SessionImplementor型的参数外,都接收前面准备好的或onLoad传来的四个参数:event, persister, keyToLoad, loadType.
大致看了下这三个方法,proxyOrLoad,lockAndLoad这两人都有视情况不同而调用load方法.这样就先研究这个load方法了.
今天写了不少,虽说是有些乱.在下一篇中将重点看这个load方法.
声明了一个变量persister.(写到这有两种选择,一是像对SessionImplementor一样对这个EntityPersister接口好好研究一番,这样的话研究的很细,不过一个突出的问题就是由get方法牵连出很多点,分散精力;另一选择是把这个EntityPersister放在当前DefaultLoadEventListener类里结合它的具体应用来有个初步的认识. ... 现在尝试第二种选择,由于对SessionImplementor的研究是用第一种方法来展开的,看看这第二种选择有什么优缺点,再进一步来看如何与第一种结合起来以达到优势互补.)
有了上面的研究策略,这样把DefaultLoadEventListener类里的第62行至第76行拿出来做为一个意群(借用高考语文阅读训练中的一个术语)整体研究.
先看persister这个变量的声明方式,这里在第62行并没有对其赋值,结合项目中的一些经验,这样的声明是有一定的风险的,处理不好就报NullPointerException了.
先说说项目中遇到的类似情况及是怎么报异常的:
这样的一个异常容易发生在MVC端,
假设在C端有如下的处理逻辑:
Result result;
if(parameterConditionOne){
result = conditionOneProgram.getResult();
}else if (parameterConditionTwo){
result = conditionTwoProgram.getResult();
}
.................
result.setXXX(); // --- A
看上面的代码,若两个条件都不满足的话,在A处就会报NullPointerException,原因就在于分析情况时没有考虑全就造成现在这样的漏网之鱼.若在else if后再加一个else分枝呢?这样也是有值得注意的地方的,是否在非parameterConditionOne非parameterConditionTwo时就一定是else里的情况呢?
看Hibernate是怎么处理类似的麻烦事的:
在源码里71至76行的判断及异常处理.也就是说在经过if...else的赋值后再看这个persister是否这空,若为空就抛出一个HibernateException,这样把NullPointerException这样很唐突很底层的异常转为更为specific地异常.
现在看了Hibernate这里的处理方法后,佩服Hibernate的同时,也感觉到自己的成长,这样也就更加坚定了自己研究源码.
上面这段让我想起了自己项目中的一个Bug,现在再回到Hibernate的处理逻辑中.
第一个分支,即:event.getInstanceToLoad() != null 也就是说若event在初始化时传入并设置了instanceToLoad属性,这当然不是get方法时的调用情况了,BTW:Session接口中的load(Object object, Serializable id)方法就是这样的情况:在初始化LoadEvent事例时把load方法传入的Object对象再次传给LoadEvent的构造方法,从而就赋给了instanceToLoad属性.
若是这样的已初始化了instanceToLoad属性,persister的初始化就不同了,即通过source.getEntityPersister来返回,这样好像也能说的过去,毕竟那个instance已经在persistenceContext里存在了,这个instance也已经绑定了与之对应的EntityPersister了,也就不用再new一个EntityPersister的实现类对象了.在这里需要做的就是把event里的EntityClassName赋下值,现在的猜想是若传入了instanceToLoad就没有再传相应类的名字.这也很好理解,毕竟一个instanceToLoad本身就已经带了很丰富的内容.
source.getEntityPersiter()
与event.getInstanceToLoad() != null相反的是没有传入instanceToLoad对象,这种情况下我猜想是Hibernate在此时利用如下语句new出来一个与当前EntityClassName对应的EntityPersister实现类对象.
persister = source.getFactory().getEntityPersister( event.getEntityClassName());
追着源码看,并没有找到我猜想的那个new,那两种getEntityPersister有什么区别?也就是说若instanceToLoad不为null时的source.getEntityPersister与instanceToLoad为null时的source.getFactory().getEntityPersister两种有什么不同?这样就落在getFactory有什么用?
...............
从表面上来看是落在getFactory的作用上,但若往深入地看时发现,source.getEntityPersister实质上也是调用getFactory,而真正不同的是,这个方法中又调用了SessionImpl类里的guessEntityName方法,顾名思义,就是根据传入的Object对象来猜出这个Object在Hibernate里的Entity名字,不过还想往下问的是这样guess出来的名字与利用Object的反射得到的className有什么不同?毕竟LoadEvent里的getEntityClassName就是通过Class类的getName()方法获得的.这个问题先放这吧.
接着往下走,就是看ID是用什么类来表示的?是Long?是Integer?是String?并把这个类型与xxx.hbm.xml中的配置的类型做比较,若不符的话就抛出TypeMismatchException,我觉得这样的比较意义倒不是很大,或者说我不大理解为什么要这样比较?像一个Long型的id若用一个int型的值来表达时也不是不可以的吧?当然只要那个实际的值别超过int类型的范围,不过不理解归不理解,在使用时还是一定注意这个问题的.
这里还有一个问题:
persister.getIdentifierType().isComponentType() && EntityMode.DOM4J == event.getSession().getEntityMode()
怎么上面这个条件成立时就不用来比较了呢?当然若是componentType的话就没有明显的一个className了,这倒是一个原因,但这与EntityMode.DOM4J又有什么关系呢?这个问题还是只能先放这了.
下面又有一个新类EntityKey,不禁要问这个类是干啥的?为什么叫EntityKey呢?这样这个onLoad方法在每次调用时都new出来一个这样的对象是不是很是浪费?好像是JVM在每new一个对象时很耗资源的.能不能像Spring所提倡的那样最好用单例?又是什么因素制约着不能用单例呢?带着这些问题看EntityKey这个类的源码.
看这个类的源码发现与其它的类并没什么特殊的联系.自身除了三个方法外,也没有特别的.
这三个方法分别为private的generateHashCode与访问修饰为默认的serialize还有一个static且返回值为Entity的deserialize方法.这个deserialize方法没什么可说的,它自身就是一个工厂方法,返回一个EntityKey对象.
先看generateHashCode方法,这个方法也只是在EntityKey的构造方法中调用一次,对传入构造方法里的各个参数分别调用它们的hashCode方法并最终拼出一值赋给EntityKey的属性hashCode,这样也就能多多少少地了为什么叫key了.那这个key又具体怎么用呢?只能在它的具体应用中来看了.这让我想起了现在项目中通过字符串的方式拼出来的值作为自制缓存中key来使用了,为什么不用类的hasCode呢?那样的话显得的更专业.
再看serialize这个方法,它将EntityKey里的属性通过writeObject方法写进传入的ObjectOutputStream参数中?为什么要这样呢?这个serialize方法在什么情况下调用?想达到什么效果?是要写入本地文件中吗?追着看了看没看出什么门道,就先放在这吧.
从EntityKey返回到DefaultLoadEventListener类里的onLoad方法.下面将根据loadType是否为isNakedEntityReturned与LockMode是否为LockMode.NONE来调用不同的load方法,这里有三个不同的方法,依次是load,proxyOrLoad,lockAndLoad.这三个方法中除了第三个方法lockAndLoad多一个SessionImplementor型的参数外,都接收前面准备好的或onLoad传来的四个参数:event, persister, keyToLoad, loadType.
大致看了下这三个方法,proxyOrLoad,lockAndLoad这两人都有视情况不同而调用load方法.这样就先研究这个load方法了.
今天写了不少,虽说是有些乱.在下一篇中将重点看这个load方法.