2014-10-08 3 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.

I протекала для создания моего UniqueLocationValidator и столкнулся с проблемой использования контекста персистентности. Я хотел запустить защитный отбор и, таким образом, попытался применить широкомасштабное приложение @Produces @PersistenceContext EntityManager.

Therefor Я включил 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 ecieving действие от потребителя, вещь работает чудесно и правильно маркирует дублированное имя места как недействительное. Также упорствует работает как раз отлично когда имя места не дублируется.

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

ум, что entityManager здесь и entityManager в UniqueLocationValidator оба вводят через Weld CDI от вышеупомянутого @PersistenceContext EntityManager.

Что не работает, состоит в следующем:

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

При вызове этого кода я получаю относительно короткий StackTrace, который имеет ConcurrentModificationException как первопричин.

Я не понимаю, почему это так, и как я могу это исправить. Я нигде не пытался явно использовать многопоточное мое приложение, поэтому это должно было управляться JBoss 7.1.1-Final, которое я использую в качестве сервера приложений.

ответ

3

То, что вы пытаетесь сделать, невозможно с помощью EntityManager. Ну, не нормально.

Ваш валидатор вызывается во время обработки обновлений. Запросы, отправленные через EntityManager, влияют на внутреннее хранилище, ActionQueueEntityManager. Это то, что вызывает ConcurrentModificationException: результаты вашего запроса изменяют список, который EntityManager выполняет итерацию при изменении смыва.

Обходным путем для этого является обход EntityManager.

Как мы можем это сделать?

Ну, это немного грязно, так как вы эффективно добавляете зависимость от реализации спящего режима, но можете 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!) 
Смежные вопросы