Problems with adding custom wrapper for PersistentSet


#1

Hi everyone,

I asked a question about a week ago about using custom wrappers for returned objects, and I got a few answers. However, I also found this topic on Stackoverflow: Correct way to wrap persistent set in hibernate, which is basically what I want to do. However, after trying out this method I get the following error:

org.hibernate.MappingException: Cannot instantiate custom type: org.autofetch.hibernate.AutofetchSetType

Which makes sense, since AutofetchSetType does not have an empty constructor for initialization. This is the two wrapper classes that I’m using:

public class AutofetchSetType extends SetType implements UserCollectionType {

    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public AutofetchSetType(TypeScope typeScope, String role, String propertyRef) {
        super(typeScope, role, propertyRef);
    }
	
    @Override
    public PersistentCollection instantiate(SessionImplementor session,
            CollectionPersister persister, Serializable key) {
            return new AutofetchSet(session);
    }

    @Override
    public PersistentCollection wrap(SessionImplementor session,
            Object collection) {
            return new AutofetchSet(session, (java.util.Set) collection);
    }
    
    @Override
    public Iterator getElementsIterator(Object collection) {
            return ((PersistentSet)collection).iterator();
    }
	@Override
	public PersistentCollection instantiate(SessionImplementor session, CollectionPersister persister)
			throws HibernateException {
		return new AutofetchSet(session);
	}
	@Override
	public boolean contains(Object collection, Object entity) {
		return ((PersistentSet) collection).contains(entity);
	}
	@Override
	public Object replaceElements(Object original, Object target, CollectionPersister persister, Object owner,
			Map copyCache, SessionImplementor session) throws HibernateException {
		((PersistentSet) target).clear();
		((PersistentSet) target).addAll((Collection<?>) original);
		return target;
	}
}

public class AutofetchSet extends PersistentSet implements Trackable {

    private CollectionTracker collectionTracker = new CollectionTracker();

    public AutofetchSet() {
        super();
    }

    public AutofetchSet(SessionImplementor si, Set s) {
        super(si, s);
    }

    public AutofetchSet(SessionImplementor si) {
        super(si);
    }
    
    

    public void addTracker(Statistics tracker) {
        collectionTracker.addTracker(tracker);
    }

    public void addTrackers(Set<Statistics> trackers) {
        collectionTracker.addTrackers(trackers);
    }

    public boolean disableTracking() {
        boolean oldValue = collectionTracker.isTracking();
        collectionTracker.setTracking(false);
        return oldValue;
    }

    public boolean enableTracking() {
        boolean oldValue = collectionTracker.isTracking();
        collectionTracker.setTracking(true);
        return oldValue;
    }

    public void removeTracker(Statistics stats) {
        collectionTracker.removeTracker(stats);
    }

    private void accessed() {
        if (wasInitialized()) {
            collectionTracker.trackAccess(set);
        }
    }

    /**
     * @see java.util.Set#size()
     */
    @Override
    public int size() {
        int ret = super.size();
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    /**
     * @see java.util.Set#isEmpty()
     */
    @Override
    public boolean isEmpty() {
        boolean ret = super.isEmpty();
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    /**
     * @see java.util.Set#contains(Object)
     */
    @Override
    public boolean contains(Object object) {
        boolean ret = super.contains(object);
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    /**
     * @see java.util.Set#iterator()
     */
    @Override
    public Iterator iterator() {
        Iterator iter = super.iterator();
        if (wasInitialized()) {
            accessed();
        }
        return iter;
    }

    /**
     * @see java.util.Set#toArray()
     */
    @Override
    public Object[] toArray() {
        Object[] arr = super.toArray();
        if (wasInitialized()) {
            accessed();
        }
        return arr;
    }

    /**
     * @see java.util.Set#toArray(Object[])
     */
    @Override
    public Object[] toArray(Object[] array) {
        Object[] arr = super.toArray(array);
        if (wasInitialized()) {
            accessed();
        }
        return arr;
    }

    /**
     * @see java.util.Set#add(Object)
     */
    @Override
    public boolean add(Object value) {
        Boolean exists = isOperationQueueEnabled()
                ? readElementExistence(value) : null;
        if (exists == null) {
            initialize(true);
            accessed();
            if (set.add(value)) {
                dirty();
                return true;
            } else {
                return false;
            }
        } else {
            return super.add(value);
        }
    }

    /**
     * @see java.util.Set#remove(Object)
     */
    @Override
    public boolean remove(Object value) {
        boolean ret = super.remove(value);
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    /**
     * @see java.util.Set#containsAll(Collection)
     */
    @Override
    public boolean containsAll(Collection coll) {
        boolean ret = super.containsAll(coll);
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    /**
     * @see java.util.Set#addAll(Collection)
     */
    @Override
    public boolean addAll(Collection coll) {
        if (coll.size() > 0) {
            initialize(true);
            accessed();
            if (set.addAll(coll)) {
                dirty();
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * @see java.util.Set#retainAll(Collection)
     */
    @Override
    public boolean retainAll(Collection coll) {
        boolean val = super.retainAll(coll);
        if (wasInitialized()) {
            accessed();
        }
        return val;
    }

    /**
     * @see java.util.Set#removeAll(Collection)
     */
    @Override
    public boolean removeAll(Collection coll) {
        boolean val = super.removeAll(coll);
        if (wasInitialized()) {
            accessed();
        }
        return val;
    }

    @Override
    public void clear() {
        super.clear();
        if (wasInitialized()) {
            accessed();
        }
    }

    @Override
    public String toString() {
        //if (needLoading) return "asleep";
        String ret = super.toString();
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    @Override
    public boolean equals(Object other) {
        boolean ret = super.equals(other);
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }

    @Override
    public int hashCode() {
        int ret = super.hashCode();
        if (wasInitialized()) {
            accessed();
        }
        return ret;
    }
    
    public boolean isAccessed() {
        return collectionTracker.isAccessed();
    }
}

This is how I map my entities:

@Entity
@Tuplizer(impl = AutofetchTuplizer.class)
public class Employee {

    @Id
    @Column(name = "employee_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long m_id;

    @Column(name = "name")
    private String m_name;
    
    @JoinColumn(name = "supervisor_id")
    @ManyToOne(cascade = {CascadeType.ALL})
    private Employee m_supervisor;

    @CollectionType(type="org.autofetch.hibernate.AutofetchSetType")
    @JoinColumn(name = "supervisor_id")
    @OneToMany(cascade = {CascadeType.ALL})
    private Set<Employee> m_subordinates;

    @Embedded
    private Address m_address;

    @JoinColumn(name = "mentor_id")
    @ManyToOne(cascade = {CascadeType.ALL})
    private Employee m_mentor;

    @Type(type="org.autofetch.hibernate.AutofetchSetType")
    @CollectionType(type="org.autofetch.hibernate.AutofetchSetType")
    @ManyToMany(cascade = {CascadeType.ALL})
    @JoinTable(name = "friends", joinColumns = {@JoinColumn(name = "friend_id")}, inverseJoinColumns = {@JoinColumn(name = "befriended_id")})
    private Set<Employee> m_friends;

So it’s clear why I’m getting the error, hibernate does not find an empty constructor for this type, but I can’t find any documentation how the integration of my wrapper is supposed to be done. Am I using the annotation for the type incorrectly? Any ideas?


#2

Using a custom type that extends from Hibernate SetType might not work as you expect. I have never tested it like that and Hibernate support for custom collection types is not widely used, hence it might have all sorts of limitations.

Try to debug it and see why do you get that exception. It’s unlikely that someone will figure out a solution for these kind of issues you’re bumping into since you are practically altering the way Hibernate works out-of-the-box.


#3

Hey again vlad. I get the error because hibernate tries to get the empty constructor through reflection in an attempt to initialize the object. But the type itself does not provide an empty constructor, hence it fails. For me it doesn’t make sense however why it tries to initialize the SetType instead of the Set, which I have supplied in the wrap and instantiate methods. It is strange…


#4

Hibernate needs the SetType object to be created when you bootstrap since that’s when all Types are instantiated.


#5

Alright. But then how does it work for the normal types, such as the SetType for example? They don’t have their own empty constructor, which should lead them to not being instantiated either?

UPDATE:
After changing so that the AutofetchSetType doesn’t extend setType anymore, the object can be instantiated, but now instead the problem lies in that the object is empty instead. I’ll look into this. Thanks for the help.


#6

The collection types are treated differently because they substitute the real collections with proxies.