JPA/Hibernate Static Metamodel Attributes not Populated -- NullPointerException

Viewed 23138

I would like to use JPA2 Criteria API with metamodel objects, which seems to be pretty easy:

...
Root<JPAAlbum> albm = cq.from(JPAAlbum.class);
... albm.get(JPAAlbum_.theme) ... ;

but this Root.get always throws a NullPointerException. JPAAlbum_.theme was automatically generated by Hibernate and looks like

public static volatile SingularAttribute<JPAAlbum, JPATheme> theme;

but it's obviously never populated.

Am I missing a step in the initialization of the framework ?

EDIT: here is a snippet of how I use JPA and the metamodel when it's crashing:

    CriteriaBuilder cb = em.getCriteriaBuilder();

    CriteriaQuery<JPAAlbum> cq = cb.createQuery(JPAAlbum.class) ;
    Root<JPAAlbum> albm = cq.from(JPAAlbum.class);
    cq.where(cb.equal(albm.get(JPAAlbum_.theme).get(JPATheme_.id),
                        session.getTheme().getId())) ;

(JPAAlbum_ is a class, so I just import before) and the associated stacktrace:

Caused by: java.lang.NullPointerException
    at org.hibernate.ejb.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:138)
    at net.wazari.dao.jpa.WebAlbumsDAOBean.getRestrictionToAlbumsAllowed(WebAlbumsDAOBean.java:55)

EDIT 2:

In the JBoss EntityManager guide, I can see that

When the Hibernate EntityManagerFactory is being built, it will look for a canonical metamodel class for each of the managed typed is knows about and if it finds any it will inject the appropriate metamodel information into them, as outlined in [JPA 2 Specification, section 6.2.2, pg 200]

I could also verify with

     for (ManagedType o : em.getMetamodel().getManagedTypes()) {
            log.warn("___") ;
            for (Object p : o.getAttributes()) {
                log.warn(((Attribute)p).getName()) ;
            }
        }

that Hibernate is aware of my metamodel, the attribute names are written, however

   log.warn("_+_"+JPAPhoto_.id+"_+_") ;

remains desperately empty ...

EDIT3: here is the JPAAlbum entity and its metamodel class.

What else can I tell about my configuration ...

  • I use Hibernat 3.5.6-Final (according to META-INF/MANIFEST.MF),

  • deploy on Glassfish 3.0.1

  • from Netbeans 6.9.1;

  • and the application relies on EJB 3.1,

I hope it will help !

EDIT 4:

unfortunately, the JUnit test leads to the same exception:

java.lang.NullPointerException
    at org.hibernate.ejb.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:138)
    at net.wazari.dao.test.TestMetaModel.foo(TestMetaModel.java:55)

A much simpler project is available here/tarball. It only contains my entities and their metamodel, plus a JUnit test (foo crashes with metamodel, bar is okay with the usual Query.

EDIT 5:

You should be able to reproduce the problem by downloading the tarball, building the project:

ant compile
or
ant dist

and start the JUnit test net.wazari.dao.test.TestMetaModel

 CLASSPATH=`sh runTest.sh` java org.junit.runner.JUnitCore  net.wazari.dao.test.TestMetaModel

(edit runTest.sh to point CLASSPATH to the right location of your JUnit4-5 jar)

All the hibernate dependencies I use should be included in the archive.

9 Answers

I had the same issue and it was fixed by putting the Model and Model_ class into the same package.

2019-04-24

The usual issue for unpopulated metamodel class attributes, is when the metamodel classes are in a different package than the corresponding managed classes.

The latest, JPA 2.2 specification still requires to have your metamodel classes in the same package as your corresponding managed classes.

Reference: Page 238, §6.2.1.1 Canonical Metamodel

FYI, I encountered a case where Hibernate creates a metamodel attribute but never initializes it, leading to a NullPointerException's when trying to use it.

public class Upper {
    public String getLabel() { return this.label; }
    public void setLabel(String label) { this.label = label; }
}

public class Lower extends Upper {
   @Override
   public String getLabel() { return super.getLabel(); }
}

Hibernate generate a label attribute declaration in both classes:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Upper.class)
public abstract class Upper_ {
    public static volatile SingularAttribute<Upper, String> label;
}

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Lower.class)
public abstract class Lower_ {
    public static volatile SingularAttribute<Lower, String> label;
}

...and it will initialize Upper_.label but leave Lower_.label equal to null.

Boom.

The class and the metaModel should be in the same package, i.e.

Folder entities:

  • Eje
  • Eje_
  • Element
  • Element_

I attached one example of the metamodel code

import javax.annotation.Generated;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
import java.util.Date;

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Eje.class)
public abstract class Eje_ {
    public static volatile SingularAttribute<Eje, Integer> id;
    public static volatile SingularAttribute<Eje, String> name;
    public static volatile SingularAttribute<Eje, Integer> users;
    public static volatile SingularAttribute<Eje, Date> createdAt;
    public static volatile SingularAttribute<Eje, Date> updatedAt;
    public static volatile SetAttribute<Eje, FactorCritico> factorCriticos;
}

If nothing of above resolve this NPE issue, you also can check whether you are using List in your Entities relationships instead of Set.

I found out that using List's it is needed to declare ListAttribute instead of SetAttribute in the metamodel, otherwise, it provoke a NullPointerException and if you don't see the whole stack trace you will not notice that the metamodel was not initialized by your JPA specification.

Debbie's answer got me half of the way there.

There is another "name matching" voodoo gotcha.

Short version:

The "names" have to match between Model and the MetaModel for the "properties".

Longer version:

I was using non-easy-peezy names.

First the entities:

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.LinkedHashSet;
import java.util.Set;

@Entity
@Table(name = "DepartmentTable")
public class DepartmentJpaEntity implements Serializable {

    @Id
    @Column(name = "DepartmentKey", unique = true)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long departmentKey;
    @Column(name = "DepartmentName", unique = true)
    private String departmentName;
    @Column(name = "CreateOffsetDateTime", columnDefinition = "TIMESTAMP WITH TIME ZONE")
    private OffsetDateTime createOffsetDateTime;
    @OneToMany(
            mappedBy = "parentDepartmentJpaEntity",
            cascade = CascadeType.PERSIST,
            orphanRemoval = true,
            fetch = FetchType.LAZY /* Lazy or Eager here */
    )

    private Set<EmployeeJpaEntity> employeeJpaEntities = new LinkedHashSet<>();

}

and

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
import java.time.OffsetDateTime;

@Entity
@Table(name = "EmployeeTable")
public class EmployeeJpaEntity implements Serializable {

    @Id
    @Column(name = "EmployeeKey", unique = true)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long employeeKey;

    @Column(name = "Ssn")
    private String ssn;

    @Column(name = "LastName")
    private String lastName;

    @Column(name = "FirstName")
    private String firstName;

    @Column(name = "CreateOffsetDateTime", columnDefinition = "TIMESTAMP WITH TIME ZONE")
    private OffsetDateTime createOffsetDateTime;

    //region Navigation
    
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = DepartmentJpaEntity.class, cascade = CascadeType.PERSIST )
    @JoinColumn(name = "DepartmentForeignKey")
        private DepartmentJpaEntity parentDepartmentJpaEntity;
    //endregion

}

Note, my somewhat not default names. pay attention to the OnetoMany and ManyToOne object names.

Now, my meta-models:

import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(DepartmentJpaEntity.class)

public class DepartmentJpaEntity_ {
  public static volatile SingularAttribute<DepartmentJpaEntity, Long> departmentKey;
  public static volatile SetAttribute<DepartmentJpaEntity, EmployeeJpaEntity> employees; /* DOES NOT WORK :(  */
}

but this (below) will work, because I'm magic voodoo'ing the same names:

import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(DepartmentJpaEntity.class)

public class DepartmentJpaEntity_ {
  public static volatile SingularAttribute<DepartmentJpaEntity, Long> departmentKey;
  public static volatile SetAttribute<DepartmentJpaEntity, EmployeeJpaEntity> employeeJpaEntities;
}

and the other one:

import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(EmployeeJpaEntity.class)

public class EmployeeJpaEntity_ {
  public static volatile SingularAttribute<EmployeeJpaEntity, Long> employeeKey;
  public static volatile SingularAttribute<EmployeeJpaEntity, DepartmentJpaEntity> parentDepartment;  /*does NOT work...its null at run time...no voodoo name matching */
}

But this does work:

import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(EmployeeJpaEntity.class)

public class EmployeeJpaEntity_ {
  public static volatile SingularAttribute<EmployeeJpaEntity, Long> employeeKey;
  public static volatile SingularAttribute<EmployeeJpaEntity, DepartmentJpaEntity> parentDepartmentJpaEntity;
}

One of the reasons I purposely use non "standard" names for things is to flush out these voodoo setup issues at the beginning......in order to try and fail early...vs a failLater (hopefully in QA, not in production) issue(s) because someone renamed a property which seems benign at the time.

Related