我有一个复合键叫做ContractServiceLocationPK
,由三个类型为long的id(contractId
, locationId
, serviceId
)组成,存储在一个可嵌入类中。使用这个复合键的类是ContractServiceLocation
,它使用@MapsId
注释将这些id映射到它们的对象上。以下是它的样子(已经删除了setter/getter和不相关的属性):
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;
}
合同服务位置主键
@Embeddable
public class ContractServiceLocationPK implements Serializable {
private long contractId;
private long locationId;
private long serviceId;
}
合同服务位置
@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;
}
当试图以任何方式(直接或通过合同)持久化ContractServiceLocation类型的对象时,我会得到以下错误:
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
我的假设是JPA/Hibernate期望一个Contract对象而不是一个长变量,但如果我将嵌入式中的变量从长更改为其类型,那么我会得到“由关系'contract'映射的ID的类型与目标实体的主键类不一致。” 如果我尝试使用id类而不是嵌入式,则在Contract的OneToMany映射中使用“mappedby”,我会收到“在属性'contractServiceLocation'中,“mapped by”属性'contract'对于此关系具有无效的映射类型。”要创建具有多个ManyToOne映射的复合键,我该怎么办?编辑:添加了一个片段,在其中我尝试保存项目:
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();
这个原因是因为我在开发应用程序的其余部分之前,先生成数据库并测试项目。
编辑2:我已经重写了我的模型,现在一切似乎都正常了,除了在Eclipse中我遇到了一个持久性错误。当前情况如下:
Contract-没有变化(除了去掉了Eager loading)
ContractServiceLocationPK-现在是一个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()
}
合同服务位置
@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()
}
目前看起来是正常的。它创建了一个复合键,并与所有复合属性保持多对一的关系。但有些奇怪。在Contract中,Eclipse标记OneToMany注释下的mappedBy参数为ContractServiceLocation集合,并显示错误消息:“在属性“contractServiceLocation”中,“mapped by”属性“contract”的映射类型无效。”我猜这是因为在ContractServiceLocation中定义的Contract属性没有ManyToOne注释,而是在复合类中定义的。我是不是遇到了“非兼容JPA但可以使用Hibernate”的陷阱,还是出了什么问题?
ContractServiceLocation
的构造函数来初始化ContractServiceLocationPK
,也可以正常工作。 - Youans