[hibernate-issues] [Hibernate-JIRA] Commented: (HV-230) Database Connective or @Unique

Ken Egervari (JIRA) noreply at atlassian.com
Sun Sep 20 01:23:50 EDT 2009


    [ http://opensource.atlassian.com/projects/hibernate/browse/HV-230?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=33994#action_33994 ] 

Ken Egervari commented on HV-230:
---------------------------------

Okay, I implemented a proof of concept of how this works, however some of the code is application specific. This suggests that come configuration would be required. Right now, this is tied to spring specifically, but I'm sure this could be done without Spring as well.

At the top of my domain class, I have this constraint:

@QueryConstraint( hql = "from UserAccount where emailAddress = :emailAddress and id != :id",
	message = "{userAccount.email.unique}")
public class UserAccount extends DomainObject {
...
}

The annotation code looks like this - I practically xerox'd it from your documentation:
@ValidatorClass( QueryConstraintValidator.class )
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Documented
public @interface QueryConstraint {

	String hql() default "";
	String message() default "{validator.query}";

}

The QueryConstraintValidator looks like this below. Now, I commented it to death to explain that this is just a proof of concept. The implementation in Hibernate Validator would be much different, to generify how it works. 

public class QueryConstraintValidator implements Validator<QueryConstraint>, PropertyConstraint {

	private String hql;

	public void initialize( QueryConstraint parameters ) {
		hql = parameters.hql();
	}

	public void apply( Property property ) {

	}

	public boolean isValid( Object value ) {
		if( value == null ) return true;
		if( !( value instanceof DomainObject ) ) return false;

		// I doubt I really have to cast to my application specific domain class, since we are just
		// using reflection anyway. I am doing it in my case since I mistakenly put @QueryConstraint
		// on the email address, not thinking that only the email was being passed (stupid me)

		DomainObject domainObject = ( DomainObject ) value;

		// Again, if you were to use this in your library, you probably would not want to use this
		// Spring-specific BeanWrapper, but rather, you would use your own way of getting values
		// through reflection.

		final BeanWrapper beanWrapper = new BeanWrapperImpl( domainObject );

		// This is the easiest way for me to get a session factory from here. I have no idea
		// how to get it in an easier way. If Hibernate validator could hook into Hibernate and
		// auto-wire the session factory during configuration (as a spring bean, or something
		// like that), then this would be much more trivial to do. Perhaps have a SessionFactoryAware
		// interface one the validator, or make a specific HibernateValidator class to extend from.

		SessionFactory sessionFactory =
			( SessionFactory ) ApplicationContextProvider.getBean( "sessionFactory" );

		// Opening a stateless session seemed like the best way to handle this type of
		// feature... because it's not within spring transactions or in the environment. Really,
		// this is a low risk operation. It doesn't need to be a in transaction, does it? If it does,
		// then we need that functionality ot auto-magically work ;)

		Query query = sessionFactory.openStatelessSession().createQuery( hql );

		// The idea is to get the parameter names from inside the hql query, and then bind them via
		// the bean wrapper. If the parameter names are misspelled, it will bomb, but this is just
		// a proof of concept. I'm sure you guys have much more sophisticated hql parsers to get this
		// information reliably and to throw the correct exception. In this case, getParameterNames()
		// simply returns [ "emailAddress", "id" ] for my example.

		for( String parameterName : HqlParser.getParameterNames( hql ) ) {
			query.setParameter( parameterName, beanWrapper.getPropertyValue( parameterName ) );
		}

		// Basically if the query does not result a value, then it is good. If it returns 1 or
		// more results, then it fails. Perhaps this can be customized using parameters in the
		// annotation, but let's just keep this simple for now.

		return query.list().size() == 0;

	}
}

I dunno if you can make good use of this. I hope so. I hope you like this idea and can somehow make it work, in Hibernate Validator, or in a sub-project that makes it work with Hibernate. Thanks for listening.


> Database Connective or @Unique
> ------------------------------
>
>                 Key: HV-230
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HV-230
>             Project: Hibernate Validator
>          Issue Type: New Feature
>    Affects Versions: 3.1.0.GA
>            Reporter: Ken Egervari
>            Priority: Critical
>
> I find a common validation use case is checking to see if a field is unique to all those in the table, such as emails, isbns, usernames, keywords, etc. these fields may not be the primary key, but still need to be unique.
> It would be fantastic if hibernate validator implemented this.
> This is a common problem though because the domain class will need to have access the database. I dunno if there's an easy way to wire in a copy of sessionFactory to make this easy to write.
> Perhaps something like this:
> @Query( "select user from User user where user.emailAddress = :this.emailAddress and user.id != :this.id", message = "That Email is already being used by another user in the system" )
> private String emailAddress;
> Basically the idea is that if @Query returns no results, then the validation constraint is good, and if returns more than 1 result, then it fails. Kind of like simpleJdbcTemplate.queryForMap() does within the Spring testing framework. That method causes the test to fail if no row is returned, because it expects 1 result.
> If it were possible to just say
> @Unique
> private String emailAddress;
> That would be extremely concise and would save people a ton of time. 
> Of course, this requires some interoperability with Hibernate... but I think that's a good thing, no?
> Thanks for taking this into consideration. I'd appreciate an email letting me know how to do this in a clean way in 3.1.0 GA as it is as well if you would. Thank you.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the hibernate-issues mailing list