views:

110

answers:

2

Dear all,

I have a Hibernate class which is essentially just a wrapper around loads of collections.

So the class is (massively simplified/pseudo) something like:

@Entity  
public class MyClass {

  @OneToMany  
  Map1

  @OneToMany  
  Map2

  @OneToMany   
  Map3

  AddToMap1();  
  AddToMap2();  
  AddToMap3();  
  RemoveFromMap1();  
  RemoveFromMap2();  
  RemoveFromMap3();  
  DoWhateverWithMap1();  
  DoWhateverWithMap2();  
  DoWhateverWithMap3();  

}

etc. Each of those Maps then has a few methods associated with it (add/remove/interrogate/etc).

As you can imagine, by the time I added the 10th collection or so, the class is getting a tad ridiculous in size.

What I'd love to do is something along the lines of:

 
@Entity  
public class MyClass {

  ClassWrappingMap1;

  ClassWrappingMap2;

  ClassWrappingMap3;
}

With all the various methods wrapped up in those classes:

public class ClassWrappingMap1 {

  @OneToMany
  Map

  AddToMap();  
  RemoveFromMap();  
  DoWhateverWithMap();  

}

I thought perhaps I could use @Embedded for this, but I don't seem to be able to get it to work (Hibernate simply doesn't even try to persist the Map inside the wrapperClass).

Has anyone ever done something like this before? Any hints?

Many thanks,
Ned

A: 

Hibernate manual for annotations states following:

While not supported by the EJB3 specification, Hibernate Annotations allows you to use association annotations in an embeddable object (ie @ToOne nor @ToMany). To override the association columns you can use @AssociationOverride.

So your wrapping approach should work.


First of all, you should check all log files etc for any related errors.

You could try something like this:

  • In your master class (MyClass )
@Entity  
public class MyClass {

  @Embedded
  ClassWrappingMap1 map1;
}
  • And in your wrapping class
@Embeddable
public class ClassWrappingMap1 {

  @OneToMany
  Map map1;

}

Notice that ClassWrappingMap1 uses @Embeddable annotation. However, according to docs the @Embeddable annotation should not be needed, it should be default when @Embedded annotation is used.

Make sure that every ClassWrappingMap class maps a different column in database. Also ClassWrappingMap classes should not have a primary key (@Id or @EmbeddedId columns).

Juha Syrjälä
Sorry but Hibernate does not support @OneToMany when using @Embeddable. You have seen above "nor @*ToMany"
Arthur Ronald F D Garcia
Yeah, I noticed that but guessed it was a typo (since the context implies it is possible - "allows you to use association annotations in an embeddable object") and it was supposed to say "or". Perhaps it should say "but not" rather than "nor".I suppose I could break out some helper classes containing the methods, and leave the @OneToManys in the main class. Seems a bit dodgy though.Any other suggestions?Thanks a lot Arthur and Juha for replying.
Ned Lowe
You are right. It sounds better as "but not @*ToMany"
Arthur Ronald F D Garcia
Back from holidays - looked at this again. The @Embeddable works fine. The reason it wasn't working for me was just because I am programming to interfaces, so I needed to add a @Target annotation to the actual class. To reiterate - it *is* possible to put a Collection in an @Embeddable.
Ned Lowe
A: 

Hi,

Although i do not know a default strategy when using a wrapper class, you could use a Hibernate Interceptor to initialize your wrapper's by overriding onLoad method. Something like

public class WrapperInterceptor extends EmptyInterceptor {

    private Session session;

    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof MyClass) {
             MyClass myClass = (MyClass) entity;

             Query query = session.createQuery(<QUERY_TO_RETRIEVE_WRAPPED_ENTITY_GOES_HERE>);

             WrappedEntity wrappedEntity = query.list().get(0);

             myClass.setWrapperClass(new WrapperClass(wrappedEntity));
        }
    }

    public void setSession(Session session) {
        this.session = session;
    }
}

Takes care of the following:

A client using this interceptor must set Session property

So your code looks like this one

WrapperInterceptor interceptor = new WrapperInterceptor();

Session session = sessionFactory().openSession(interceptor);

Transaction tx = session.beginTransaction();

interceptor.setSession(session);

MyClass myClass = (MyClass) session.get(newItem, myClassId); // Triggers onLoad event

tx.commit();
session.close();

Or use Spring AOP to do the same task. See Domain Driven Design with Spring and Hibernate

If you know another strategy, share it with us.

regards,

Arthur Ronald F D Garcia
Arthur - I think I see where you're going with this suggestion - but if anything this makes life even more complicated :-)Worst case scenario - I can put the @OneToMany collections in the main class, then pass a reference to it to a separate class containing all the methods.Thanks again.
Ned Lowe