【发布时间】:2014-12-15 18:07:02
【问题描述】:
JSF 页面包含银行帐户列表。
当用户点击账户ID时,会显示一个新页面,允许从账户中提取一些资金。这个新页面有一个 Account 类型的参数视图,带有一个转换器,用于将 id 转换为 Account。页面显示账户信息并询问提款金额。
当用户提交表单以提取一些钱时,新余额会在数据库中注册一个无状态的 EJB(带有合并)。
我想检测其他用户是否在同一时刻修改了同一个帐户,但这是不可能的,因为从来没有 OptimisticLockException。
解释:如果一开始版本号(@Version注解的属性)是5,如果有其他用户刚刚更改余额并立即提交表单,版本号就递增到6。我以为第一个用户会有一个 OptimisticLockException 但事实并非如此。事实上,当第一个用户提交表单更改余额时,表单在服务器上重新生成,并再次读取帐户,新版本号等于 6,并且在注册新余额时未检测到版本号更改在 EJB 中具有合并(后跟提交)的数据库。
因此,第一个用户不知道其他用户在处理帐户时更改了余额值。这可能是个问题。
如何检测并发更改?我是否应该在代码中保留版本号并与合并前的版本号进行比较(使用悲观锁!)?还有其他更好的方法吗?也许我必须改变我的页面结构?
我的代码(抱歉,是法语):
修改帐户(充值或提款)的 JSF 页面:
<ui:composition template="./template.xhtml">
<ui:param
name="soustitre"
value="Ajouter/retirer de l'argent sur le compte de
#{mouvementBean.compteBancaire.nom}"/>
<ui:define name="metadata">
<f:metadata>
<f:viewParam name='id' value='#{mouvementBean.compteBancaire}'
converter='#{modifierBean.converter}' />
</f:metadata>
</ui:define>
<ui:define name="content">
<f:phaseListener type="jsf.util.DebugPhaseListener"/>
<h1>Ajouter/enlever de l'argent sur le compte de
#{mouvementBean.compteBancaire.nom}</h1>
<h:form id="form">
<h3>Type mouvement :</h3>
<h:selectOneRadio
id='typeMouvement'
value='#{mouvementBean.typeMouvement}'
required='true'
layout='pageDirection'
requiredMessage="Vous devez dire s'il s'agit d'un ajout ou d'un retrait">
<f:selectItem itemValue="ajout"
itemLabel="Ajout"/>
<f:selectItem itemValue="retrait"
itemLabel="Retrait"/>
</h:selectOneRadio>
<h:message for="typeMouvement"/>
<h3>Montant de la sommme</h3>
<h:inputText
id="montant" value='#{mouvementBean.montant}' required='true'
requiredMessage="Le montant doit être un nombre entier positif"/>
<h:message for="montant"/>
<br/><br/>
<h:commandButton action="#{mouvementBean.sauvegarder()}"
value="Enregistrer"/>
</h:form>
</ui:define>
</ui:composition>
页面的支持bean:
Named(value = "mouvementBean")
@ViewScoped
public class MouvementBean implements Serializable {
@EJB
private GestionnaireDeCompteBancaire gestionnaireDeCompteBancaire;
private CompteBancaire compteBancaire;
private String typeMouvement;
@Min(value = 1, message = "Le montant doit être un entier positif")
private int montant;
public CompteBancaire getCompteBancaire() {
return compteBancaire;
}
public void setCompteBancaire(CompteBancaire compteBancaire) {
this.compteBancaire = compteBancaire;
}
public String getTypeMouvement() {
return typeMouvement;
}
public void setTypeMouvement(String typeMouvement) {
this.typeMouvement = typeMouvement;
}
public int getMontant() {
return montant;
}
public void setMontant(int montant) {
this.montant = montant;
}
public String sauvegarder() {
// Enregistre le mouvement dans l'entité
switch (typeMouvement) {
case "ajout":
compteBancaire.deposer(montant);
break;
case "retrait":
try {
compteBancaire.retirer(montant);
} catch (CompteException ex) {
Logger.getLogger(MouvementBean.class.getName()).log(Level.WARNING, null, ex);
Util.messageErreur("Solde insuffisant sur le compte",
"Solde insuffisant sur le compte de " + compteBancaire.getNom(),
"form:montant");
return null;
}
break;
}
// Enregistre dans la base de données
gestionnaireDeCompteBancaire.modifierCompte(compteBancaire);
// Message de succès
Util.addFlashInfoMessage(typeMouvement + " de " + montant + " effectué pour "
+ compteBancaire.getNom());
return "listeComptes?faces-redirect=true";
}
}
转换器(在另一个支持 bean ModifierBean 中):
public Converter getConverter() {
return new Converter() {
@Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
// Pour faire des tests pour la concurrence
CompteBancaire c =
gestionnaireDeCompteBancaire.getById(Long.parseLong(value));
System.out.println("Dans le convertisseur à la fin de getAsObject " + c);
return c;
// return gestionnaireDeCompteBancaire.getById(Long.parseLong(value));
}
@Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((CompteBancaire)value).getId().toString();
}
};
}
指向所有帐户列表中页面的链接:
<p:dataTable value="#{listeManagedBean.comptes}" var="item"
paginator="true" rows="5"
rowsPerPageTemplate="2,5,10,20">
<p:column filterBy="#{item.id}" filterMatchMode="exact"
sortBy="#{item.id}"
style="width: 5%; text-align: center;">
<f:facet name="header">
<h:outputText value="Id"/>
</f:facet>
<h:link outcome="mouvement" value="#{item.id}">
<f:param name="id" value="#{item.id}"/>
</h:link>
</p:column>
【问题讨论】:
-
请提供更多细节。您使用什么 JPA 提供程序?您如何以及在何处实施乐观锁定?
-
我不强制乐观锁定;合并完成时,JPA 具有默认的乐观锁定。如果不会为第一个用户再次读取实体,则版本号将与数据库的版本号不同(因为另一个用户进行了更新)并且合并将抛出 OptimisticLockException。
-
默认情况下 Eclipselink 不强制乐观锁定。相反,它假设应用程序本身管理并发问题。所以你最终没有锁定策略。正如Understanding Eclipselink 中所说,“默认情况下,EclipseLink 持久性提供程序假定应用程序负责数据一致性”
标签: jsf jakarta-ee concurrency