Hello,
Another idea I recently tried to implement (but failed because not enough knowledge/information available on Hibernate meta-data) :
Example : An application creates order information, but wants to be able to reference historical data efficiently. When related objects change, the order still has to have it's local copy of the object with the state at order time.
By this I mean that the columns of the related object are also available in the order table, and when we perform orderline.setProductType(prodTyp), all information of the object 'prodTyp' would get copied in columns of the orderline table.
This essentially means that we want to be able to have a certain Entity type (ProductType) and we sometimes whish to treat it as a ValueType in some other object. (I know you can serialize it in a BLOB column, but this is obviously less desirable for maintenance).
I tried to implement this idea as a UserType, but couldn't figure out where to get all the necessary metadata. (On a side note it would be great if the UserType could also know what the Type of the object it is mapping is)
I haven't finished it, but for completeness sake I'll include it :
package com.dhl.ncg.ums.model;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import com.dhl.ncg.ums.aop.PersistencySupport;
import com.dhl.ncg.ums.exception.InternalServerException;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.UserType;
import net.sf.hibernate.impl.SessionFactoryImpl;
import net.sf.hibernate.metadata.ClassMetadata;
import net.sf.hibernate.type.Type;
Code:
/**
* @author Dimitry D'hondt
*
* This class is the base class for all 'Entity as value' wrappers.
* Being able to wrap an AbstractEntity as a valkue inside of another object
* allows us to define 'historically copied data' in our domain model.
*
* A good example is the UldRepairPart object. This object needs to maintain
* an historical copy of UldPart, UlrPartCost, UldRepairLaboutCost and GenericHour objects,
* so that exact details are known about the cost-calculation, even if in the future
* any of the above objects are updated. Clearly updating a UldPart with a new number of minutes
* for labour does NOT affect repairs, made in the past..
*
* The containing object may declare a property with type = "SomeConcreteEntityAsValueUserType" and
* then all property columns for the value of that property will get copied into the containing object,
* when an assignment is made on the usertype property.
*
* No changes need to be made to the classes being included.
*
* AbstractEntities, wrapped as values, are not mutable either, you replace them completely, or not at all..
*
* Wrapping an entity as value also removes any relationships it may have with other entities.
* All relationships will simply be null.
*
* For a concrete example see UldRepairPart.
*/
public abstract class AbstractEntityAsValueUserType implements UserType {
private static Logger log = Logger.getLogger(AbstractEntityAsValueUserType.class);
/**
* @see net.sf.hibernate.UserType#deepCopy(java.lang.Object)
*/
public Object deepCopy(Object value) throws HibernateException {
AbstractEntity abstractEntity = (AbstractEntity) value;
return abstractEntity.clone();
}
/**
* @see net.sf.hibernate.UserType#equals(java.lang.Object, java.lang.Object)
*/
public boolean equals(Object x, Object y) throws HibernateException {
AbstractEntity ae1 = (AbstractEntity) x;
AbstractEntity ae2 = (AbstractEntity) y;
return ae1.equals(ae2);
}
/**
* @see net.sf.hibernate.UserType#isMutable()
*/
public boolean isMutable() {
return false;
}
/**
* @see net.sf.hibernate.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
*/
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
// TODO Auto-generated method stub
return null;
}
/**
* @see net.sf.hibernate.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
*/
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
// TODO Auto-generated method stub
}
/**
* @see net.sf.hibernate.UserType#returnedClass()
*/
public Class returnedClass() {
// TODO Auto-generated method stub
return null;
}
/**
* @see net.sf.hibernate.UserType#sqlTypes()
*/
public int[] sqlTypes() {
ClassMetadata metaData = getClassMetadata();
int[] ret = null;
String[] properties = metaData.getPropertyNames();
SessionFactoryImpl sessionFactory = (SessionFactoryImpl) PersistencySupport.getSessionFactory();
Type[] types = metaData.getPropertyTypes();
List sqlTypes = new ArrayList();
for (int i = 0; i < types.length; i++) {
Type type = types[i];
String property = properties[i];
// if(type.sqlTypes(sessionFactory).length != 1) {
//// types.add(r(type.sqlTypes(sessionFactory)[0]));
// } else {
// log.debug("Property <" + property + "> not mapped for EntityAsValue wrapper.");
// }
}
// ret = (int[]) sqlTypes.toArray(new int[0]);
return ret;
}
/**
* Override this operation to configure the metadata
* @return
*/
private ClassMetadata getClassMetadata() {
ClassMetadata ret = null;
try {
Class clazz = Class.forName("com.dhl.ncg.ums.model." + getEntityClassName());
ret = PersistencySupport.getSessionFactory().getClassMetadata(clazz);
} catch(ClassNotFoundException e) {
String msg = "Configuration for UserType <" + this.getClass().getName() + "> specified an entity class <" + getEntityClassName() + "> that is not available in the VM.";
log.error(msg,e);
throw new InternalServerException(msg);
} catch(HibernateException e) {
String msg = "Configuration for UserType <" + this.getClass().getName() + "> specified an entity class <" + getEntityClassName() + "> unknown to Hibernate.";
log.error(msg,e);
throw new InternalServerException(msg);
}
return ret;
}
/**
* Override this operation to configure the concrete class to wrap.
* Don't specify the package ! com.dhl.ncg.ums.model. is prefixed automatically.
* @return
*/
protected abstract String getEntityClassName();
}
Does anyone know how to pull this off ? Is this an idea for the next version of Hibernate ?
Kind regards,
Dimitry.