Hibernate 2.1 / MySQL
I have read some previous questions about how to map many-to-any with collections in this forum but I still do not manage to get it right....
My though is to create a database logging service were most objects to be logged are subclasses of LogData but for some cases (were the variations are so many that the number of tables would be to big if following a strict inheritance pattern) one can create just one subclass and add some “add-hock” arguments that vary from record to record in an array of polymorphic data.
I want to use a custom mapper that represent each ”any” reference as two longs (an index representing what table the value is stored in and an index into that table) and I want to map an array of this king of polymorphic entities.
I have been googling for hours but fail to find one single example of how to use any-to-many to map collections of polymorhic objects... perhaps this is because it can’t be done????
Perhaps somebody can give me some input on how the Hibernate mapping of the LogData class should be done (this is the last mapping in the email)?
Sorry for the long text but I tried to be reasonably complete to show what I want to achieve.
----- Code and mapping documents follow! -----
Code:
package test;
import java.util.Date;
public class LogData {
private long m_id = -1;
private Date m_time;
private long m_sequenceNumber;
private Source m_source;
private RecordType m_recordType;
private String m_description;
private Argument[] m_arguments; // Polymorphic array!
protected LogData(){}
public LogData(Source source, String message, long sequenceNumber, Date daytime, RecordType recordType, Argument[] arguments){
m_source= source;
m_description = message;
m_sequenceNumber = sequenceNumber;
m_time = daytime;
m_recordType = recordType;
m_arguments = arguments;
}
public long getId(){
return m_id;
}
public Source getSource(){
return m_source;
}
public String getDescription(){
return m_description;
}
public long getSequenceNumber(){
return m_sequenceNumber;
}
public Date getTime(){
return m_time;
}
public RecordType getRecordType() {
return m_recordType;
}
public Argument[] getArguments(){
return m_arguments;
}
private void setId(long id){
m_id = id;
}
private void setSource(Source source){
m_source = source;
}
private void setDescription(String description){
m_description = description;
}
private void setSequenceNumber(long sequenceNumber){
m_sequenceNumber = sequenceNumber;
}
private void setTime(Date time){
m_time = time;
}
private void setRecordType(RecordType recordType) {
this.m_recordType = recordType;
}
private void setArgument(Argument[] arguments){
m_arguments = arguments;
}
}
package test;
public interface Argument { // Marker interface for all argument types
long getId();
}
package test;
public class IntegerArgument implements Argument {
private long m_id = -1;
private Integer m_value;
private IntegerArgument(){}
public IntegerArgument(int value){
m_value = new Integer(value);
}
public long getId(){
return m_id;
}
public Integer getValue(){
return m_value;
}
private void setId(long id){
m_id = id;
}
private void setValue(Integer value) {
m_value = value;
}
}
package test;
public class StringArgument implements Argument {
private String m_value;
private long m_id = -1;
private StringArgument(){}
private StringArgument(String value){
m_value = value;
}
public long getId(){
return m_id;
}
public String getValue(){
return m_value;
}
private void setId(long id){
m_id = id;
}
private void setValue(String value) {
m_value = value;
}
}
// More different argument classes in real application….
package test;
import java.util.Date;
public class ArgumentMapper extends ClassToIntegerMapper{
static public final Integer STRINGARGUMENT_VALUE = new Integer(1);
static public final Integer INTEGERARGUMENT_VALUE = new Integer(2);
static final private ClassToIntValue[] mappings =
{
new ClassToIntValue(STRINGARGUMENT_VALUE, StringArgument.class),
new ClassToIntValue(INTEGERARGUMENT_VALUE, IntegerArgument.class),
};
public ClassToIntValue[] getMappings() {
return mappings;
}
}
package test;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;
/**
* @author Dhananjay Nene
*/
abstract public class ClassToIntegerMapper implements UserType{
static final private int[] TYPES = {Types.INTEGER };
abstract public ClassToIntValue[] getMappings();
public int[] sqlTypes() {
return TYPES;
}
public Class returnedClass() {
return java.lang.Class.class;
}
public boolean equals(Object arg0, Object arg1) throws HibernateException {
if (arg0 == arg1) return true;
if ((arg0 == null) || (arg1 == null)) return false;
return ((java.lang.Class) arg0).equals((java.lang.Class) arg1);
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
Integer result = (Integer) Hibernate.INTEGER.nullSafeGet(rs,names[0]);
if(result == null) {
return null;
}
ClassToIntValue[] mappings = getMappings();
for(int i = 0 ; i < mappings.length ; i++) {
if (result.equals(mappings[i].getIntVal())){
return mappings[i].getClazz();
}
}
return null;
}
public void nullSafeSet(PreparedStatement stmt, Object value, int index) throws HibernateException, SQLException {
java.lang.Class clazz = (java.lang.Class) value;
if (clazz == null) {
Hibernate.INTEGER.nullSafeSet(stmt,null,index);
return;
}
Integer intval = null;
ClassToIntValue[] mappings = getMappings();
for(int i = 0 ; i < mappings.length ; i++) {
if (clazz.equals(mappings[i].getClazz())){
intval = mappings[i].getIntVal();
}
}
Hibernate.INTEGER.nullSafeSet(stmt,intval,index);
}
public Object deepCopy(Object arg0) throws HibernateException {
if (arg0 == null) return null;
Class clazz = (Class)arg0;
Class newClazz = (Class) Hibernate.CLASS.deepCopy(clazz);
return newClazz;
}
public boolean isMutable() {
return false;
}
}[/code]
(Some of) The Mapping files:
Code:
<hibernate-mapping
package="test">
<class name="StringArgument">
<id name="id" unsaved-value="-1">
<generator class="native"/>
</id>
<property name="value" not-null="true"/>
</class>
</hibernate-mapping>
<hibernate-mapping
package="test">
<class name="IntegerArgument">
<id name="id" unsaved-value="-1">
<generator class="native"/>
</id>
<property name="value" not-null="true"/>
</class>
</hibernate-mapping>
// This is the one were I don’t manage to get the syntax right.....
<hibernate-mapping
package="test">
<class name="LogData">
<id name="id" unsaved-value="-1">
<generator class="native"/>
</id>
<property name="time" not-null="true"/>
<property name="sequenceNumber" not-null="true"/>
<many-to-one name="source" class="Source" not-null="true"/>
<many-to-one name="recordType" class="RecordType" not-null="true"/>
<property name="description" not-null="true"/>
<array name="arguments">
<many-to-any name="argument" id-type="long" meta-type="org.hibernate.log.ArgumentMapper">
<column name="argumentType"/>
<column name="argumentId"/>
</many-to-any>
</array>
</class>
</hibernate-mapping>
[/code]