2014-10-08 63 views
2

我的任务是为自定义验证创建注释。这是由于handling database constraint violations nicely.的一些问题,我做了什么回应这是相对简单。我为需要它的一个域类创建了一个类级CustomConstraint。我得到了我的当前结果如下:自定义验证注解引入ConcurrentModificationException

@UniqueLocation译注:

@Target({ TYPE, ANNOTATION_TYPE }) 
@Retention(RUNTIME) 
@Constraint(validatedBy = UniqueLocationValidator.class) 
@Documented 
public @interface UniqueLocation { 

    String message() default "must be unique!"; 

    Class<?>[] groups() default {}; 

    Class<? extends Payload>[] payload() default {}; 
} 

这不是壮观,实际上它被复制几乎逐字从hibernate documentation.

我继续创建我的UniqueLocationValidator,并遇到了在那里使用持久化上下文的问题。我想运行一个防御选择,因此试图注入我的应用程序范围广泛的@Produces @PersistenceContext EntityManager

为此我包括JBoss Seam的使用它的InjectingConstraintValidatorFactory配置我的validation.xml如下:

<?xml version="1.0" encoding="UTF-8"?> 
<validation-config 
    xmlns="http://jboss.org/xml/ns/javax/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd"> 

    <constraint-validator-factory> 
     org.jboss.seam.validation.InjectingConstraintValidatorFactory 
    </constraint-validator-factory> 

</validation-config> 

运行到一些问题,Creating Constraint Violations后,这是我的验证实际效果的:

@ManagedBean 
public class UniqueLocationValidator implements 
     ConstraintValidator<UniqueLocation, Location> { 
    // must not return a result for name-equality on the same Id 
    private final String QUERY_STRING = "SELECT * FROM Location WHERE locationName = :value AND id <> :id"; 

    @Inject 
    EntityManager entityManager; 

    private String constraintViolationMessage; 

    @Override 
    public void initialize(final UniqueLocation annotation) { 
     constraintViolationMessage = annotation.message(); 
    } 

    @Override 
    public boolean isValid(final Location instance, 
      final ConstraintValidatorContext context) { 
     if (instance == null) { 
      // Recommended, instead use explicit @NotNull Annotation for 
      // validating non-nullable instances 
      return true; 
     } 

     if (duplicateLocationExists(instance)) { 
      createConstraintViolations(context); 
      return false; 
     } else { 
      return true; 
     } 
    } 

    private void createConstraintViolations(
      final ConstraintValidatorContext context) { 
     context.disableDefaultConstraintViolation(); 
     context.buildConstraintViolationWithTemplate(constraintViolationMessage) 
       .addNode("locationName").addConstraintViolation(); 
    } 

    private boolean duplicateLocationExists(final Location location) { 
     final String checkedValue = location.getLocationName(); 
     final long id = location.getId(); 

     Query defensiveSelect = entityManager.createNativeQuery(QUERY_STRING) 
       .setParameter("value", checkedValue).setParameter("id", id); 

     return !defensiveSelect.getResultList().isEmpty(); 
    } 
} 

对于我目前的配置,现在到了真正的牛肉,这个问题:

当我运行下面的代码后r从用户那里获取一个动作,这个东西奇妙而正确地将一个重复的位置名称标记为无效。当位置名称不重复时,持久化工作也很好。

public long add(@Valid final Location location) { 
    entityManager.persist(location); 
    return location.getId(); 
} 

记住,这里的entityManagerentityManager在UniqueLocationValidator都通过焊接CDI注入从上述@PersistenceContext EntityManager的。

什么不工作如下:

public long update(@Valid final Location location){ 
    entityManager.merge(location); 
    return location.getId(); 
} 

调用此代码,我得到一个相对较短的堆栈跟踪,具有ConcurrentModificationException的根源。

我既不明白为什么会这样,也不知道如何解决这个问题。我没有试图明确地多线程化我的应用程序,所以这应该由我作为应用程序服务器使用的JBoss 7.1.1-Final来管理。

回答

3

你试图做的是不可能通过EntityManager。好吧,不正常。

在处理更新期间调用验证程序。通过EntityManager发送的查询会影响内部存储,EntityManagerActionQueue。这是导致ConcurrentModificationException的原因:来自查询的结果会改变当清除更改时EntityManager正在迭代的列表。

解决此问题的方法是跳过EntityManager

我们该怎么做?

呃,这有点肮脏,因为你有效地添加了对hibernate实现的依赖,但是你可以用各种方式来get the connection from the Session or EntityManager。一旦你有一个java.sql.Connection对象,那么你可以使用类似于PreparedStatement的东西来执行你的查询。


实施例修正:

Session session = entityManager.unwrap(Session.class); 
SessionFactoryImplementor sessionFactoryImplementation = (SessionFactoryImplementor) session.getSessionFactory(); 
ConnectionProvider connectionProvider = sessionFactoryImplementation.getConnectionProvider(); 
try { 
     connection = connectionProvider.getConnection(); 
     PreparedStatement ps = connection.prepareStatement("SELECT 1 FROM Location WHERE id <> ? AND locationName = ?"); 
     ps.setLong(1, id); 
     ps.setString(2, checkedValue); 
     ResultSet rs = ps.executeQuery(); 
     boolean result = rs.next();//found any results? if we can retrieve a row: yes! 
     rs.close(); 
     return result; 
}//catch SQLException etc... 
//finally, close resources (only the resultset!)