Realsolve Logo
Blogs
 

Phil Zoio's Weblog, August 24, 2005

An alternative approach to ID Generation in Hibernate

I am experimenting with an alternative approach to primary key generation and would welcome any comments on it.

I have difficulties with the idea of having to have business key semantics in my equals() and hashCode() implementations for my persistent classes. The identifier cannot be used because the persistent objects would not behave properly when added to sets. Not all of the tables have natural or obvious business keys. Some would use dates as business keys, and I don't want to have to worry about date synchronization to ensure object identity integrity. That's what the primary key is for.

One way around this is to mandate that primary key values are inserted manually when the object is first created (i.e. is transient), but before being added to a set.

Here's my approach to implementing this:

1) define the identifier element as before, as if I were assigning IDs the normal way

2) override Spring's LocalSessionFactoryBean. Here, I extract the identifiers during the Configuration postprocessing, and store them locally. I then substitute the identifier generation strategy with assigned, so that when the session factory is built, it is as if I had specified "assigned" for all of the generators

Here's the code:

public class HibernateSessionFactoryBean extends LocalSessionFactoryBean
{

    private Map<String, IdentifierGenerator> identifierGenerators = new HashMap<String, IdentifierGenerator>();

    @Override
    protected void postProcessConfiguration(Configuration config) throws HibernateException
    {
        
        
        Iterator classes = config.getClassMappings();
        Settings settings = config.buildSettings();

        while (classes.hasNext())
        {
            PersistentClass model = (PersistentClass) classes.next();
            if (!model.isInherited())
            {
                
                KeyValue identifier = model.getIdentifier();
                
                IdentifierGenerator generator = identifier.createIdentifierGenerator(settings.getDialect(),
                        settings.getDefaultCatalogName(), settings.getDefaultSchemaName(), (RootClass) model);
                
                String entityName = model.getEntityName();
                identifierGenerators.put(entityName, generator);
                
                //hack 1: identifier would ordinarily be SimpleValue but this is not part of the public API
                if (identifier instanceof SimpleValue)
                {
                    SimpleValue sv = (SimpleValue) identifier;
                    sv.setIdentifierGeneratorProperties(null);
                    sv.setIdentifierGeneratorStrategy("assigned");
                }
                
            }
            
        }

    }
    
    public IdentifierGenerator getIdentifierGenerator(Object target)
    {
        return identifierGenerators.get(target.getClass().getName());
    }
    

}

3) create factory methods for each of the domain classes, instead of using the "new" operator. The id is added in the factory method

public Project newProject()
{
    final Project project = new Project();
    Long objectIdentifier = (Long) getObjectIdentifier(project);
    project.setProjectId((objectIdentifier).longValue());
    return project;
}

private Serializable getObjectIdentifier(final Object object)
{

    HibernateSessionFactoryBean sessionFactoryBean = (HibernateSessionFactoryBean) context
            .getBean(BeanFactory.FACTORY_BEAN_PREFIX + SpringBeans.sessionFactory);

    //pick up the identifier generator being used in the application
    final IdentifierGenerator identifierGenerator = sessionFactoryBean.getIdentifierGenerator(object);
    
    return (Serializable) hibernateDaoSupport.getHibernateTemplate().execute(new HibernateCallback()
    {
        public Object doInHibernate(Session session) throws HibernateException, SQLException
        {
            return identifierGenerator.generate((SessionImplementor) session, object);
        }
    }, true);

}

There are a couple of obvious issues: the IdentifierGenerator API is designed to be used internally, not by the application. That's why I'm having to cast to SessionImplementor. I'm also having to cast to SimpleValue in the postProcessConfiguration() method. It would be nice if the identifier generator API were public and what I'm doing here were an "approved" option.





Want to comment on this blog? Please email me at philzoio@realsolve.co.uk. At some stage I'll probably add a form, or maybe even a public forum, but for now I'm keeping the site pretty basic.