在持久化实体之前检查是不是违反约束

Posted

技术标签:

【中文标题】在持久化实体之前检查是不是违反约束【英文标题】:checks for constraint violation before persisting an entity在持久化实体之前检查是否违反约束 【发布时间】:2011-01-11 04:23:51 【问题描述】:

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

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

您是否会让数据库抛出一个 ConstraintViolationException 并在 UI 层适当地处理此消息。在 jboss seam 框架中应该在哪里执行此类检查。

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

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

【问题讨论】:

【参考方案1】:

我不同意处理 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 标记来验证焦点何时更改。

【讨论】:

如果你的数据库处理是唯一的,那么它会抛出异常,你的通用异常处理程序会修复它。 在“流程验证”和“更新模型”JSF 阶段之间仍有可能使用重复的电子邮件更新您的数据库,并且会引发 ConstraintException。 如果您使用乐观锁定,则不太可能发生这种情况,即@Version【参考方案2】:

即使您在持久化用户对象之前检查代码中的条件,也总有可能有人会在您检查和持久化新用户之间创建重复的登录 ID。

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

所以我会两者兼而有之。假设您从 Seam 的 EntityHome 扩展:

    在persist() 方法中运行查询以确保登录ID 是唯一的。如果不是,则将错误消息添加到相应的控件并返回 null。 包装对 super.persist() 的调用并捕获 ConstraintViolationException,显示一般的重复错误消息

编辑

正如 Shervin 提到的,创建 JSF 验证器是一个好主意(替换)上面的 #1,但您仍然应该期待最坏的情况并捕获 ConstraintViolationException。

【讨论】:

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

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

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

【讨论】:

是的,我对这种方法的唯一担心是几乎任何记录的创建或修改都必须触发很少的查询。我只是在寻找用户喜欢的最佳方法。谢谢 问题是:什么更昂贵,对数据库的每个写入操作进行测试或仅在出现问题时处理异常?

以上是关于在持久化实体之前检查是不是违反约束的主要内容,如果未能解决你的问题,请参考以下文章

刷新单向实体时不违反空约束

以下代码是不是违反持久性无知规则

实体持久注册验证侦听器

DDD 域实体与持久性实体

尝试持久化和具有主键唯一约束的实体时出错

违反约束时是不是可以获取行的值?