Piotr Nowicki
Piotr Nowicki

Reputation: 18224

Hibernate exception with @MapsId, @EmbeddedId

I've got a problem with @MapsId annotation and @EmbeddedId. When running a code in Hibernate I get:

Caused by: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.entities.EmployeeId.serverId

But, let's start from the beginning... I have a composite primary key for entity Employee which consists of foreign keys to two other entities (Server and Website). In order to have a clean design I use entity relationships in Employee entity which should be reflected in the EmployeeId embeddable. The example is quite simple and is as follows:

@Entity
public class Server implements Serializable {

    @Id
    @GeneratedValue
    private int id;

    private String url;

    public Server() {}

    public Server(String name) {
        this.url = name;
    }
}

@Entity
public class Website implements Serializable {

    @Id
    @GeneratedValue
    private int id;

    private String name;

    public Website() {}

    public Website(String name) {
        this.name = name;
    }
}

@Embeddable
public class EmployeeId implements Serializable {

    protected int websiteId;
    protected int serverId;
}

@Entity
public class Employee implements Serializable {

    @EmbeddedId
    private EmployeeId id;

    private String firstName;

    @ManyToOne
    @MapsId("serverId")
    private Server server;

    @OneToOne
    @MapsId("websiteId")
    private Website website;

    public Employee() {}

    public Employee(String firstName, Server server, Website website) {
        this.firstName = firstName;
        this.server = server;
        this.website = website;
    }
}

Now I have some simple test method written in Java SE (note this is a regular class executed from static main method - it is not a JUnit class):

private void executeTest() {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("standaloneTests");

    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    Server s = new Server("BigServer");
    em.persist(s);

    Website w = new Website("domain.com");
    em.persist(w);

    Employee e = new Employee("John", s, w);
    em.persist(e);

    tx.commit();

    em.close();
    emf.close();
}

As you can see, I'm not doing any fancy stuff here - just set entities in the Employee object and persist it. As I understand, the @MapsId annotation should reflect the state of the annotated entity in the EmbeddedId.

Now the problem is that in EclipseLink everything works smoothly and entities are properly persisted. When I change the JPA 2.0 provider to Hibernate (4.0 CR5) it throws me an PropertyAccessException:

Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.entities.EmployeeId.serverId at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1353) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1281) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1287) at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:853) at com.test.Standalone.executeTest(Standalone.java:101) at com.test.Standalone.main(Standalone.java:35) Caused by: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.entities.EmployeeId.serverId at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:150) at org.hibernate.mapping.Component$ValueGenerationPlan.execute(Component.java:436) at org.hibernate.id.CompositeNestedGeneratedValueGenerator.generate(CompositeNestedGeneratedValueGenerator.java:121) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:120) at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:78) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:180) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:136) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:64) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:729) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:705) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:709) at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:847) ... 2 more Caused by: java.lang.NullPointerException at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:36) at sun.reflect.UnsafeIntegerFieldAccessorImpl.set(UnsafeIntegerFieldAccessorImpl.java:57) at java.lang.reflect.Field.set(Field.java:657) at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:138) ... 13 more

I don't know where from the NullPointerException might come from - as you can see all entity properties are set.

I don't even know why on earth Hibernate needs a setter, but just to be sure I've provided setters for every field (includng IDs) for Employee, Website and Server entities. As expected - nothing changed and Hibernate still cannot find (already present) setter.

Do you think it might be some bug or I just didn't understand some part of the JPA 2.0 contract?

Thanks in advance!

Upvotes: 10

Views: 14437

Answers (4)

lac_dev
lac_dev

Reputation: 1449

I had the same problem and fixed it by doing the following, when Hibernate attempts to set the values for Employee.id, employee.id is null, just instantiate employee.id and you'll be set.


@Entity
public class Employee implements Serializable {

    @EmbeddedId
    private EmployeeId id = new EmployeeId();

    private String firstName;

    @ManyToOne
    @MapsId("serverId")
    private Server server;

    @OneToOne
    @MapsId("websiteId")
    private Website website;

    public Employee() {}

    public Employee(String firstName, Server server, Website website) {
        this.firstName = firstName;
        this.server = server;
        this.website = website;
    }
}

Upvotes: 3

Riccati
Riccati

Reputation: 447

Solution Here: Got the same problem, and got it working now.

For it to work, the EmployeeId of your Employee class should be instantiated either in the constructor of Employee, or in the SessionBean before making the persistence.

Otherwise, it tries to populates the values of your embeddedid with it being null.

Not sure if this is normal or if it's a bad implementation of JPA specifications. I think the latter since the constructor for your PK is defined and hibernate should be able to call it by itself.

Upvotes: 19

oak
oak

Reputation: 3028

its an old but might be useful for some one

try to

@Entity
public class Employee implements Serializable {

@EmbeddedId
private EmployeeId id;

private String firstName;

@ManyToOne
@MapsId("id")
@JoinColumn(name ="serverId")
private Server server;

@OneToOne
@MapsId("id")
@JoinColumn(name= "websiteId")
private Website website;

Upvotes: 2

jFrenetic
jFrenetic

Reputation: 5552

See this issue. It simply states that

org.hibernate.PropertyAccessException may be thrown if an Entity contains the following conditions:

  1. Uses @EmbeddedId
  2. Uses @JoinTable on a collection or association property/field, which references another property/field of the entity.

As you see, the only 'workaround' suggested so far is Do not use @EmbeddedId, which is kind of weird.

Upvotes: 5

Related Questions