问题 在持久化实体之前检查约束违规


在创建之前防止约束违规检查的最佳机制是什么?修改实体?

假设'User'实体将'loginid'作为唯一约束,在创建或修改之前检查是否已存在具有此loginid名称的用户条目是明智的。

要么

您是否允许数据库抛出ConstraintViolationException并在UI层中适当地处理此消息。应该在jboss seam框架中强制执行此类检查。

注意:目前没有对seam-gen代码强制执行此类检查。

我们目前使用Seam 2.2,Richfaces和Hibernate。


4387
2018-01-30 11:32


起源



答案:


即使您在持久化用户对象之前检查代码中的条件,也有可能有人在您检查的时间和持久保存新用户之间创建重复的loginid。

但是,如果进行显式检查,在UI中显示相应的错误消息会更容易。如果表上有多个约束,捕获ConstraintViolationException将不允许您轻松确定违反了哪个约束。

所以我会两个都做。假设您从Seam的EntityHome扩展:

  1. 在persist()方法中运行查询以确保loginid是唯一的。如果它没有向适当的控件添加错误消息并返回null。
  2. 将调用包装到super.persist()并捕获ConstraintViolationException,显示一般的重复错误消息

编辑

正如Shervin所说,创建一个JSF Validator是一个好主意(替换)#1,但你应该仍然期待最坏的并捕获ConstraintViolationException。


6
2018-01-30 16:28



如果独立客户端进行直接JPA调用,您如何防止这些问题。我们如何才能在这些场景中重复使用代码? - Joe
独立客户端是否始终进行直接JPA调用,或者您是否可以强制它通过某种类型的DAO? - mtpettyp
我们正在使用Seam组件(EntityHome,EntityQuery接口),它们目前充当用户界面和JPA持久层之间的粘合剂。我不清楚,如果我可以强制独立客户端使用Seam抽象层来实现持久性而不是直接使用JPA层。如果必须存在Seam层,则需要将Seam库作为客户端工具包的一部分提供。不知道这里最好的方法是什么 - Joe
这可能导致相当多的额外查询,这将严重影响性能。 - pihentagy


答案:


即使您在持久化用户对象之前检查代码中的条件,也有可能有人在您检查的时间和持久保存新用户之间创建重复的loginid。

但是,如果进行显式检查,在UI中显示相应的错误消息会更容易。如果表上有多个约束,捕获ConstraintViolationException将不允许您轻松确定违反了哪个约束。

所以我会两个都做。假设您从Seam的EntityHome扩展:

  1. 在persist()方法中运行查询以确保loginid是唯一的。如果它没有向适当的控件添加错误消息并返回null。
  2. 将调用包装到super.persist()并捕获ConstraintViolationException,显示一般的重复错误消息

编辑

正如Shervin所说,创建一个JSF Validator是一个好主意(替换)#1,但你应该仍然期待最坏的并捕获ConstraintViolationException。


6
2018-01-30 16:28



如果独立客户端进行直接JPA调用,您如何防止这些问题。我们如何才能在这些场景中重复使用代码? - Joe
独立客户端是否始终进行直接JPA调用,或者您是否可以强制它通过某种类型的DAO? - mtpettyp
我们正在使用Seam组件(EntityHome,EntityQuery接口),它们目前充当用户界面和JPA持久层之间的粘合剂。我不清楚,如果我可以强制独立客户端使用Seam抽象层来实现持久性而不是直接使用JPA层。如果必须存在Seam层,则需要将Seam库作为客户端工具包的一部分提供。不知道这里最好的方法是什么 - Joe
这可能导致相当多的额外查询,这将严重影响性能。 - pihentagy


我不同意处理ConstraintException。我写了一个验证器,在保存之前检查重复项,它运行良好。

以下是检查重复电子邮件的示例。

@Name("emailValidator")
@Validator
@BypassInterceptors
@Transactional
public class UniqueEmailValidator implements javax.faces.validator.Validator, Serializable {

private static final long serialVersionUID = 6086372792387091314L;

@SuppressWarnings("unchecked")
public void validate(FacesContext facesContext, UIComponent component, Object value) throws ValidatorException {
    EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
    String newEmail = (String) value;
    String oldEmail = String.valueOf(component.getAttributes().get("oldEmail"));
    if (oldEmail != null && !oldEmail.equalsIgnoreCase(newEmail)) {
        List<User> users = entityManager.createQuery(
                "SELECT DISTINCT u FROM " + User.class.getName() + " p where lower(p.fromEmail) = :email").setParameter("email",
                newEmail.toLowerCase()).getResultList();
        if (!users.isEmpty()) {
            Map<String, String> messages = Messages.instance();
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, messages.get("admin.emailexists"), messages
                    .get("admin.emailexists")));
        }
    }
}
}

在你的形式(xhtml)你写:

<s:decorate template="/layout/definition.xhtml">
        <ui:define name="label">#{messages['processdata.email']}</ui:define>
        <h:inputText id="fromEmail" size="30" required="true" value="#  {userAdmin.existingUser.fromEmail}">
            <f:validator validatorId="emailValidator"/>
            <f:attribute name="oldEmail" value="#{userAdmin.existingUser.fromEmail}" />
            <s:validate />
        </h:inputText>
    </s:decorate>

这样,它将始终在保存之前验证字段。您甚至可以使用a:support标记来验证焦点何时更改。


6
2018-02-10 19:06



如果您的数据库处理唯一,那么它将抛出异常,您的一般异常处理程序将修复它。 - Shervin Asgari
在“流程验证”和“更新模型”JSF阶段之间,仍有可能使用重复的电子邮件更新您的数据库,并且会抛出ConstraintException。 - mtpettyp
如果您使用乐观锁定,即不会发生这种情况 @Version - Shervin Asgari


我的建议是,如果你可以检查一个条件然后检查它,即在你的情况下是UserExists方法调用。抛出异常是昂贵的,并且用于通常与您的控制之外的事物有关的特殊情况,例如:光盘访问等

在调用实体添加到数据库之前,通常会在业务逻辑中执行此检查。


2
2018-01-30 11:47



是的,我对此方法的唯一关注是几乎不需要为记录的任何创建或修改触发查询。我只是在寻找用户喜欢的最佳方法。谢谢 - Joe
问题是:什么是更昂贵的,测试每次写入操作DB或只在出现问题时处理异常? - Randy