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.
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.
In normal circumstances the identifier cannot be used because the persistent objects would not behave properly when added to sets.
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:
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
Code:
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.
Nevertheless, it may still be worth it. At least I can have very simple identifier based implementations of hashCode() and equals(), which means fewer opportunities for bugs here.
It would be nice if the identifier generator API were public and what I'm doing here were an "approved" option.