I've spent hours (and hours!) trying to figure this out myself and I'm going bonkers - I need help please with @ManyToAny. First a bit of background:
What I'm trying to do is straightforward and must be quite common now that we're all using generics and coding to interfaces rather than implementations. I have an entity that contains a Map (LinkedHashMap as it happens) of other entities. The Map is generic, and the generic type (DataChangeListener) is an interface:
Code:
private Map<Long, DataChangeListener> changeListeners = new LinkedHashMap<Long, DataChangeListener>();
The DataChangeListener interface is straightforward enough (HasID is another interface that mandates the get/setID() fields but I've overridden them in this interface too in case it helps!):
Code:
public interface DataChangeListener extends HasID
{
@Override
public long getID();
@Override
public void setID(long newID);
public void listenedDataHasChanged(DataChangeEvent event);
}
I've figured-out that I can't just use @OneToMany on the Map, because it's typed with an interface not an abstract class or implementation. (that's a pity, but I can understand why this doesn't work). So, I realised that @ManyToAny is the solution to my problems! (@OneToAny would be ideal but doesn't exist) It allows me to tell Hibernate to use TWO columsn (in a separate table as it is @ManytoAny not @OneToAny) that contain both the primary key of the DataChangeListener but also an implementor type-flag so Hibernate knows what the implementation class of the DataChangeListener interface is. Everything seems great. Except.... when I annotate using the following:
Code:
@AnyMetaDef(name = "DataChangeListeners", idType = "long", metaType = "string", metaValues = { @MetaValue(value = "DynamicMonthImpl", targetEntity = DynamicMonthImpl.class) })
@ManyToAny(metaDef = "DataChangeListeners", metaColumn = @Column(name = "listener_type"))
@JoinTable(name = "DatachangeManagerToListenersMapping", joinColumns = { @JoinColumn(name = "listenerID", referencedColumnName = "iD") }, inverseJoinColumns = @JoinColumn(name = "dataChangeManagerID", referencedColumnName = "iD"))
private Map<Long, DataChangeListener> changeListeners = new LinkedHashMap<Long, DataChangeListener>();
I get an error on deployment as follows:
Code:
Caused by: java.lang.UnsupportedOperationException: collection of any not supported yet
at org.hibernate.ejb.metamodel.AttributeFactory.determineAttributeMetadata(AttributeFactory.java:455)
at org.hibernate.ejb.metamodel.AttributeFactory.buildAttribute(AttributeFactory.java:91)
at org.hibernate.ejb.metamodel.MetadataContext.wrapUp(MetadataContext.java:185)
at org.hibernate.ejb.metamodel.MetamodelImpl.buildMetamodel(MetamodelImpl.java:64)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:91)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:904)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:889)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:73)
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.createContainerEntityManagerFactory(PersistenceUnitServiceImpl.java:162)
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.start(PersistenceUnitServiceImpl.java:85)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1811) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1746) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
... 3 more
But I can't figure out why. Firstly, Map isn't a Collection (but I guess the exception is thrown because somewhere inside the LinkedHashMap implementation there is a Collection?). Secondly though, if this function doesn't work with Collection instances, how do I persist my Map?
This must be a REALLY common use-case, surely? I have looked through my class model, and I can't change DataChangeListener to be an abstract class even if I wanted-to (no multiple-inheritance in Java, and DataChangeListener isn't the only interface in the heirarchy) - and it feels wrong to have to remove interfaces from the model when they are such a powerful feature of the language.
Maybe I have to drop Map altogether and use a List? That would be considerable less efficient in-use (I'd have to iterate-over all instances to find the one I want).
Where am I going wrong guys? How does everyone else handle this?
TIA! James.