]
Hardy Ferentschik resolved HV-246.
----------------------------------
Resolution: Fixed
BeanValidationEventListener has a bug (technically a problem with
hibernate-annotations 3.5.0beta1... but I couldn't find that project)
---------------------------------------------------------------------------------------------------------------------------------------
Key: HV-246
URL:
http://opensource.atlassian.com/projects/hibernate/browse/HV-246
Project: Hibernate Validator
Issue Type: Bug
Components: engine
Affects Versions: 4.0.0.CR1
Environment: hibernate 3.3.2ga, hibernate validator 4.0cr1, hibernate-annotations
3.5.0beta1
Reporter: Ken Egervari
Priority: Blocker
Fix For: 4.1.0-Beta-1
The interaction with a hibernate session and sessions created within a validator do not
work together.
For example, here is a test case:
{code}
@Test
public void jobSeekersCanSaveAPreviousResumeWithTheSameName() {
JobSeekerResume resume = ( JobSeekerResume ) resumeDao.find( 1 );
resume.setDescription( "new desc." );
resumeDao.save( resume );
flush();
jdbcMap = simpleJdbcTemplate.queryForMap(
"select * from resume where resume_id = ?", resume.getId() );
assertEquals( resume.getDescription(), jdbcMap.get( "DESCRIPTION" ) );
}
{code}
the flush() method creates an exception:
{code}
ERROR AssertionFailure:45 - an assertion failure occured (this may indicate a bug in
Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: collection [jawbs.domain.jobseeker.JobSeeker.categories]
was not processed by flush()
at org.hibernate.engine.CollectionEntry.postFlush(CollectionEntry.java:228)
at
org.hibernate.event.def.AbstractFlushingEventListener.postFlush(AbstractFlushingEventListener.java:356)
at
org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
at jawbs.DatabaseTests.flush(DatabaseTests.java:113)
{code}
Obviously hibernate core is saying this could be a bug, or it could be unsafe.
Now, the reason this happens is because JobSeekerResume has a contraint that accesses the
hibernate session. Now, I have many, many constraints that use this constraint and they
work fine. This is the only test that is causing problems for me, and I don't have a
clue why.
Nonetheless, I'm going to give you a lot of code. Here is the constraint annotation
on JobSeekerResume:
{code}
@QueryConstraint(
hql = "select count(*) from JobSeekerResume where name = :name and id != :id and
jobSeeker.id = :jobSeeker.id",
message = "{jobSeekerResume.name.unique}",
enabled = true
)
{code}
And here is the code for the validator implementation:
{code}
public class QueryConstraintValidator extends
ValidatorSupport<QueryConstraint,DomainObject> {
/* Fields */
private String hql;
private boolean enabled;
/* Services */
public void initialize( QueryConstraint queryConstraint ) {
this.hql = queryConstraint.hql();
this.enabled = queryConstraint.enabled();
}
public boolean isValid( DomainObject domainObject ) {
return isValid( domainObject, null );
}
@Override
protected boolean preValidate() {
return !enabled;
}
protected boolean testQuery( Session session, DomainObject domainObject ) {
logger.debug( "Enabled - Validating constraint with: " );
logger.debug( hql );
Query query = session.createQuery(
HqlParser.removePeriodsFromParameterNames( hql )
);
BeanWrapper beanWrapper = new BeanWrapperImpl( domainObject );
for( String parameterName : HqlParser.getParameterNames( hql ) ) {
query.setParameter(
HqlParser.removePeriodsFromParameterName( parameterName ),
beanWrapper.getPropertyValue( parameterName )
);
}
boolean result = (Long) query.uniqueResult() == 0;
logger.debug( "isValid is returning: " + result );
return result;
}
...
}
{code}
The support constraint (the important one most likely) is as follows:
{code}
public abstract class ValidatorSupport<T extends Annotation,U> implements
ConstraintValidator<T,U> {
/* Fields */
protected static Logger logger =
LoggerFactory.getLogger( QueryConstraintValidator.class );
/* Services */
public boolean isValid( U object, ConstraintValidatorContext constraintValidatorContext
) {
if( preValidate() ) {
return true;
}
SessionFactory sessionFactory =
( SessionFactory ) ApplicationContextProvider.getBean(
"sessionFactory" );
if( sessionFactory != null ) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
boolean result = testQuery( session, object );
tx.commit();
session.close();
return result;
}
return true;
}
protected boolean preValidate() {
return false;
}
protected abstract boolean testQuery( Session session, U object );
}
{code}
Lastly, here is the mapping of the JobSeeker subclass:
{code}
<subclass name="jawbs.domain.jobseeker.JobSeeker"
discriminator-value="ROLE_JOBSEEKER">
<property name="isPrivate" column="is_private" />
<bag name="categories" table="job_seeker_to_category"
cascade="all-delete-orphan" inverse="true">
<key column="job_seeker_id"/>
<many-to-many class="jawbs.domain.Category"
column="category_id"/>
</bag>
<bag name="watchedJobs"
table="job_seeker_to_watched_job" cascade="all-delete-orphan"
inverse="true">
<key column="job_seeker_id"/>
<many-to-many class="jawbs.domain.job.Job"
column="watched_job_id"/>
</bag>
<bag name="applications" table="candidate"
cascade="all-delete-orphan" inverse="true">
<key column="job_seeker_id"/>
<many-to-many class="jawbs.domain.candidate.Candidate"
column="candidate_id"/>
</bag>
<bag name="blacklistedEmployers"
table="job_seeker_to_blacklisted_employer">
<key column="job_seeker_id"/>
<many-to-many class="jawbs.domain.employer.Employer"
column="blacklisted_employer_id"/>
</bag>
<bag name="resumes" table="resume"
inverse="true" cascade="all-delete-orphan">
<key column="job_seeker_id"/>
<one-to-many class="jawbs.domain.resume.Resume" />
</bag>
</subclass>
{code}
And here is the mapping of the subclass of resume:
{code}
<subclass name="jawbs.domain.resume.JobSeekerResume"
discriminator-value="JOBSEEKER">
<many-to-one name="jobSeeker"
class="jawbs.domain.jobseeker.JobSeeker"
column="job_seeker_id" />
</subclass>
{code}
If you need any more assistance, please let me know. This is a critical blocker to
getting a test to pass, this is most likely 100% fine.
Thanks
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: