BLOB图像仅在通过PrimeFaces的p:dataTable进行更新后刷新页面时显示。

7

我将以以下方式在<p:graphicImage>上显示以BLOB形式存储在MySQL中的图像。

<p:dataTable var="row" value="#{testManagedBean}" lazy="true" editable="true" rows="10">
    <p:column headerText="id">
        <h:outputText value="#{row.brandId}"/>
    </p:column>

    <p:column headerText="Image">
        <p:cellEditor>
            <f:facet name="output">
                <p:graphicImage value="#{brandBean.image}" height="100" width="100">
                    <f:param name="id" value="#{row.brandId}"/>
                </p:graphicImage>
            </f:facet>
            <f:facet name="input">
                <p:graphicImage id="image" value="#{brandBean.image}" height="100" width="100">
                    <f:param name="id" value="#{row.brandId}"/>
                </p:graphicImage>
            </f:facet>
        </p:cellEditor>
    </p:column>

    <p:column headerText="Edit" width="50">
        <p:rowEditor/>
    </p:column>
</p:dataTable>

在编辑行时,<p:overlayPanel>上显示了一个<p:fileUpload>。为了简单起见,本例中省略了许多其他内容,因为它们与具体问题无关。

相关的JSF托管bean:

@ManagedBean
@ViewScoped
public final class TestManagedBean extends LazyDataModel<Brand> implements Serializable
{
    @EJB
    private final TestBeanLocal service=null;
    private static final long serialVersionUID = 1L;

    @Override
    public List<Brand> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
        setRowCount(3);
        return service.getList();
    }
}

BrandBean是一个从数据库中根据唯一行标识符检索图像的bean。

@ManagedBean
@ApplicationScoped
public final class BrandBean
{
    @EJB
    private final BrandBeanLocal service=null;
    public BrandBean() {}

    public StreamedContent getImage() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();

        if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
            return new DefaultStreamedContent();
        }
        else {
            String id = context.getExternalContext().getRequestParameterMap().get("id");
            System.out.println("id = "+id);
            byte[] bytes = service.findImageById(Long.parseLong(id));
            return bytes==null? new DefaultStreamedContent(new ByteArrayInputStream(new byte[0])):new DefaultStreamedContent(new ByteArrayInputStream(bytes));
        }
    }
}

当在数据表中点击最后一列位置上的打勾按钮(由<p:rowEditor>指示),编辑完成的行被更新后,应该会调用BrandBean中的getImage()方法。在运行GlassFish server 4.0、PrimeFaces 5.0和JSF 2.2.6的应用程序中,这样做是正确的。
在数据表中更新行(因此也更新了数据库)后,数据表中将立即显示新图像。
在Tomcat server 8.0.5中使用Spring 4.0.0 GA的另一个应用程序中,更新数据表中的行后getImage()方法未被调用,导致数据表中继续显示旧图像(而非新更新的图像)(尽管更改已正确传播到数据库)。
只有当按下大多数浏览器上的F5刷新页面时,才会显示新更新的图像。它甚至不会在页面加载时(输入URL到地址栏,然后按下Enter键)显示。
换句话说,当通过<p:rowEditor>点击数据表中的行时进行更新时,getImage()方法不会被调用(因此无法从数据库获取新图像以显示在<p:graphicImage>上)。只有按下F5快捷键刷新/重新加载页面时,该方法才会被调用。
为什么会这样?如何在行更新后立即显示新更新的图像?
表面上看,这既与Spring也与JPA无关(点击打勾后更新操作已正确传播到数据库)。而是与Tomcat服务器有关。

也许你应该在Tomcat邮件列表中询问此问题。 - Leo
2个回答

1
新更新的图片只有在按下F5(大多数浏览器)刷新页面时才会显示。甚至在页面加载时(输入URL到地址栏,然后按下回车键),它也不会显示。
该图片被Web浏览器缓存。资源是基于每个URL缓存的,通过在响应头中设置与缓存相关的指令。您具体的问题是由于资源URL仍然相同,而Web浏览器不知道服务器端已更改资源。OmniFaces CacheControlFilter showcase page详细解释了缓存(注意:过滤器不是解决此问题的方法)。
基本上,您需要通过更改URL来强制Web浏览器重新请求资源。这种情况下最常见的方法之一是将图像的“最后修改”时间戳附加到URL的查询字符串中,以便立即将其更改反映给所有客户端。鉴于您正在使用JPA,因此应该这样做:
  • brand表中添加lastModified列:

    ALTER TABLE brand ADD COLUMN lastModified TIMESTAMP DEFAULT now();
    
  • 使用适当的属性扩展 Brand实体,以及一个 @PreUpdate 方法来设置它:

    @Column @Temporal(TemporalType.TIMESTAMP)
    private Date lastModified;
    
    @PreUpdate
    public void onUpdate() {
        lastModified = new Date();
    }
    
    // +getter+setter
    

    (@PreUpdate注释的方法会在每个UPDATE查询之前由JPA调用)

  • 将其附加到图像URL(参数名v是“版本”的提示):

    <p:graphicImage value="#{brandBean.image}" ...>
        <f:param name="id" value="#{brand.id}" />
        <f:param name="v" value="#{brand.lastModified.time}" />
    </p:graphicImage>
    

    (我将这里的row重命名为brand以提高清晰度,并将brandId重命名为id以去重)

  • 最后,如果您正在使用PrimeFaces 5.0或更高版本,则还需要禁用服务器端缓存:

    <p:graphicImage value="#{brandBean.image}" cache="false" ...>
    
设计注意事项:如果图像不一定在每次更新Brand时都需要更新,则将其拆分到另一个表Image中,并让Brand有一个外键(@ManyToOne@OneToOne)指向Image。这也使得“image”属性可以在webapp中的各种实体中重复使用。

0

你好,

我有同样的问题,但我的图像源位于硬盘而不是数据库中。 当我使用随机数但没有读取它时,我已经解决了这个问题。 通常情况下,在图像的src中添加一个参数,当此参数为随机数时。

我的解决方案如下:

public class UserPage {
  private String fotoCVPath;
  //all variabile
    private String getRandomImageName() {
        int i = (int) (Math.random() * 10000000);

        return String.valueOf(i);
    }
 public void editImage() {
//init the fotoCVPath
fotoCVPath = fotoCVPath + "?tmpVal=" + getRandomImageName();
}
  /**
     * @return the fotoCVPath <br>
     * 
     * @author  - asghaier <br>
     * 
     *         Created on 29/mag/2014
     */
    public String getFotoCVPath() {

        return fotoCVPath;
    }

    /**
     * @param fotoCVPath the fotoCVPath to set <br>
     * 
     * @author- asghaier <br>
     * 
     *         Created on 29/mag/2014
     */
    public void setFotoCVPath(String fotoCVPath) {
        this.fotoCVPath = fotoCVPath;
    }
}

在我的页面中,我有一个名为graphicImage的元素,就像这样:

<h:panelGrid ...al param >
    <h:column>
       <p:graphicImage id="imgCV" value="#{userPage.fotoCVPath}" styleClass="imgCVStyle" />
    </h:column>
</h:panelGrid>

当我重新加载图像时,我更新组件panelGrid。

对于我的示例,我使用了

<p:ajax event="click" onstart="PF('uploadImgCV').show();" update ="idPanelGrid"/>

我希望这个模式能够帮助您解决问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接