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

2023-11-25

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

<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>

While editing of a row, a <p:fileUpload> is displayed on a <p:overlayPanel>. This and many other things are omitted in this example for the sake of simplicity as they are not related to the concrete problem.

关联的 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();
    }
}

根据唯一行标识符从数据库检索图像的 bean -BrandBean.

@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>)在数据表的最后一列中,getImage()方法中的BrandBean被按其应有的方式调用。

在使用 PrimeFaces 5.0 和 JSF 2.2.6 的 GlassFish server 4.0 上运行的应用程序中,这种情况会正确发生。

在数据表(以及数据库)中更新行后,新图像将立即显示在数据表中。


还有另一个使用 Spring 4.0.0 GA 在 Tomcat 服务器 8.0.5 上运行的应用程序,其中the getImage()方法未被调用更新数据表保存的行后,仍然显示旧图像(不是新更新的)在数据表中(即使更改已正确传播到数据库)。

The newly updated image is displayed only when the page refreshed by pressing F5 (on most browsers). It is even not displayed on page load (entering a URL into the address bar and then pressing the enter key).

In other words, when a row in a data table is updated by clicking the tick indicated by <p:rowEditor>, the getImage() method is not invoked (hence, the new image is not fetched from the database to be displayed on <p:graphicImage>). This method is invoked only when the page is refreshed/reloaded by pressing the F5 shortcut key.


为什么会出现这种情况?如何在更新一行后立即显示新更新的图像?

表面上看,这应该与 Spring 或 JPA 无关(点击勾号后更新操作正确传播到数据库)。这应该与 Tomcat 服务器有关。


仅当按 F5(在大多数浏览器上)刷新页面时才会显示新更新的图像。它甚至不会在页面加载时显示(在地址栏中输入 URL,然后按 Enter 键)。

该图像正在被网络浏览器缓存。通过响应标头中设置的与缓存相关的指令,以每个 URL 为基础对资源进行缓存。您的具体问题是由于资源 URL 仍然相同而 Web 浏览器不知道资源在服务器端已更改而引起的。这OmniFacesCacheControlFilter展示页面详细解释了缓存(注意:过滤器不是这个问题的解决方案)。

您基本上需要通过更改 URL 来强制网络浏览器重新请求资源。对于这种情况(可缓存资源突然更改并且其更改需要立即反映到所有客户端),最常见的方法之一是将图像的“上次修改”时间戳附加到 URL 的查询字符串。鉴于您正在使用 JPA,因此应该这样做:

  • Add a lastModified列到brand table:

    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
    

    (a @PreUpdate带注释的方法由 JPA 在每个之前调用UPDATE query)

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

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

    (我在这里重命名row to brand为了清楚起见和brandId to id去重复)

  • 最后,如果您使用的是 PrimeFaces 5.0 或更高版本,那么您还需要禁用服务器端缓存:

    <p:graphicImage value="#{brandBean.image}" cache="false" ...>
    

设计注意事项:如果图像不一定在每次更新时更新Brand,然后将其拆分到另一个表Image然后让Brand有一个 FK (@ManyToOne or @OneToOne) to Image。这也使得属性“image”可以在 web 应用程序中的各个实体之间重用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

仅在 PrimeFaces 中通过 p:dataTable 进行更新后刷新页面时才会显示 BLOB 图像 的相关文章

随机推荐