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

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

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

详细分析:

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

@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()方法的调用。

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

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

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

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,我们细化看下:

不难看出,这个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()方法

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

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

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()方法

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

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

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

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

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

而从这里可以看出这个类其实对应的数据库表是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.

转载出处:http://supercharles888.blog.51cto.com/609344/1281101?utm_source=tuicool&utm_medium=referral

转载Liferay PortletPreference store()方法研究的更多相关文章

  1. [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)

     [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching) http://www.360doc.com/content/12/0428/17/6187784 ...

  2. (手写识别) Zinnia库及其实现方法研究

    Zinnia库及其实现方法研究 (转) zinnia是一个开源的手写识别库.采用C++实现.具有手写识别,学习以及文字模型数据制作转换等功能. 项目地址 [http://zinnia.sourcefo ...

  3. Properties集合_list方法与store方法

    Properties集合和流对象结合的功能 list()方法: import java.util.Properties; public class PropertiesDemo { public st ...

  4. Android Service 通知Activity更新界面的方法研究

    Android Service 通知Activity更新界面的方法研究   Android的最重要的组件式service和activity,那么在使用的过程中,我们最常遇到的问题是他们之间的通信问题. ...

  5. 基于MATLAB的多项式数据拟合方法研究-毕业论文

    摘要:本论文先介绍了多项式数据拟合的相关背景,以及对整个课题做了一个完整的认识.接下来对拟合模型,多项式数学原理进行了详细的讲解,通过对文献的阅读以及自己的知识积累对原理有了一个系统的认识.介绍多项式 ...

  6. RapidIOIP核的验证方法研究_王玉欢

    RapidIOIP核的验证方法研究_王玉欢 https://wenku.baidu.com/view/0fd3c925d4d8d15abf234e73.html

  7. 基于FPGA实现的高速串行交换模块实现方法研究

    基于FPGA实现的高速串行交换模块实现方法研究 https://wenku.baidu.com/view/9a3d501a227916888486d7ed.html

  8. 《基于 UML 的教务系统设计方法研究》论文笔记(十五)

    标题:基于 UML 的教务系统设计方法研究 时间:2009 来源:太原师范学院 关键词:UML:面向对象:建模:教务管理系统. 二.研究内容 UML 建模 UML 涵盖了面向对象的分析.设计和实现,融 ...

  9. 基于MIndSpore框架的道路场景语义分割方法研究

    基于MIndSpore框架的道路场景语义分割方法研究 概述 本文以华为最新国产深度学习框架Mindspore为基础,将城市道路下的实况图片解析作为任务背景,以复杂城市道路进行高精度的语义分割为任务目标 ...

随机推荐

  1. wm_concat函数 用法

    首先让我们来看看这个神奇的函数wm_concat(列名),该函数可以把列值以","号分隔起来,并显示成一行,接下来上例子,看看这个神奇的函数如何应用 准备测试数据 SQL>  ...

  2. swift - 利用UIDatePicker实现定时器的效果

    效果图如下: 可以通过UIDatePicker调整倒计时的时间,然后点击UIButton开始倒计时,使用NSTimer进行倒计时的时间展示,我是声明了一个label也进行了标记, 然后点击按钮开始倒计 ...

  3. cocos2d-x游戏引擎核心之三——主循环和定时器

    一.游戏主循环 在介绍游戏基本概念的时候,我们曾介绍了场景.层.精灵等游戏元素,但我们却故意避开了另一个同样重要的概念,那就是游戏主循环,这是因为 Cocos2d 已经为我们隐藏了游戏主循环的实现.读 ...

  4. PyQt4消息窗口

    默认情况下,如果我们单击了窗口标题栏上的X标记,窗口就会被关闭.但是有些时候我们想要改变这一默认行为.比如,我们正在编辑的文件内容发生了变化,这时若单击X标记关闭窗口,编辑器就应当但出确认窗口. #! ...

  5. Django学习笔记 Django的工程目录

    mysite├── manage.py 管理项目:包括数据库建立.服务器运行.测试……└── mysite    ├── __init__.py     ├── settings.py 配置文件:应用 ...

  6. Delphi 单元

    单元(unit)是组成Pascal程序的单独的源代码模块,单元有函数和过程组成,这些函数和过程能被主程序调用.一个单元至少要有unit语句,interface,和implementation三部分,也 ...

  7. maven setting详细解读

    全局配置: ${M2_HOME}/conf/settings.xml 用户配置: ${user.home}/.m2/settings.xml note:用户配置优先于全局配置.${user.home} ...

  8. Thinkphp --- 去掉index.php

    这里我使用的面板是宝塔,操作的 apche: 具体的配置可以参考这里: https://www.cnblogs.com/fangziffff123/p/7588782.html 首先是:Thinkph ...

  9. 微信小程序 --- app.json文件

    app.json文件用于配置项目:用于对小程序进行全局设置: pages:定义小程序的路由.(凡是不在这个配置里面的东西,都无法打开) (特别注意:结尾不能有 逗号 否则会出错) window:定义小 ...

  10. AIX系统崩溃后oracle数据库的恢复方法

    首先要确保数据库实例的完整性,包括控制文件,日志文件,表空间(系统表空间.用户表空间等). 新建同名数据库实例(表空间.用户等不需要). Sql>shutdown immediate关闭数据库 ...