我們對於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()方法的調用。
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S55933-0.png" title="1.png" />
從上面可以看出來,它首先通過PortletPreferencesFactoryUtil的toXML方法吧我們的PortletPreferences對象轉成一個xml字串,這是第一個亮點,原因很簡單,因為String是序列化的,方便儲存。
比如我們的例子中,我們的PortletPreferences被轉為下面的字串:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S51U9-1.png" title="2.png" />
緊接著,它會調用重載的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,我們細化看下:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S52363-2.png" title="3.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()方法
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S55461-3.png" title="4.png" />
而它會去建立一個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()方法
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S52619-4.png" title="5.png" />
而ClassLoaderSession的merge()方法通過動態代理會委託調用com.liferay.portal.dao.orm.hibernate.SessionImpl的merge()方法:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S55Z5-5.png" title="6.png" />
而它會接著委託到org.hibernate.Session的merge()方法,所以說,最終這個調用實際上是一個資料庫調用。
縱觀上文,讀者肯定有疑問,既然最終是個資料庫的調用,那麼操作的表是什麼呢?我們回到BatchSessionImpl.update()方法:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S54028-6.png" title="7.png" />
從上面調試可以看出,實際session.merge(model)本質上是因為Liferay的資料模型和資料庫做了一個O/R mappinig,然後通過操作Entity來操作資料庫,而這個merge就是update方法。我們可以看出,這model類是PortletpreferencesImpl類,我們現在找出它對應的資料庫表。
不難發現,這個com.liferay.portal.model.impl.PortletPreferencesImpl類的基類是PortletPreferencesModelImpl類:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/112S5B32-7.png" title="8.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.
本文出自 “平行線的凝聚” 部落格,請務必保留此出處http://supercharles888.blog.51cto.com/609344/1281101