我的任务是为自定义验证创建一个注释。这是由于handling database constraint violations nicely.的一些问题。我对此做的是相对简单。我专门为需要它的一个域类创建了一个类级别的CustomConstraint。我得到的是当前的结果如下:
@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的一些问题之后,这就是我的Validator实际看起来的样子:
@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();
}
}
对于我目前的配置,现在真正的牛肉,问题是:
当我在收到用户的操作后运行以下代码时,该功能非常有效且正确地将重复的位置名称标记为无效。当locationName未重复时,也可以正常工作。
public long add(@Valid final Location location) {
entityManager.persist(location);
return location.getId();
}
请注意,此处的entityManager
和UniqueLocationValidator中的entityManager
都是通过上述@PersistenceContext EntityManager中的Weld CDI注入的。
什么行不通的是:
public long update(@Valid final Location location){
entityManager.merge(location);
return location.getId();
}
调用此代码时,我会得到一个相对较短的堆栈跟踪,它有一个ConcurrentModificationException
作为根本原因。
我既不明白为什么会这样,也不会理解如何解决这个问题。我没有尝试显式多线程我的应用程序,所以这应该由JBoss 7.1.1-Final我用作应用程序服务器进行管理..
答案 0 :(得分:4)
EntityManager
无法实现您的目标。嗯,通常不会。
在处理更新期间调用您的验证器。通过EntityManager
发送的查询会影响内部存储ActionQueue
EntityManager
。这就是导致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!)