2014-10-08 4 views
2

사용자 정의 유효성 검사에 대한 주석을 작성해야했습니다. 이것은 handling database constraint violations nicely.의 몇 가지 문제점으로 인해 발생했습니다. 이에 대한 응답으로 은 비교적입니다. 필자는 필 요한 도메인 클래스에 대해 클래스 수준의 CustomConstraint를 만들었습니다.사용자 정의 유효성 검사 주석에 ConcurrentModificationException이 도입되었습니다

@UniqueLocation 주석 : 이것은, 실제로는 내가 진행 hibernate documentation.

에서 거의 그대로하지 장관 복사 된입니다

@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 {}; 
} 

내가 내 현재의 결과로 도착하면 다음과 같다 내 UniqueLocationValidator을 생성하고 거기에 영속 컨텍스트를 사용할 때 문제가 발생했습니다. 방어 선택을하고 싶었고, 내 응용 프로그램을 넓게 주입하려했습니다. @Produces @PersistenceContext EntityManager.

<?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와 함께 몇 가지 문제로 실행 한 후이 내 검사기가 실제로 모습입니다 : 내가 포함 그에

제이보스 Seam은이를 다음과 같이 내 validation.xml을 구성 InjectingConstraintValidatorFactory의 사용

@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 사용자로부터 행동을 취하는 것, 그 일은 놀랍게도 올바르게 작동하고 중복 된 위치 이름을 무효로 표시합니다. 또한 locationName이 중복되지 않은 경우에도 영속적으로 작동합니다. UniqueLocationValidator에서 여기 entityManagerentityManager하고 모두 상기 @PersistenceContext EntityManager의 CDI로부터 용접을 통해 주입된다

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

마음.

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

이 코드를 호출, 나는 근본 원인으로 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!)