2014-10-08 62 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!)