JPA复合键与ManyToOne一起使用时出现org.hibernate.PropertyAccessException: could not set a field value by reflection setter of错误

17

我有一个复合键叫做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”的陷阱,还是出了什么问题?

3个回答

45

对于你原始的问题(未修改的变体):

你需要在ContractServiceLocation类中实例化 "ContractServiceLocationPK id"。 将以下行:

@EmbeddedId ContractServiceLocationPK id;

替换为以下代码:

@EmbeddedId ContractServiceLocationPK id = new ContractServiceLocationPK();

然后就可以正常工作了。 因为Hibernate正在尝试设置内部属性,但在NullPointerException上失败。


如果您使用 ContractServiceLocation 的构造函数来初始化 ContractServiceLocationPK,也可以正常工作。 - Youans
很高兴我找到了这个问题和答案。这真的很“惊人”。例如,EclipseLink 2.6.4在相同的上下文中完全正常运行。 - Sergey Vyacheslavovich Brunov
谢谢。糟糕的情况是,如此重要和有价值的库Hibernate忽略了在连接表中具有附加字段的多对多关系。这是一个核心、基本的RDBMS设计组件。而且必须创建一个嵌入式ID(不要忘记实例化它)并经过许多步骤才能使其正常工作,这是不真实的,并且典型的Hibernate社区非常狂热于Java,却错过了基础知识,在这种情况下,是关于RDBMS的... - tom
4
经过三天的搜索和阅读这个答案(并验证它是我的情况)。 内心尖叫 - Draaksward

6
你需要将getter和setter方法放在@Embeddable类中。 hashCode()和equals()方法也需要放在该类中,但我在你发布的类中没有看到它们。

为了保存ContractServiceLocation,需要首先保存以下对象,因为你正在使用它们的ID作为ContractServiceLocation的复合键,是吗?你现在创建的是新对象,所以它们会没有ID,因为它们还没有持久化。因此,你需要先持久化这些对象,并将已持久化的对象设置到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

0

在创建连接实体的新实例时,@EmbeddedId复合主键字段应手动初始化,因为Hibernate无法通过反射设置该值

因此,在ContractServiceLocation构造函数中设置ContractServiceLocationPK复合类字段的值


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接