JPA:当孩子有嵌入式 ID 时如何级联保存实体?

Hei*_*erg 7 java hibernate jpa spring-data-jpa

我有以下父实体。

@Entity
@Table(name = "vital_entry")
public class VitalEntry {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "vital_entry_id", columnDefinition = "INT")
    private int vitalEntryId;

    @OneToMany(mappedBy = "vitalEntry", cascade = CascadeType.ALL)
    private Set<VitalEntryValues> vitalEntryValues = new HashSet<>();

    .... other fields

}
Run Code Online (Sandbox Code Playgroud)

这是我的孩子实体

@Entity
@Table(name = "vital_entry_values")
public class VitalEntryValues {

    @EmbeddedId
    private VitalEntryValuesId vevId;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
    @MapsId("vital_entry_id")
    @JoinColumn(name = "vital_entry_id", columnDefinition = "INT")
    private VitalEntry vitalEntry;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
    @MapsId("vital_id")
    @JoinColumn(name = "vital_id", columnDefinition = "INT")
    private Vitals vitals;

    @Column(name = "vital_unit_id", insertable = false, updatable = false)
    private int vitalUnitId;

    @Basic
    private double value;

    @Basic
    @Column(columnDefinition = "INT")
    private int type;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
    @JoinColumn(name = "vital_unit_id", columnDefinition = "INT")
    private VitalUnits vitalUnits;


    public VitalEntryValues() {
    }

    public VitalEntryValues(double value, int type, VitalEntry vitalEntry, VitalUnits vitalUnits, Vitals vitals) {
        this.value = value;
        this.type = type;
        this.vitalEntry = vitalEntry;
        this.vitalUnits = vitalUnits;
        this.vitals = vitals;
    }

}
Run Code Online (Sandbox Code Playgroud)

和 EmbeddedId 类:

@Embeddable
public class VitalEntryValuesId implements Serializable {

    @Column(name = "vital_entry_id", columnDefinition = "INT", insertable = false, updatable = false)
    private int vitalEntryId;

    @Column(name = "vital_id", columnDefinition = "INT", insertable = false, updatable = false)
    private int vitalId;
}
Run Code Online (Sandbox Code Playgroud)

我是这样保存的:

VitalEntry = new VitalEntry(accountId, vitalEntryVO.getDateTime(),
                DateUtility.getCurrentGMTDate(), vitalEntryVO.getNotes(), vitalEntryVO.getVitalEntryType(), vitalEntryVO.getParentVitalId());

Set<VitalEntryValues> vitalEntryValues = new HashSet<>();

for (VitalEntryValueVO value : vitalEntryVO.getValues()) {
    VitalEntryValues values = new VitalEntryValues(value.getValue(), vitalEntryVO.getType(), vitalEntry,
    vitalRepo.findUnit(value.getUnitId()), vitalRepo.findOne(value.getVitalId()));
    vitalEntryValues.add(values);
}
vitalEntry.setVitalEntryValues(vitalEntryValues);
vitalEntry =  vitalEntryRepo.save(vitalEntry);
Run Code Online (Sandbox Code Playgroud)

但在保存时我得到了这个例外:

Caused by: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.v2.entity.VitalEntryValues
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:771)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:281)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:780)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:765)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
    at com.sun.proxy.$Proxy555.persist(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
Run Code Online (Sandbox Code Playgroud)

我在这里做错了什么?任何帮助表示赞赏..

编辑:

@Entity
@Table(name = "vitals")
public class Vitals {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "vital_id", columnDefinition = "INT")
    private int vitalId;

    @Basic
    @Column(name = "has_value")
    private boolean hasValue;

    @Basic
    @Column(name = "is_decimal")
    private boolean isDecimal;

    @Basic
    @Column(name = "is_oneday_reading")
    private boolean isOnedayReading;

    @Basic
    @Column(length = 100)
    private String name;

    @Basic
    @Column(name = "parent_id", columnDefinition = "INT")
    private int parentId;

    @OneToMany(targetEntity = PatientVitals.class, mappedBy = "vitals", cascade = CascadeType.MERGE)
    private Set<PatientVitals> patientVitals = new HashSet<PatientVitals>();

    @Basic
    @Column(columnDefinition = "INT")
    private int type;

    @OneToMany(targetEntity = VitalEntryValues.class, mappedBy = "vitals", cascade = CascadeType.MERGE)
    private Set<VitalEntryValues> vitalEntryValues = new HashSet<VitalEntryValues>();

    @OneToMany(targetEntity = VitalUnits.class, mappedBy = "vitals", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
    private Set<VitalUnits> vitalUnits = new HashSet<VitalUnits>();
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*nov 4

简介和背景

\n\n

我使用了所提供源代码的简化版本,足以重现所描述的问题。

\n\n

因此,分析和解决方案可能不完整。

\n\n

休眠版本:

\n\n
<dependency>\n    <groupId>org.hibernate</groupId>\n    <artifactId>hibernate-core</artifactId>\n    <version>5.2.10.Final</version>\n</dependency>\n
Run Code Online (Sandbox Code Playgroud)\n\n

分析及解决方案

\n\n

1.MapsId注解使用

\n\n

MapsId请考虑检查有关注释及其用法的文档。

\n\n

对于所提供的上下文,它应该是:

\n\n
<dependency>\n    <groupId>org.hibernate</groupId>\n    <artifactId>hibernate-core</artifactId>\n    <version>5.2.10.Final</version>\n</dependency>\n
Run Code Online (Sandbox Code Playgroud)\n\n

2. Hibernate:EmbeddedId字段初始化

\n\n

正如我的实验所示,用注释注释的字段EmbeddedId也需要初始化,以避免内部 Hibernate 异常 \xe2\x80\x94 org.hibernate.PropertyAccessException。详情请参阅问题。

\n\n
...\npublic class VitalEntryValues {\n    ...\n\n    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)\n    @MapsId("vitalEntryId")\n    @JoinColumn(name = "vital_entry_id", columnDefinition = "INT")\n    private VitalEntry vitalEntry;\n\n    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)\n    @MapsId("vitalId")\n    @JoinColumn(name = "vital_id", columnDefinition = "INT")\n    private Vitals vitals;\n\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

希望这可以帮助。

\n