JPA Composite key with ManyToOne getting org.hibernate.PropertyAccessException: could not set a field value by reflection setter of
Asked Answered
T

3

18

I have a composite key ContractServiceLocationPK made out of three id's (contractId, locationId, serviceId) of type long in an embeddable class. The class which uses this composite key, ContractServiceLocation, maps these ids, using @MapsId annotation, to their objects. Here's how it looks like (removed setters/getters and irrelevant properties):

Contract

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

    public Contract() {
    }

    @Id
    @GeneratedValue
    private long id;
    @OneToMany(mappedBy = "contract", cascade = CascadeType.ALL, fetch= FetchType.EAGER)
    Collection<ContractServiceLocation> contractServiceLocation;
}

ContractServiceLocationPK

@Embeddable
public class ContractServiceLocationPK implements Serializable {

    private long contractId;
    private long locationId;
    private long serviceId;
}

ContractServiceLocation

@Entity
@Table(name="Contract_Service_Location")
public class ContractServiceLocation implements Serializable {

    @EmbeddedId
    ContractServiceLocationPK id;

    @ManyToOne(cascade = CascadeType.ALL)
    @MapsId("contractId")
    Contract contract;

    @ManyToOne(cascade = CascadeType.ALL)
    @MapsId("locationId")
    Location location;

    @ManyToOne(cascade = CascadeType.ALL)
    @MapsId("serviceId")
    Service service;    

    BigDecimal price;
}

When attempting to persist an object of type ContractServiceLocation in any way(directly or throught contract) I get:

Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.model.ContractServiceLocationPK.contractId
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1187)
    at com.test.MainTest.main(MainTest.java:139)
Caused by: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.model.ContractServiceLocationPK.contractId
    at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:134)
    at org.hibernate.mapping.Component$ValueGenerationPlan.execute(Component.java:441)
    at org.hibernate.id.CompositeNestedGeneratedValueGenerator.generate(CompositeNestedGeneratedValueGenerator.java:121)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:117)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:84)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:206)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:149)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:75)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:811)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:784)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1181)
    ... 1 more
Caused by: java.lang.NullPointerException
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source)
    at sun.reflect.UnsafeLongFieldAccessorImpl.set(Unknown Source)
    at java.lang.reflect.Field.set(Unknown Source)
    at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:122)
    ... 12 more

My assumption is that JPA/Hibernate expects a Contract object instead of a long variable, but if I change the variables in embeddable from long to their type then I get The type of the ID mapped by the relationship 'contract' does not agree with the primary key class of the target entity.. If I try using id class instead of embeddable then mappedby in Contract's OneToMany mapping I get In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.. What should I do to make a composite key with multiple ManyToOne mappings?

EDIT: Added a snippet where I try to persist the items:

    Service service = new Service();
    // Set all service properties       
    Contract contract = new Contract();
    // Set all contract properties
    Location location = new Location();
    // Set all location properties
    ContractServiceLocation csl = new ContractServiceLocation();
    csl.setContract(contract);
    csl.setLocation(location);
    csl.setService(service);
    Collection<ContractServiceLocation> cslItems = new ArrayList<>();
    cslItems.add(csl);

    em.getTransaction().begin();
    em.persist(location);
    em.persist(service);
    em.persist(csl);
    em.persist(contract);
    em.getTransaction().commit();

The reason it looks like this instead of being in some DAO is because I'm generating the database and testing the items first before I get on with developing the rest of the app.

EDIT 2: I've rewrote my models and now everything seems to work except in Eclipse I get a persistent error. Here's how the things currently look:

Contract - No change (Except removed the Eager loading)

ContractServiceLocationPK - Is now an ID class

public class ContractServiceLocationPK implements Serializable {

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)      
    @JoinColumn(name = "contract_id")
    private Contract contract;
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)      
    @JoinColumn(name = "location_id")
    private Location location;
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)      
    @JoinColumn(name = "service_id")
    private Service service;

    //getters and setters
    //overridden equals() and hashCode()
}

ContractServiceLocation

@Entity
@Table(name="Contract_Service_Location")
@IdClass(ContractServiceLocationPK.class)
public class ContractServiceLocation implements Serializable {

    @Id
    Contract contract;

    @Id
    Location location;

    @Id
    Service service;    

    BigDecimal price;
        //getters and setters
        //overridden equals() and hashCode()
}

This appears to work correctly for now. It creates a composite key and maintains a many-to-one relationship with all the composite properties. However there is something weird. In Contract eclipse marks mappedBy on the @OneToMany annotation for the ContractServiceLocation collection with the error message In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.. I'm assuming that this is because the Contract property defined in ContractServiceLocation doesn't have a @ManyToOne annotation, but that is defined in the composite class. Did I stumble upon "non-compliant JPA but working with Hibernate" trap or what's going on here?

Tabitha answered 19/5, 2014 at 11:20 Comment(0)
N
46

For your original question (not modified variant):

You have to instatiate "ContractServiceLocationPK id" in your ContractServiceLocation class. Replace line:

@EmbeddedId ContractServiceLocationPK id;

with this:

@EmbeddedId ContractServiceLocationPK id = new ContractServiceLocationPK();

Then it should works. Because Hibernate is trying to set properties inside, but fail on NullPointerException.

Nanceynanchang answered 14/12, 2014 at 9:22 Comment(4)
Also works if you used Constructor of ContractServiceLocation to initialize ContractServiceLocationPKStefan
Glad that I have found this question and this answer. This is quite «surprising». For example, EclipseLink 2.6.4 works just fine in the same context.Masakomasan
Thanks. Horrible situation is that such an important and valuable lib, hibernate, ignores many to many relationships with additional fields in join table. This is a core, basic, RDBMS design component. And that one must create an embedded id (dont forget to instantiate it) and go through so many machinations to get it to work is UNREAL. and so typical of hibernate and that community that is so psyched for Java and misses the basics, in this case, of RDBMS...Liturgist
After 3 days of search and reading this answer (and verifying it was my case). Internal screamingMckinleymckinney
L
6

You need to put the getters and setters in your @Embeddable class as well, your hashCode() and equals() methods will go in to that class which I couldn't see in your class posted here.

In order to save the ContractServiceLocation, following objects needs to be saved first because you are using their ids as composite key for the ContractServiceLocation, right? Here what you are doing is you are creating these as new objects so obviously they won't have their id, because they are not persisted. so you need to persist them first and use the persisted objects and set objects into the ContractServiceLocation.

    Service service = new Service();
    // Set all service properties       
    Contract contract = new Contract();
    // Set all contract properties
    Location location = new Location();
    // Set all location properties
Laina answered 19/5, 2014 at 11:34 Comment(0)
U
0

When creating a new instance for the joined entity, the @EmbeddedId composite primary key field should be initialized manually as Hibernate would not be able to set the value via reflection

so set values of ContractServiceLocationPK composite class fields in ContractServiceLocation constructor

Uterus answered 14/9, 2021 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.