我正在如下显示在MySQL中以BLOB形式存储的图像<p:graphicImage>。
<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>
在编辑行时,a <p:fileUpload>显示在上<p:overlayPanel>。为了简单起见,在本示例中省略了这些以及许多其他内容,因为它们与具体问题无关。
<p:fileUpload>
<p:overlayPanel>
关联的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。
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会按需调用中的方法。
<p:rowEditor>
getImage()
这在使用PrimeFaces 5.0和JSF 2.2.6在GlassFish服务器4.0上运行的应用程序中正确发生。
在数据表(因此在数据库中)更新一行后,新图像将立即显示在数据表中。
在使用Spring 4.0.0 GA的Tomcat服务器8.0.5上运行的另一个应用程序中,在getImage()更新了数据表所保存的行之后,该 方法仍未调用 ,导致仍然在其中显示 旧图像 (而不是新近更新的 图像 )。数据表(即使更改已正确传播到数据库)。
仅在通过按F5(在大多数浏览器中)刷新页面时才显示新更新的图像。甚至在页面加载时也不显示(在地址栏中输入URL,然后enter按键)。
F5
enter
换句话说,当通过单击所指示的对勾来更新数据表中的一行时<p:rowEditor>,getImage()不会调用该方法(因此,不会从数据库中获取新图像以在上显示<p:graphicImage>)。仅在通过F5按快捷键刷新/重新加载页面时才调用此方法。
为什么会这样?如何在更新行后立即显示新更新的图像?
从表面上看,这既不应与Spring无关,也不应与JPA无关(单击勾号后,更新操作将正确传播到数据库)。这应该与Tomcat服务器有关。
仅在通过按F5刷新页面时才显示新更新的图像(在大多数浏览器上)。 它甚至不会在页面加载时显示(在地址栏中输入URL,然后按Enter键)。
该图像正由Web浏览器缓存。资源通过响应标头中设置的与缓存有关的指令按URL进行缓存。造成您的具体问题的原因是,资源URL仍然相同,并且Web浏览器不知道服务器端的资源已更改。该OmniFaces CacheControlFilter展示页解释了缓存详细(注:过滤器是不是解决这个问题)。
CacheControlFilter
基本上,您需要通过更改URL来强制Web浏览器重新请求资源。在这种情况下,可缓存资源突然更改并且需要立即将其更改反映给所有客户端的一种最常见方法是将图像的“上次修改时间”时间戳附加到URL的查询字符串中。鉴于您正在使用JPA,因此应该这样做:
lastModified在brand表中添加一列:
lastModified
brand
ALTER TABLE brand ADD COLUMN lastModified TIMESTAMP DEFAULT now();
Brand使用适当的属性扩展实体,并通过对其@PreUpdate进行设置:
Brand
@PreUpdate
@Column @Temporal(TemporalType.TIMESTAMP) private Date lastModified; @PreUpdate public void onUpdate() { lastModified = new Date(); } // +getter+setter
(@PreUpdate在每次UPDATE查询之前,JPA都会调用带注释的方法)
UPDATE
v
<p:graphicImage value="#{brandBean.image}" ...> <f:param name="id" value="#{row.brandId}" /> <f:param name="v" value="#{row.lastModified.time}" /> </p:graphicImage>
(我将在这里重新命名row,以brand清楚和brandId到id到重复数据删除)
row
brandId
id
<p:graphicImage value="#{brandBean.image}" cache="false" ...>
设计注意事项:如果图像不一定在每次更新时都进行更新Brand,则将其拆分到另一个表中Image,Brand并用FK(@ManyToOne或@OneToOne)进行更改Image。这也使属性“图像”可在Web应用程序的各个实体之间重复使用。
Image
@ManyToOne
@OneToOne