结论:
当在Liferay中用管理员登录,导航到控制面板->Documents and Media ,在指定文件夹下添加BasicDocument时,服务器做了如下的事情:
(1) 在DLFILEENTRY表中添加一条记录代表被添加的文档。
(2) 在DLFILEENTRYVERSION表中添加一条记录,通过fileEntryId外键关联到DLFILEENTRY表,用来记录刚被添加文档的版本信息。
(3) 在DLFOLDER表中更新最新post提交的时间戳。
(4) 根据服务器对于com.liferay.portlet.documentlibrary.store的具体实现的不同,先对文件进行病毒扫描(在portal.properties 中有开关),然后把上传的资源文件存入到Store的某个具体位置。
Store有5种实现,我至少可以保证,如果用的是FileSystemStore,那么这个资源文件被放入$liferay_home/data/document_library目录,并且最终文件名不再是上传的文件名,而是<version_number>.如果使用的是DBStore,那么这个资源文件最终会被放在数据库中DLCONTENT表中,并且资源文件(比如图片)以BLOB的形式存储。
(5)在ASSETENTRY表中添加一条记录,它通过classpk外键关联到DLFILEENTRY,因为File也是一种资产,所以必须在这张表中也留下记录。
具体分析:
Liferay控制面板中,当创建了文件夹,然后要在其中添加某个Basic Document:
查看浏览器debug信息:
可以发现,它调用的struts_action为/document_library/edit_file_entry
我们去struts-config.xml中去找:
- <action path="/document_library/edit_file_entry" type="com.liferay.portlet.documentlibrary.action.EditFileEntryAction">
- <forward name="portlet.document_library.edit_file_entry" path="portlet.document_library.edit_file_entry" />
- <forward name="portlet.document_library.error" path="portlet.document_library.error" />
- </action>
可以发现, 它会forward到portlet.document_library.edit_file_entry的路径名下找页面:
我们去tiles-def.xml中找匹配:
- <definition name="portlet.document_library.edit_file_entry" extends="portlet.document_library">
- <put name="portlet_content" value="/portlet/document_library/edit_file_entry.jsp" />
- </definition>
所以,它最终会访问/portlet/document_library/edit_file_entry.jsp页面:
这个页面会吧你刚才选择的目录列出来,比如我们刚才选择了abcde目录,那么在这里,它就会显示出来:
对应的显示代码是
- <aui:field-wrapper label="folder">
- <aui:a href="<%= viewFolderURL %>" id="folderName"><%= folderName %></aui:a>
- <c:if test="<%= referringPortletResourceRootPortletId.equals(PortletKeys.ASSET_PUBLISHER) %>">
- <aui:button name="openFolderSelectorButton" onClick='<%= renderResponse.getNamespace() + "openFolderSelector();" %>' value="select" />
- <%
- String taglibRemoveFolder = "Liferay.Util.removeFolderSelection('folderId', 'folderName', '" + renderResponse.getNamespace() + "');";
- %>
- <aui:button disabled="<%= folderId <= 0 %>" name="removeFolderButton" onClick="<%= taglibRemoveFolder %>" value="remove" />
- </c:if>
- </aui:field-wrapper>
然后,它会有一个文件上传框:
- <aui:input name="file" type="file">
- <aui:validator name="acceptFiles">
- '<%= StringUtil.merge(PrefsPropsUtil.getStringArray(PropsKeys.DL_FILE_EXTENSIONS, StringPool.COMMA)) %>'
- </aui:validator>
- </aui:input>
它会通过aui框架的校验器来校验被上传的文件扩展名和大小。
输入文件标题和正文的部分我就略过了,不是重点,我们现在想关注的是,到底当点击最下方的"Publish"按钮时发生了什么。
深入分析点击"Publish"按钮后发生的事情
对应的代码是:
- <aui:button disabled="<%= checkedOut && !hasLock || (pending && PropsValues.DL_FILE_ENTRY_DRAFTS_ENABLED) %>" name="publishButton" type="submit" value="<%= publishButtonLabel %>" />
它会吧整个表单提交,对应代码是:
- <aui:form action="<%= editFileEntryURL %>" cssClass="lfr-dynamic-form" enctype="multipart/form-data" method="post" name="fm" onSubmit='<%= "event.preventDefault(); " + renderResponse.getNamespace() + "saveFileEntry(false);" %>'>
- <aui:input name="<%= Constants.CMD %>" type="hidden" />
- <aui:input name="redirect" type="hidden" value="<%= redirect %>" />
- <aui:input name="backURL" type="hidden" value="<%= backURL %>" />
- <aui:input name="referringPortletResource" type="hidden" value="<%= referringPortletResource %>" />
- <aui:input name="uploadProgressId" type="hidden" value="<%= uploadProgressId %>" />
- <aui:input name="repositoryId" type="hidden" value="<%= repositoryId %>" />
- <aui:input name="folderId" type="hidden" value="<%= folderId %>" />
- <aui:input name="fileEntryId" type="hidden" value="<%= fileEntryId %>" />
- <aui:input name="workflowAction" type="hidden" value="<%= WorkflowConstants.ACTION_PUBLISH %>" />
- ..
而因为用post提交,所以这些信息都在payload中:
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_formDate"
- 1341536588996
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_cmd"
- add
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_redirect"
- http://localhost:8080/group/control_panel/manage?p_p_id=20&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&doAsGroupId=19&refererPlid=12655&_20_refererPlid=12655&_20_doAsGroupId=19&_20_struts_action=%2Fdocument_library%2Fview&_20_folderId=16904
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_backURL"
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_referringPortletResource"
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_uploadProgressId"
- dlFileEntryUploadProgress
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_repositoryId"
- 19
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_folderId"
- 16904
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_fileEntryId"
- 0
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_workflowAction"
- 1
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_file"; filename="charles_wang.jpg"
- Content-Type: image/jpeg
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_title"
- title of new document
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_description"
- a new document which contains a image and resides in abcde folder
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_fileEntryTypeId"
- 0
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_assetTagNames"
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_assetLinkSearchContainerPrimaryKeys"
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_assetLinkEntryIds"
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_inputPermissionsShowOptions"
- false
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_inputPermissionsViewRole"
- Guest
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_guestPermissions"
- ADD_DISCUSSION
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_guestPermissions"
- VIEW
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_groupPermissions"
- ADD_DISCUSSION
- ------WebKitFormBoundaryHrjra277wXTqeY5D
- Content-Disposition: form-data; name="_20_groupPermissions"
- VIEW
- ------WebKitFormBoundaryHrjra277wXTqeY5D--
所以,action还是自身页面,不难在struts-config.xml中找到Action类名为EditFileEntryAction:
因为我们的cmd是add,所以它会走以下流程:
- public void processAction(
- ActionMapping mapping, ActionForm form, PortletConfig portletConfig,
- ActionRequest actionRequest, ActionResponse actionResponse)
- throws Exception {
- String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
- ..
- else if (cmd.equals(Constants.ADD) || cmd.equals(Constants.UPDATE)
- || cmd.equals(Constants.UPDATE_AND_CHECKIN)) {
- updateFileEntry(portletConfig, actionRequest, actionResponse);
- }
- ..
它会去调用updateFileEntry方法,这就是我们研究的重心:
- protected void updateFileEntry(
- PortletConfig portletConfig, ActionRequest actionRequest,
- ActionResponse actionResponse)
- throws Exception {
- UploadPortletRequest uploadPortletRequest =
- PortalUtil.getUploadPortletRequest(actionRequest);
- ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
- WebKeys.THEME_DISPLAY);
- String cmd = ParamUtil.getString(uploadPortletRequest, Constants.CMD);
- long fileEntryId = ParamUtil.getLong(
- uploadPortletRequest, "fileEntryId");
- long repositoryId = ParamUtil.getLong(
- uploadPortletRequest, "repositoryId");
- long folderId = ParamUtil.getLong(uploadPortletRequest, "folderId");
- String sourceFileName = uploadPortletRequest.getFileName("file");
- String title = ParamUtil.getString(uploadPortletRequest, "title");
- String description = ParamUtil.getString(
- uploadPortletRequest, "description");
- String changeLog = ParamUtil.getString(
- uploadPortletRequest, "changeLog");
- boolean majorVersion = ParamUtil.getBoolean(
- uploadPortletRequest, "majorVersion");
- if (folderId > 0) {
- Folder folder = DLAppServiceUtil.getFolder(folderId);
- if (folder.getGroupId() != themeDisplay.getScopeGroupId()) {
- throw new NoSuchFolderException();
- }
- }
- InputStream inputStream = null;
- try {
- String contentType = uploadPortletRequest.getContentType("file");
- long size = uploadPortletRequest.getSize("file");
- if (cmd.equals(Constants.ADD) && (size == 0)) {
- contentType = MimeTypesUtil.getContentType(title);
- }
- if (cmd.equals(Constants.ADD) || (size > 0)) {
- String portletName = portletConfig.getPortletName();
- if (portletName.equals(PortletKeys.MEDIA_GALLERY_DISPLAY)) {
- String portletResource = ParamUtil.getString(
- actionRequest, "portletResource");
- PortletPreferences portletPreferences = null;
- if (Validator.isNotNull(portletResource)) {
- PortletPreferencesFactoryUtil.getPortletSetup(
- actionRequest, portletResource);
- }
- else {
- portletPreferences = actionRequest.getPreferences();
- }
- String[] mimeTypes = DLUtil.getMediaGalleryMimeTypes(
- portletPreferences, actionRequest);
- if (Arrays.binarySearch(mimeTypes, contentType) < 0) {
- throw new FileMimeTypeException(contentType);
- }
- }
- }
- inputStream = uploadPortletRequest.getFileAsStream("file");
- ServiceContext serviceContext = ServiceContextFactory.getInstance(
- DLFileEntry.class.getName(), actionRequest);
- FileEntry fileEntry = null;
- if (cmd.equals(Constants.ADD)) {
- if (Validator.isNull(title)) {
- title = sourceFileName;
- }
- // Add file entry
- fileEntry = DLAppServiceUtil.addFileEntry(
- repositoryId, folderId, sourceFileName, contentType, title,
- description, changeLog, inputStream, size, serviceContext);
- AssetPublisherUtil.addAndStoreSelection(
- actionRequest, DLFileEntry.class.getName(),
- fileEntry.getFileEntryId(), -1);
- }
- else if (cmd.equals(Constants.UPDATE_AND_CHECKIN)) {
- // Update file entry and checkin
- fileEntry = DLAppServiceUtil.updateFileEntryAndCheckIn(
- fileEntryId, sourceFileName, contentType, title,
- description, changeLog, majorVersion, inputStream,
- size, serviceContext);
- }
- else {
- // Update file entry
- fileEntry = DLAppServiceUtil.updateFileEntry(
- fileEntryId, sourceFileName, contentType, title,
- description, changeLog, majorVersion, inputStream,
- size, serviceContext);
- }
- AssetPublisherUtil.addRecentFolderId(
- actionRequest, DLFileEntry.class.getName(), folderId);
- }
- finally {
- StreamUtil.cleanUp(inputStream);
- }
- }
从第06-27行就是从post的请求的payload中获取一些参数信息,然后第29行对folderId进行判断,因为我们的folderId为19604,它大于0,所以第30行用DLAppServiceUtil的方法获取这个folder的实例,它最终会调用DLFolderLocalServiceImpl的getFolder(folderId)方法:
- public DLFolder getFolder(long folderId)
- throws PortalException, SystemException {
- return dlFolderPersistence.findByPrimaryKey(folderId);
- }
这会发起一个数据库的查询,然后把查出来的结果封装成DLFolder对象:
从这里看出来,这个folder就是我们昨天创建的名字叫"abcde"的folder.
然后从40行开始,正式对于这个文档中的文件进行操作。先从40-72行做一些信息提取工作,然后从74行开始正式上传文件,因为我们的cmd是“add",所以它会执行86-95行,我们详细分析:
添加FileEntry:
第88-90行最终会调用DLAppServiceImpl类的addFileEntry方法:
- public FileEntry addFileEntry(
- long repositoryId, long folderId, String sourceFileName,
- String mimeType, String title, String description, String changeLog,
- InputStream is, long size, ServiceContext serviceContext)
- throws PortalException, SystemException {
- if (is == null) {
- is = new UnsyncByteArrayInputStream(new byte[0]);
- size = 0;
- }
- Repository repository = getRepository(repositoryId);
- FileEntry fileEntry = repository.addFileEntry(
- folderId, sourceFileName, mimeType, title, description, changeLog,
- is, size, serviceContext);
- dlAppHelperLocalService.addFileEntry(
- getUserId(), fileEntry, fileEntry.getFileVersion(), serviceContext);
- return fileEntry;
- }
它一共做了2件事情:
事情1:
第14行-15行,它最终会调用DLFileEntryLocalServiceImpl类的addFileEntry方法:
- public DLFileEntry addFileEntry(
- long userId, long groupId, long repositoryId, long folderId,
- String sourceFileName, String mimeType, String title,
- String description, String changeLog, long fileEntryTypeId,
- Map<String, Fields> fieldsMap, File file, InputStream is, long size,
- ServiceContext serviceContext)
- throws PortalException, SystemException {
- if ((size == 0) && Validator.isNull(title)) {
- throw new FileNameException();
- }
- // File entry
- User user = userPersistence.findByPrimaryKey(userId);
- folderId = dlFolderLocalService.getFolderId(
- user.getCompanyId(), folderId);
- String name = String.valueOf(
- counterLocalService.increment(DLFileEntry.class.getName()));
- String extension = getExtension(title, sourceFileName);
- fileEntryTypeId = getFileEntryTypeId(
- DLUtil.getGroupIds(groupId), folderId, fileEntryTypeId);
- Date now = new Date();
- validateFile(groupId, folderId, title, extension, file, is);
- long fileEntryId = counterLocalService.increment();
- DLFileEntry dlFileEntry = dlFileEntryPersistence.create(fileEntryId);
- dlFileEntry.setUuid(serviceContext.getUuid());
- dlFileEntry.setGroupId(groupId);
- dlFileEntry.setCompanyId(user.getCompanyId());
- dlFileEntry.setUserId(user.getUserId());
- dlFileEntry.setUserName(user.getFullName());
- dlFileEntry.setVersionUserId(user.getUserId());
- dlFileEntry.setVersionUserName(user.getFullName());
- dlFileEntry.setCreateDate(serviceContext.getCreateDate(now));
- dlFileEntry.setModifiedDate(serviceContext.getModifiedDate(now));
- dlFileEntry.setRepositoryId(repositoryId);
- dlFileEntry.setFolderId(folderId);
- dlFileEntry.setName(name);
- dlFileEntry.setExtension(extension);
- dlFileEntry.setMimeType(mimeType);
- dlFileEntry.setTitle(title);
- dlFileEntry.setDescription(description);
- dlFileEntry.setFileEntryTypeId(fileEntryTypeId);
- dlFileEntry.setVersion(DLFileEntryConstants.VERSION_DEFAULT);
- dlFileEntry.setSize(size);
- dlFileEntry.setReadCount(DLFileEntryConstants.DEFAULT_READ_COUNT);
- dlFileEntryPersistence.update(dlFileEntry, false);
- // File version
- DLFileVersion dlFileVersion = addFileVersion(
- user, dlFileEntry, serviceContext.getModifiedDate(now), extension,
- mimeType, title, description, null, StringPool.BLANK,
- fileEntryTypeId, fieldsMap, DLFileEntryConstants.VERSION_DEFAULT,
- size, WorkflowConstants.STATUS_DRAFT, serviceContext);
- dlFileEntry.setFileVersion(dlFileVersion);
- // Folder
- if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
- dlFolderLocalService.updateLastPostDate(
- dlFileEntry.getFolderId(), dlFileEntry.getModifiedDate());
- }
- // File
- if (file != null) {
- DLStoreUtil.addFile(
- user.getCompanyId(), dlFileEntry.getDataRepositoryId(), name,
- false, file);
- }
- else {
- DLStoreUtil.addFile(
- user.getCompanyId(), dlFileEntry.getDataRepositoryId(), name,
- false, is);
- }
- return dlFileEntry;
- }
所以,总结来说就是(13-52行)在DLFILEENTRY表中添加一条记录,代表了我们刚创建的文件:
然后(54-62行)在DLFILEVERSION表中添加一条记录:
然后(64-69行)在DLFOLDER表中更新最新post提交的时间戳:
然后(73-82行)会添加这个我们被上传的文件(比如 charles_wang.jpg), 如何添加呢?可以一直跟进直到DLStoreImpl类的addFile方法:
- public void addFile(
- long companyId, long repositoryId, String fileName,
- boolean validateFileExtension, File file)
- throws PortalException, SystemException {
- validate(fileName, validateFileExtension, file);
- if (PropsValues.DL_STORE_ANTIVIRUS_ENABLED) {
- AntivirusScannerUtil.scan(file);
- }
- store.addFile(companyId, repositoryId, fileName, file);
- }
它首先会判断是否开启了store的病毒扫描机制,这个变量定义在portal.properties文件中:
- # Set this property to true to enable execution of antivirus check when
- # files are submitted into a store. Setting this value to true will prevent
- # any potential virus files from entering the store but will not allow for
- # file quarantines.
- #
- dl.store.antivirus.enabled=false
然后调用store的addFile方法,它会具体根据我们Store的实现来决定如何添加这个资源文件。
那么我们的服务器上用的是什么样的store呢?可以参见portal.properties中的定义:
- #
- # Set the name of a class that implements
- # com.liferay.portlet.documentlibrary.store.Store. The
- # document library server will use this to persist documents.
- #
- #dl.store.impl=com.liferay.portlet.documentlibrary.store.AdvancedFileSystemStore
- #dl.store.impl=com.liferay.portlet.documentlibrary.store.CMISStore
- #dl.store.impl=com.liferay.portlet.documentlibrary.store.DBStore
- dl.store.impl=com.liferay.portlet.documentlibrary.store.FileSystemStore
- #dl.store.impl=com.liferay.portlet.documentlibrary.store.JCRStore
- #dl.store.impl=com.liferay.portlet.documentlibrary.store.S3Store
所以,我们会用FileSystemStore作为Store的实现,这也是默认的实现。
- public void addFile(
- long companyId, long repositoryId, String fileName, InputStream is)
- throws PortalException, SystemException {
- try {
- File fileNameVersionFile = getFileNameVersionFile(
- companyId, repositoryId, fileName, VERSION_DEFAULT);
- if (fileNameVersionFile.exists()) {
- throw new DuplicateFileException(fileNameVersionFile.getPath());
- }
- FileUtil.write(fileNameVersionFile, is);
- }
- catch (IOException ioe) {
- throw new SystemException(ioe);
- }
- }
从调试信息上来看,它会先去获取companyId,repositoryId,fileName等信息,最终File对象是服务器节点上一个带版本号的url:
而被上传的文件也被盖头换面了,居然在$tomcat_home/temp目录下,而且名字叫upload_000010.jpg,我们在这个目录下找,是我们所要上传的文件:
所以,最终这个文件被保存到了D:\Liferay_Cluster_Enterprise\Node1\liferay-portal-tomcat-6.1.10-ee-ga1\liferay-portal-6.1.10-ee-ga1\data\document_library\1\16904\501 目录下,并且文件名不再是charles_wang.png,而是就叫<version_number>.并且后面不带扩展名了。我们比较这charles_wang.png和这个1.0文件的大小,发现他们是一致的(都是153kb),所以这说明文件已经被复制到了Liferay节点上。
进一步研究,如果不使用FileSystemStore会如何。我们在portal-ext.properties中把 dl.store.impl改为DBStore:
- dl.store.impl=com.liferay.portlet.documentlibrary.store.DBStore
它会去调用DBStore类的addFile代码,最终会调用DBStore类的updateFile代码:
- public void updateFile(
- long companyId, long repositoryId, String fileName,
- String versionLabel, File file)
- throws PortalException, SystemException {
- if (DLContentLocalServiceUtil.hasContent(
- companyId, repositoryId, fileName, versionLabel)) {
- throw new DuplicateFileException(fileName);
- }
- InputStream inputStream = null;
- try {
- inputStream = new FileInputStream(file);
- }
- catch (FileNotFoundException fnfe) {
- throw new SystemException(fnfe);
- }
- DLContentLocalServiceUtil.addContent(
- companyId, repositoryId, fileName, versionLabel, inputStream,
- file.length());
- }
它最终会调用DLContentLocalServiceImpl的addContent方法:
- public DLContent addContent(
- long companyId, long repositoryId, String path, String version,
- InputStream inputStream, long size)
- throws SystemException {
- try {
- long contentId = counterLocalService.increment();
- DLContent dlContent = dlContentPersistence.create(contentId);
- dlContent.setCompanyId(companyId);
- dlContent.setRepositoryId(repositoryId);
- dlContent.setPath(path);
- dlContent.setVersion(version);
- OutputBlob dataOutputBlob = new OutputBlob(inputStream, size);
- dlContent.setData(dataOutputBlob);
- dlContent.setSize(size);
- dlContentPersistence.update(dlContent, false);
- return dlContent;
- }
- finally {
- StreamUtil.cleanUp(inputStream);
- }
- }
从这里我们可以很清楚的看到,它会吧这个文件的内容以Blob的形式保存,然后连同companyId,repositoryId,version等信息一起写在DLCONTENT数据库表中:
我们复制上述动作时候,不幸发生了如下的异常:
- Caused by: java.lang.ClassCastException: com.liferay.portal.kernel.dao.jdbc.OutputBlob cannot be cast to oracle.sql.BLOB
- at oracle.jdbc.driver.OraclePreparedStatement.setBlob(OraclePreparedStatement.java:6663)
- at oracle.jdbc.driver.OraclePreparedStatementWrapper.setBlob(OraclePreparedStatementWrapper.java:128)
从http://issues.liferay.com/browse/LPS-26375上我找到了解决方法,就是修改portal-hbm.xml文件,然后把类型配置正确就可以了:
- <class name="com.liferay.portlet.documentlibrary.model.DLContentDataBlobModel" table="DLContent" lazy="true">
- <id name="contentId" column="contentId">
- <generator class="foreign">
- <param name="property">com.liferay.portlet.documentlibrary.model.impl.DLContentImpl</param>
- </generator>
- </id>
- <property column="data_" name="dataBlob" type="org.hibernate.type.BlobType" />
- </class>
把这里的dataBlob的type改为java.sql.Blob就可以了。
为此,我们新建一个文件叫portal-hbm-new.xml,让其复制portal-hbm.xml的所有内容,除了dataBlob的type改为java.sql.Blob。
然后我们在portal-ext.properties中添加如下行,让hibernate映射文件指向我们新建的这个文件:
- #added by charles to fix the dbStore problem ,use the new portal hibernate mapping file
- hibernate.configs=\
- META-INF/mail-hbm.xml,\
- META-INF/portal-hbm-new.xml,\
- META-INF/ext-hbm.xml
这样,我们就上传成功了:
我们检查数据库,在DLCONTENT表中果然找到了blob形式存在的图片:
事情2:
第18行-19行,它最终会调用DLAppHelperLocalServiceImpl的addFileEntry方法,具体不展开了,结论就是,它会在AssetEntry数据库表中添加一条记录:
到此,我们全部分析完了。