У меня есть объект, для которого мне нужно реализовать следующее ограничение:
"Для любой комбинации столбцов X и Y может быть только одна запись, для которой столбец Z равен нулю, если запись имеет тип A."
Последняя часть превращает это из простого ограничения уникальности в нечто более сложное. Я пишу пользовательский валидатор Hibernate, чтобы проверить его.
Что я делаю, это:
@Override
public boolean isValid(MyEntity value, ConstraintValidatorContext context) {
Query query = DB.createQuery( // DB is just a convenience class
"select count(*) from MyEntity" +
" where propertyX = :propertyX" +
" and propertyY = :propertyY" +
" and type = :type" +
" and propertyZ is null")
.setParameter("propertyX", value.getPropertyZ())
.setParameter("propertyY", value.getPropertyY())
.setParameter("type", MyType.PRIMARY);
return query.getResultList().size() <= 1;
}
Если существует более одной такой записи, проверка должна завершиться неудачно. Это будет принудительно всегда устанавливать propertyZ
перед вставкой новой записи.
Однако это не работает, потому что эта проверка происходит onPersist
, и в этот момент запрос возвращает результат с идентификатором null
, что вызывает исключение.
Вот несколько интересных строк из трассировки стека:
[junit] org.hibernate.AssertionFailure: null id in my.package.MyEntity entry (dont flush the Session after an exception occurs)
[junit] at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:82)
[junit] at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:190)
[junit] at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:147)
[junit] at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
[junit] at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
[junit] at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
[junit] at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185)
[junit] at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1261)
[junit] at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
[junit] at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:246)
[junit] at my.package.validation.UniqueCombinationTypeValidator.isValid(UniqueCombinationTypeValidator.java:42)
[junit] at my.package.validation.UniqueCombinationTypeValidator.isValid(UniqueCombinationTypeValidator.java:14)
[junit] at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:153)
[junit] at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:140)
...
[junit] at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
[junit] at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808)
[junit] at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782)
[junit] at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786)
[junit] at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672)
[junit] at my.package.DB.persist(DB.java:278)
[junit] at my.package.test.model.validation.UniqueCombinationTypeValidatorTest.testInsert(UniqueCombinationTypeValidatorTest.java:72)
Еще одна вещь, на которую следует обратить внимание, это то, что таблица пуста после первой вставки.
ВОПРОС ЕСТЬ, можно ли запросить ту же таблицу, которую я пытаюсь проверить? Это кажется очень логичным требованием, так как аннотацию ограничения можно поместить в класс. Моя проверка зависит от состояния данных.