Clave compuesta JPA con ManyToOne obteniendo org.hibernate.PropertyAccessException: no se pudo establecer un valor de campo por medio del reflector
Tengo una clave compuestaContractServiceLocationPK
hecho de tres id (contractId
, locationId
, serviceId
) de tipo largo en una clase incorporable. La clase que usa esta clave compuesta,ContractServiceLocation
, asigna estos identificadores, utilizando@MapsId
anotación, a sus objetos. Así es como se ve (setters / getters eliminados y propiedades irrelevantes):
Contrato
@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;
}
Cuando intento persistir un objeto de tipo ContractServiceLocation de cualquier manera (directamente o mediante contrato) obtengo:
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
Mi suposición es que JPA / Hibernate espera un objeto Contract en lugar de una variable larga, pero si cambio las variables en incrustables de largo a su tipo, obtengoThe type of the ID mapped by the relationship 'contract' does not agree with the primary key class of the target entity.
. Si intento usar la clase id en lugar de incrustable, entoncesmappedby
en el mapeo OneToMany de Contract que obtengoIn attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.
. ¿Qué debo hacer para hacer una clave compuesta con múltiplesManyToOne
mapeos?
EDITAR: Agregué un fragmento donde trato de persistir los elementos:
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();
La razón por la que se ve así en lugar de estar en algún DAO es porque estoy generando la base de datos y probando los elementos primero antes de continuar con el desarrollo del resto de la aplicación.
EDITAR 2: He reescrito mis modelos y ahora todo parece funcionar, excepto en Eclipse, obtengo un error persistente. Así es como se ven las cosas actualmente:
Contrato: sin cambios (excepto que se eliminó la carga Eager)
ContractServiceLocationPK: ahora es una clase de ID
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()
}
Esto parece funcionar correctamente por ahora. Crea una clave compuesta y mantiene una relación de muchos a uno con todas las propiedades compuestas. Sin embargo hay algo raro. En contrato marcas de eclipsemappedBy
sobre el@OneToMany
anotación para elContractServiceLocation
colección con el mensaje de errorIn attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.
. Supongo que esto se debe a que elContract
propiedad definida enContractServiceLocation
no tiene un@ManyToOne
anotación, pero eso se define en la clase compuesta. ¿Me topé con la trampa "JPA no conforme pero trabajando con Hibernate" o qué está pasando aquí?