]
Hardy Ferentschik commented on HV-246:
--------------------------------------
Just some final comments before I resolve this issue, since I don't think it belongs
here. This is a discussion for the forum and not a actual bug.
#1 Regarding the need to access ApplicationContextProvider directly in the
ConstraintValidator implementation. The right approach would be to implement a custom
ConstraintValidatorFactory. I guess within the factory you can still use
ApplicationContextProvider to get to the SessionFactory or maybe even better you could
pass the SessionFactory as constructor into the ConstraintValidatorFactory. That depends
of course on how you bootstrap Bean Validation within Spring
#2 To open a child session (a new session on top of a connection of a current session) you
can use SessionFactoty.getCurrentSession().connection() to get hold of the current
connection. Then you can pass this connection to one of the open session calls of the
SessionFactory. Session.connection() is deprecated, but I don't think there is an
alternative way of doing this as yet.
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
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
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: