Hibernates @Any
Associations of type org.hibernate.type.AnyType exist since Hibernate 3.0. They can be used to model associations to multiple entity types without a common ancestor entity (non-polymorphic).You can configure Hibernates O/R mapping through classic hbm.xml mapping files or through Hibernate Annotations which consist out of JPA annotations and Hibernate-specific annotation extensions. The AnyType has no counterpart in JPA 1/2 and up to Hibernate 3.3.1.GA you could configure this feature only through XML mappings. Since then the Hibernate-specific annotation @Any exists (another explanation).
As example we use a persistent business log that can contain references to entities as additional information:
@Entity
@Getter
@Setter
public class Blog extends BaseEntity {
private static final long serialVersionUID = -2669494633145498742L;
@ManyToOne
private User user;
@NotNull
private Date date;
@NotNull
@Column(length = Number.SIZE31)
@Enumerated(EnumType.STRING)
private BlogCategorie category;
@Size(max = Number.SIZE255)
private String message;
// no JPA metamodel for @Any!
@Any(metaColumn = @Column(name = "ref_disc"))
@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(targetEntity = User.class, value = "U"),
@MetaValue(targetEntity = Event.class, value = "E") })
@JoinColumn(name = "ref_id")
private BaseEntity ref;
@PostLoad
@SuppressWarnings("unused")
private void postLoad() {
// default mode fetch = EAGER doesn't work for @Any in Hibernate <= 4.1.1
this.ref.getId();
}
// or as example for many to any associations
@ManyToAny(metaColumn = @Column(name = "ref_disc"))
@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(targetEntity = User.class, value = "U"),
@MetaValue(targetEntity = Event.class, value = "E") })
@JoinTable(inverseJoinColumns = @JoinColumn(name = "blog_id"), joinColumns = @JoinColumn(name = "ref_id"))
private List<BaseEntity> refs;
}
In this case you often see this exception at application deployment:
ERROR [org.jboss.msc.service.fail] (MSC service thread 1-2) MSC00001: Failed to start service jboss.persistenceunit."app.war#database": org.jboss.msc.service.StartException in service jboss.persistenceunit."app.war#database": Failed to start service at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1767) [jboss-msc-1.0.2.GA.jar:1.0.2.GA] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) [rt.jar:1.7.0_03] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) [rt.jar:1.7.0_03] at java.lang.Thread.run(Thread.java:722) [rt.jar:1.7.0_03] Caused by: java.lang.UnsupportedOperationException: any not supported yet at org.hibernate.ejb.metamodel.AttributeFactory.determineAttributeMetadata(AttributeFactory.java:431) 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
JPA 2 Metamodel and the Criteria API
Since version 3.5 the Hibernate EntityManager tries to automatically generate a JPA 2 Metamodel and this fails for @Any attributes. This generation is enabled by default but can be disabled in the META-INF/persistence.xml:<property name="hibernate.ejb.metamodel.generation" value="disabled" />The Metamodel is necessary for the new Criteria API in JSF 2 (see next section) or you get such exceptions:
ERROR [app.controller.util.exception.ExceptionHandler] (http--127.0.0.1-8082-1) null: java.lang.NullPointerException at org.hibernate.ejb.criteria.QueryStructure.from(QueryStructure.java:136) [hibernate-entitymanager-4.1.1.Final.jar:4.1.1.Final] at org.hibernate.ejb.criteria.CriteriaQueryImpl.from(CriteriaQueryImpl.java:177) [hibernate-entitymanager-4.1.1.Final.jar:4.1.1.Final]Currently it isn't possible to represent a proper Metamodel for the Hibernate-specific @Any attribute. So you had to decide if you like to use the Criteria API or the @Any annotation.
Hibernate 4.1 to the rescue!
Hibernate 4.1 has a new default mode - enabled Metamodel generation which ignores unsupported attributes(see EntityManagerFactoryImpl.JpaMetaModelPopulationSetting.IGNORE_UNSUPPORTED):
"ignore unsupported"So now you can use both features together by default: Criteria API and @Any annotations.
Beware: You cannot use such @Any attributes in Criteria API queries because the method Path.get("attributeName") internally uses the generated Metamodel to resolve the Entity attributes. Hence this isn't possible:
final CriteriaBuilder cb = this.em.getCriteriaBuilder();
final CriteriaQuery<Blog> cq = cb.createQuery(Blog.class);
final Root<Blog> blog = cq.from(Blog.class);
cq.select(blog).where(cb.equal(blog.<User> get("ref"), user));
final Query q = this.em.createQuery(cq);
... q.getResultList();
But it's possible to use the Criteria API for JPA attribute types and JPA QL for @Any references like this:
final Query q = this.em.createQuery("SELECT b FROM Blog b WHERE b.ref = :ref");
q.setParameter("ref", user);
... q.getResultList();
In this case you should use a named query. You can also translate Criteria Queries into Hibernate or native SQL queries, append the ref-restriction and execute this. Example how to get native SQL from Criteria Query in Hibernate:
q.unwrap(org.hibernate.ejb.QueryImpl.class).getHibernateQuery().getQueryString()
You might also be interested in
- Build JBoss Application Server 7.x - currently JBoss AS 7 uses Hibernate 4.0.1, replace <version.org.hibernate>4.0.1.Final</version.org.hibernate> with 4.1.1 in pom.xml and build like described
- Java verbosity, JEE and Lombok - explains @Getter/@Setter annotations in above example entity