Liferay PortletPreference store()方法研究

我们对于PortletPreference 的store()用的非常广泛,很多情况下,我们一般对其进行一些设定,然后最后调用store()存储之,类似以下代码:


1
2
3
PortletPreferences preferences  = renderRequest.getPreferences();
preferences.setValue(“preference_portlet_id”,portletInstanceId);
preferences.store();


下面我们来研究这个store()方法的本质,(花了我足足2个下午,说实话,到存储层之后那段代码的调试真不是太简单的,好多是用的动态代理)


详细分析:


首先,这个store()方法的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
       public  void  store()  throws  IOException, ValidatorException {
             if  (_portletId ==  null ) {
                   throw  new  UnsupportedOperationException();
             }
             try  {
                   Portlet portlet = PortletLocalServiceUtil.getPortletById(
                         getCompanyId(), _portletId);
                   PreferencesValidator preferencesValidator =
                         PortalUtil.getPreferencesValidator(portlet);
                   if  (preferencesValidator !=  null ) {
                         preferencesValidator.validate( this );
                   }
                   PortletPreferencesLocalServiceUtil.updatePreferences(
                         getOwnerId(), getOwnerType(), _plid, _portletId,  this );
             }
             catch  (SystemException se) {
                   throw  new  IOException(se.getMessage());
             }
       }


从宏观上来说,它主要做了3件事情:

Line 7-9:它从Portlet池中根据companyId和portletId来获取我们目标的要操作的portlet

Line 10-14:对于Portlet的Preference进行validate.

Line 15-17:它吧检验过的Portlet,吧它的PortletPreference更新到对应的数据库表中。


因为我们的重点应该是如何更新数据库表和怎么更新,所以我们的重点放在第三部分



在我们的代码PortletPreferencesLocalServiceUtil.updatePreferences()方法,它最终会转为PortletPreferencesLocalServiceImpl.updatePreferences()方法的调用。

173530494.png

从上面可以看出来,它首先通过PortletPreferencesFactoryUtil的toXML方法吧我们的PortletPreferences对象转成一个xml字符串,这是第一个亮点,原因很简单,因为String是序列化的,方便存储。

比如我们的例子中,我们的PortletPreferences被转为下面的字符串:

173721138.png



紧接着,它会调用重载的updatePreferences()方法进行更新,并且刚才转为的preferences字符串也作为参数被传递进来,这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  PortletPreferences updatePreferences(
                   long  ownerId,  int  ownerType,  long  plid, String portletId,
                   String xml)
             throws  SystemException {
             PortletPreferences portletPreferences =
                   portletPreferencesPersistence.fetchByO_O_P_P(
                         ownerId, ownerType, plid, portletId);
             if  (portletPreferences ==  null ) {
                   long  portletPreferencesId = counterLocalService.increment();
                   portletPreferences = portletPreferencesPersistence.create(
                         portletPreferencesId);
                   portletPreferences.setOwnerId(ownerId);
                   portletPreferences.setOwnerType(ownerType);
                   portletPreferences.setPlid(plid);
                   portletPreferences.setPortletId(portletId);
             }
             portletPreferences.setPreferences(xml);
             portletPreferencesPersistence.update(portletPreferences,  false );
             PortletPreferencesLocalUtil.clearPreferencesPool(ownerId, ownerType);
             return  portletPreferences;
       }



其实如果熟悉框架的人一眼就可以看出这里大多数操作都是对数据库的操作。


首先,它在第7-8行通过调用PortletPreferencesPersistence的fetchByO_O_P_P方法,然后传递4个参数,来从数据库中取出已经存放的PortletPreference,我们细化看下:

174105839.png

不难看出,这个fetchByO_O_P_P()方法是从2个级别查询的,首先它会从查询缓存中获取,如果没有的话(因为有时候我们会吧查询缓存禁用)则执行数据库查询,看下这些 语句,可以发现最终拼凑的查询语句是:SELECTportletPreferences FROM PortletPreferences portletPreferences  WHERE 上述4个参数条件

这是第二个亮点:因为数据库中一个PortletPreference是由4个参数共同决定的,其中有ownerId,portlet layout id ,所以这可以解释为什么不同的用户可以使用自己的PortletPreference对象而彼此之间不会打架,因为每个用户的userId不同)


一旦查询到PortletPreference之后,拿出来,它先赋值给PortletPreferences,然后会去查看这个对象是否为null,如果为null,则新建一个,否则则使用原有的。


因为我们的例子中,已经曾经点击过save()方法,所以是旧的,这会去触发PortletPreferencePersistenceImpl的updateImpl()方法

174816893.png

而它会去创建一个ClassLoaderSession(),然后调用BatchSessionUtil.update()方法来持久我们的portletPreferences,并且因为是update,所以动作是merge.


我们来细看下BatchSessionUtil.update()方法的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public  void  update(Session session, BaseModel<?> model,  boolean  merge)
             throws  ORMException {
             if  (merge || model.isCachedModel()) {
                   session.merge(model);
             }
             else  {
                   if  (model.isNew()) {
                         session.save(model);
                   }
                   else  {
                         boolean  contains =  false ;
                         if  (isEnabled()) {
                               Object obj = session.get(
                                     model.getClass(), model.getPrimaryKeyObj());
                               if  ((obj !=  null ) && obj.equals(model)) {
                                     contains =  true ;
                               }
                         }
                         if  (!contains && !session.contains(model)) {
                               session.saveOrUpdate(model);
                         }
                   }
             }
             if  (!isEnabled()) {
                   session.flush();
                   return ;
             }
             if  ((PropsValues.HIBERNATE_JDBC_BATCH_SIZE ==  0 ) ||
                   ((_counter.get() % PropsValues.HIBERNATE_JDBC_BATCH_SIZE) ==  0 )) {
                   session.flush();
             }
             _counter.set(_counter.get() +  1 );
    }

可以发现,因为我们的session是 ClassLoaderSession,而且我们的动作是merge ,所以它最终会调用ClassLoaderSession的merge()方法

175223685.png

而ClassLoaderSession的merge()方法通过动态代理会委托调用com.liferay.portal.dao.orm.hibernate.SessionImpl的merge()方法:

175348259.png

而它会接着委托到org.hibernate.Session的merge()方法,所以说,最终这个调用实际上是一个数据库调用。


纵观上文,读者肯定有疑问,既然最终是个数据库的调用,那么操作的表是什么呢?我们回到BatchSessionImpl.update()方法:

175628168.png

从上面调试可以看出,实际session.merge(model)本质上是因为Liferay的数据模型和数据库做了一个O/R mappinig,然后通过操作Entity来操作数据库,而这个merge就是update方法。我们可以看出,这model类是PortletpreferencesImpl类,我们现在找出它对应的数据库表。


不难发现,这个com.liferay.portal.model.impl.PortletPreferencesImpl类的基类是PortletPreferencesModelImpl类:

175918995.png


而从这里可以看出这个类其实对应的数据库表是PortletPreferences,而且从其中的表字段也发现,刚好这些信息是我们存储一个PortletPreferences所必须的。尤其看到,preferences的类型是CLOB,这也是数据库存储字符串的常见做法。


到现在,一切谜底都解开了,真是很有快感。


总结:

我们总结下:

(1)PortletPreferences这个对象,在被持久化之前会被转为xml字符串,然后对应到数据库字段的类型是CLOB(字符大对象)

(2)PortletPreferences的信息是由4个属性共同决定的,portlet preference id ,ownerId,ownerType,portlet layout id ,这就保证了不同的用户可以用自己的Preference喜好来存储自己的变量,而不会影响到其他人。因为不同用户userId是不同的。

(3)PortletPreferences如果要取出来,肯定是先从查询缓存中取,如果没有的话才会去读数据库。

(4)无论是存储还是查询PortletPreferences,其对应的要操作的表名称都是PortletPreferences.





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1281101,如需转载请自行联系原作者
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值