I have a composite key ContractServiceLocationPK consisting of three identifiers ( contractId , locationId , serviceId ) of type long in a nested class. The class that uses this composite key, ContractServiceLocation , maps these identifiers using the @MapsId annotation to their objects. Here's what it looks like (remote 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 I try to save an object of type ContractServiceLocation in any way (directly or through a 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 from 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 to use the id class instead of embeddable, then mappedby in the Contract OneToMany mapping, I get In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship. . What to do to create a composite key with multiple ManyToOne ?
EDIT: Added snippet in which I am trying to save elements:
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, instead of being in some DAO, is because I first create a database and test the elements before starting to develop the rest of the application.
EDIT 2: I rewrote my models and now everything works, except for Eclipse. I get a persistent error. Here's how things look:
Contract - No change (except for unloading Eager)
ContractServiceLocationPK - now is 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() }
Now it works correctly. It creates a composite key and maintains a many-to-one relationship with all the composite properties. However, there is something strange. At the mappedby “Contract Eclipse”, mark mappedby in the mappedby 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 assume this is because the Contract property defined in ContractServiceLocation does not have @ManyToOne annotation, but it is defined in a composite class. I came across "inappropriate JPA, but working with Hibernate" or what is happening here?