Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 763
MrPlow Avatar asked May 19 '14 11:05

MrPlow


2 Answers

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.

like image 154
David Bucek Avatar answered Oct 22 '22 00:10

David Bucek


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
like image 6
Bilbo Baggins Avatar answered Oct 21 '22 22:10

Bilbo Baggins