使用ManyToOne获取org.hibernate.PropertyAccessException的JPA Composite键:无法通过反射设置器设置字段值

时间:2014-05-19 11:20:08

标签: java hibernate jpa one-to-many many-to-one

我有一个复合键ContractServiceLocationPK由嵌入类中的三个id(contractIdlocationIdserviceId)类型组成。使用此复合键ContractServiceLocation的类使用@MapsId注释将这些ID映射到其对象。这是它的样子(删除了setter / getters和不相关的属性):

合同

@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;
}

当尝试以任何方式(直接或通过合同)持久保存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对象而不是long变量,但是如果我将embeddable中的变量从long更改为它们的类型,那么我得到The type of the ID mapped by the relationship 'contract' does not agree with the primary key class of the target entity.。如果我尝试在合同的OneToMany映射中使用id class而不是embeddable然后mappedby,我会得到In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.。如何制作具有多个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();

它看起来像这样而不是在某个DAO中的原因是因为我在开发应用程序的其余部分之前先生成数据库并测试项目。

编辑2:我重写了我的模型,现在一切似乎都有效,除了在Eclipse中我遇到了持久性错误。以下是目前的情况:

合同 - 无变更(除非删除了预先加载)

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()
}

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()
}

这似乎现在可以正常工作。它创建一个复合键,并与所有复合属性保持多对一关系。然而,有一些奇怪的东西。在Contract eclipse中,对mappedBy集合的@OneToMany注释标记ContractServiceLocation,并显示错误消息In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.。我假设这是因为Contract中定义的ContractServiceLocation属性没有@ManyToOne注释,但这是在复合类中定义的。我是否偶然发现了不符合规范的JPA,但是使用了Hibernate&#34;陷阱或者这里发生了什么?

2 个答案:

答案 0 :(得分:21)

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

你必须实施&#34; ContractServiceLocationPK id&#34;在您的ContractServiceLocation类中。替换行:

  

@EmbeddedId   ContractServiceLocationPK id;

用这个:

  

@EmbeddedId   ContractServiceLocationPK id = new ContractServiceLocationPK();

然后它应该工作。因为Hibernate试图在里面设置属性,但在NullPointerException上失败。

答案 1 :(得分:4)

你需要将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