Как правильно каскадно сохранить двунаправленную связь «один к одному» по первичному ключу в Hibernate 3.6

У меня есть двунаправленные отношения один к одному с общими ключами. Когда я пытаюсь сохранить владельца ассоциации, я получаю исключение «сгенерированный нулевой идентификатор» против принадлежащей стороне отношения. Я использую hibernate-entitymanager и Spring для управления транзакциями.

Владелец

@Entity
@Table(name = "lead")
public class Lead
{
    private Long leadId;

    private LeadAffiliate leadAffiliate;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getLeadId()
    {
        return leadId;
    }

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    public LeadAffiliate getLeadAffiliate()
    {
        return leadAffiliate;
    }
}

Собственный объект

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate
{
    private Long leadId;

    private Lead lead;

    @Id
    public Long getLeadId()
    {
        return leadId;
    }

    @MapsIdmappedBy = "leadAffiliate")
    @OneToOne(cascade = CascadeType.All)
    @PrimaryKeyJoinColumn
    @JoinColumn(name = "lead_id")
    public Lead getLead()
    {
        return lead;
    }
}

и код ниже используется для сохранения объекта:

LeadAffiliate aff = new LeadAffiliate();

aff.setLead(lead);
lead.setLeadAffiliate(aff);

em.persist(lead);

Все это отлично работает в спящем режиме 3.5.0-Final. При попытке обновления до 3.5.6-Final или 3.6.0.Final я начинаю получать ошибку «нулевой идентификатор, сгенерированный для LeadAffiliate»:

javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1153)
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:678)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365)
    at $Proxy152.persist(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
    at $Proxy120.persist(Unknown Source)
    at com.sellingsource.common.dao.JpaGenericDao.create(JpaGenericDao.java:38)
    ... 64 more
Caused by: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123)
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69)
    at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:799)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:791)
    at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:48)
    at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
    at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:450)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:282)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129)
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69)
    at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
    at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786)
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672)
    ... 77 more

Кроме того, я не уверен, что аннотации на Lead Affiliate были правильными с самого начала. Они работали, но казались какими-то неуклюжими. Поэтому я с тех пор изменил их на:

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate
{
    private Long leadId;

    private Lead lead;

    @Id
    @GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
                    @org.hibernate.annotations.Parameter(name = "property", value="lead")
    })
    @GeneratedValue(generator = "foreign")
    public Long getLeadId()
    {
        return leadId;
    }

    @OneToOne(mappedBy = "leadAffiliate")
    @PrimaryKeyJoinColumn
    public Lead getLead()
    {
        return lead;
    }
}

Однако с этими изменениями я получаю тот же результат. (Работает в 3.5.0, но не в 3.5.6 или 3.6.0)

Есть ли новый способ, которым мне нужно это делать, или это ошибка? Меня беспокоит то, что мой код в настоящее время работает из-за ошибки :/.


person Mike Lively    schedule 26.10.2010    source источник
comment
Я использую простой JPA 1.0 для достижения вашей цели. Каскадная аннотация от своего аналога JPA   -  person Arthur Ronald    schedule 12.02.2011


Ответы (2)


Спецификация говорит, что производная сущность должна быть владеющей стороной отношения:

2.4.1 Первичные ключи, соответствующие производным идентификаторам

Идентификатор объекта может быть получен из идентификатора другого объекта (родительского объекта), когда предыдущий объект (зависимый объект) является владельцем отношения «многие к одному» или «один к одному» с родительским объектом и внешний ключ отображает отношение от зависимого к родительскому.

В вашем случае LeadAffiliate является производным, поэтому он должен быть владельцем, когда Lead должен быть помечен как сторона, не владеющая mappedBy. Следующее работает как в 3.5.0, так и в 3.5.6:

public class Lead { 
    @Id @GeneratedValue
    private Long leadId; 
 
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "lead")
    private LeadAffiliate leadAffiliate; 

    ...
}

.

public class LeadAffiliate {  
    @Id
    private Long leadId;  
  
    @OneToOne @MapsId
    private Lead lead; 

    ...
}
person axtavt    schedule 26.10.2010
comment
К сожалению, Hibernate не нравится подход @Id (нужно заполнить новую ошибку...). Так что +1 за решение, которое работает с Hibernate. - person Pascal Thivent; 27.10.2010
comment
Я подтвердил, что это изменение работает с 3.5.6. Мне пришлось добавить аннотацию @JoinColumn, поскольку она пыталась использовать неправильное имя столбца. Но после этого заработало хорошо. Я также проверил, что это работает в 3.6.0. Судя по документации, похоже, что это был недостаток в спящем режиме 3.5, который позволял работать моим исходным аннотациям. - person Mike Lively; 27.10.2010
comment
Застрял на этом на 2 дня, этот ответ определенно должен получить больше голосов - person singe3; 19.09.2016

Мой ответ не объясняет, почему все работает с Hibernate 3.5.0-Final, но не с 3.5.6-Final или 3.6.0.Final (и вы должны сообщить об этом, я называю это регрессией).

В любом случае производные идентификаторы гораздо лучше поддерживаются в JPA 2.0 стандартным способом, и в вашем случае, я думаю, вы могли бы просто аннотировать свое отношение OneToOne аннотацией Id.

Обновление: Как подчеркивает axtavt, при использовании производного идентификатора "зависимая" сущность должна быть владельцем отношения. Таким образом, полное сопоставление для зависимого объекта будет следующим:

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate {
    private Lead lead;

    @Id
    @OneToOne
    @JoinColumn(name="FK")
    public Lead getLead() {
        return lead;
    }
}

И "родительский" объект:

@Entity
@Table(name = "lead")
public class Lead {
    private Long leadId;

    private LeadAffiliate leadAffiliate;

    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getLeadId() {
        return leadId;
    }

    @OneToOne(cascade = CascadeType.ALL, mappedBy="lead")
    public LeadAffiliate getLeadAffiliate() {
        return leadAffiliate;
    }
}

Это правильное сопоставление JPA 2.0, работающее с EclipseLink. Однако Hibernate это не нравится, и он не будет создавать экземпляр EntityManagerFactory (черт возьми!).

В качестве обходного пути вам придется использовать решение, предложенное axtavt, то есть объявить атрибут первичного ключа а также атрибут отношения и использовать MapsId в атрибуте отношения.

Но вышеизложенное должно работать, в Hibernate есть ошибка IMO (сообщается как HHH- 5695).

использованная литература

person Pascal Thivent    schedule 26.10.2010
comment
Я использую OpenJPA 2.2.2, и это сработало для меня (JPA 2.0) - person anton1980; 18.03.2016