2012-03-28 7 views
2

Ich habe eine Entität, auf der ich die folgende Einschränkung implementieren muss: "Es kann immer nur einen Datensatz für jede Kombination von Spalten X und Y geben, für die Spalte Null ist, wenn der Datensatz vom Typ A ist."Kann ich mit der Hibernate-Validierung die zu validierende Entität abfragen?

Der letzte Teil verwandelt dies von einer einfachen Eindeutigkeitseinschränkung in etwas Komplexeres. Ich schreibe einen benutzerdefinierten Hibernate-Validator, um es zu überprüfen.

Was ich tue, ist:

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

Wenn es mehr als einen solchen Datensatz sollte die Validierung fehl. Dadurch wird immer die Einstellung propertyZ erzwungen, bevor ein neuer Eintrag eingefügt wird.

jedoch dies nicht funktioniert, weil diese Validierung onPersist und an diesem Punkt ist nun mal die Abfrage gibt ein Ergebnis mit einer null id, die eine Ausnahme verursacht.

Hier sind einige interessanten Zeilen aus dem Stack-Trace:

[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) 

Eine andere Sache zu beachten ist, dass die Tabelle leer ist, wenn dieser erste Einsatz erfolgt.

DIE FRAGE IST, kann ich die gleiche Tabelle abfragen, die ich zu validieren versuche? Es scheint eine sehr logische Anforderung zu sein, da eine Constraint-Annotation in die Klasse eingefügt werden kann. Meine Validierung hängt vom Zustand der Daten ab.

+0

Ich frage mich, ob es die neue Abfrage-Lookup innerhalb der Transaktion für die Persistenz tut? –

+0

Aus dem Ruhezustand [Forum] (https://forum.hibernate.org/viewtopic.php?f=1&t=1004374): "Es ist nicht in Ordnung, dieselbe Sitzung in Callback-Methoden, die von der Session." Dies ist wahrscheinlich das Problem. –

Antwort

3

Inspiriert von this post und dem Wissen, dass it is never OK/nennen die gleiche Sitzung in allen Callback-Methoden verwenden, das von der Sitzung ausgelöst wird, konnte ich durch habhaft meiner EntityManagerFactory, was wiederum das Problem ganz einfach lösen läßt mich bekomme eine EntityManager, die mir eine neue Session gibt. Jetzt kann ich das für die Abfrage verwenden.

EntityManager em = emFactory.createEntityManager(); 
session = (Session) em.getDelegate(); 
Query query = session.createQuery(... 

HINWEIS: Die von den EntityManager docs, getDelegate() Implementierung spezifisch ist. Ich verwende tomcat und hibernate, und es funktioniert gut.

+0

Danke dafür. Ich hatte ein ähnliches Problem - nicht mit einem separaten Entity-Manager bedeutete, dass meine Abfrage in den Validator nur ohne Rückkehr hängen ... –

+0

@StefanHaberl Ich bin froh, dass dies für jemanden nützlich war. Als ich auf dieses Problem stieß, schien niemand sonst interessiert zu sein. –