转载Liferay PortletPreference store()方法研究
我们对于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()方法研究的更多相关文章
- [转]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 ...
- (手写识别) Zinnia库及其实现方法研究
Zinnia库及其实现方法研究 (转) zinnia是一个开源的手写识别库.采用C++实现.具有手写识别,学习以及文字模型数据制作转换等功能. 项目地址 [http://zinnia.sourcefo ...
- Properties集合_list方法与store方法
Properties集合和流对象结合的功能 list()方法: import java.util.Properties; public class PropertiesDemo { public st ...
- Android Service 通知Activity更新界面的方法研究
Android Service 通知Activity更新界面的方法研究 Android的最重要的组件式service和activity,那么在使用的过程中,我们最常遇到的问题是他们之间的通信问题. ...
- 基于MATLAB的多项式数据拟合方法研究-毕业论文
摘要:本论文先介绍了多项式数据拟合的相关背景,以及对整个课题做了一个完整的认识.接下来对拟合模型,多项式数学原理进行了详细的讲解,通过对文献的阅读以及自己的知识积累对原理有了一个系统的认识.介绍多项式 ...
- RapidIOIP核的验证方法研究_王玉欢
RapidIOIP核的验证方法研究_王玉欢 https://wenku.baidu.com/view/0fd3c925d4d8d15abf234e73.html
- 基于FPGA实现的高速串行交换模块实现方法研究
基于FPGA实现的高速串行交换模块实现方法研究 https://wenku.baidu.com/view/9a3d501a227916888486d7ed.html
- 《基于 UML 的教务系统设计方法研究》论文笔记(十五)
标题:基于 UML 的教务系统设计方法研究 时间:2009 来源:太原师范学院 关键词:UML:面向对象:建模:教务管理系统. 二.研究内容 UML 建模 UML 涵盖了面向对象的分析.设计和实现,融 ...
- 基于MIndSpore框架的道路场景语义分割方法研究
基于MIndSpore框架的道路场景语义分割方法研究 概述 本文以华为最新国产深度学习框架Mindspore为基础,将城市道路下的实况图片解析作为任务背景,以复杂城市道路进行高精度的语义分割为任务目标 ...
随机推荐
- HDU2717BFS
/* WA了12发简直不能忍! . 题意非常简单.从正整数a变为b有三种方法: +1,-1.*2 特殊情况一:a与b相等不须要搜索 特殊情况二:a>b时.结果必定是a-b不需搜 特殊情况三:比較 ...
- swift -- 计步器CMPedometer的使用
最近公司接了个项目,是一款运动类型的APP,可以检测运动量(例如:步数,上下楼等).睡眠信息.速度等信息,因为以前粗略的了解过传感器方面的相关信息,知道主要是苹果设备内置的传感器在起作用,传感器的种类 ...
- ios button标记
在写项目的时候,for循环创建多个button,在需要设置背景图片和,需要标记所选中的button的需求, 在这里提供两种方法: 一: 1:把for循环创建的button全部装到一个新建的数组中,把他 ...
- Python SQLAlchemy 模块
SQLAlchemy 简介: SQLAlchemy 是用于实现 ORM(Object Relational Mapping,对象关系映射)的一个模块,即把数据库的表结构映射到对象上在 Python 中 ...
- cocos2d-x游戏引擎核心之六——绘图原理和绘图技巧
一.OpenGL基础 游戏引擎是对底层绘图接口的包装,Cocos2d-x 也一样,它是对不同平台下 OpenGL 的包装.OpenGL 全称为 Open Graphics Library,是一个开放的 ...
- PyQt4重写事件处理方法
PyQt中的事件处理主要以来重写事件处理函数来实现. #!/usr/bin/python # -*- coding: utf-8 -*- import sys from PyQt4 import Qt ...
- 罗云彬win32汇编教程笔记 子函数的声明, 定义与调用
在主程序中用call指令来调用子程序. Win32汇编中的子程序也采用堆栈来传递参数,这样就可以用invoke伪指令来进行调用和语法检查工作. 一. 子程序的定义 子程序的定义方式如下所示. 子程序名 ...
- android基础---->摄像头与相册的调用
很多应用程序都可能会使用到调用摄像头拍照和从相册选取图片的功能,今天我们开始android中摄像头与相册调用的学习. 目录导航 调用摄像头拍照 从相册中选择照片 友情链接 调用摄像头拍照 大致流程:调 ...
- [HNOI2004] 打砖块
1292. [HNOI2004] 打砖块 ★★ 输入文件:brike.in 输出文件:brike.out 简单对比时间限制:1 s 内存限制:128 MB [题目描述] 在一个凹槽中放 ...
- shell 中的()【】{}(())
本文转自:https://blog.csdn.net/taiyang1987912/article/details/39551385 shell中各种括号的作用().(()).[].[[]].{} 一 ...