Persisting a @OneToOne child entity with @MapsId throws "error:detached entity passed to persist" in Hibernate
Asked Answered
A

4

14

I read https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/. I tried suggestion config like(using spring data JPA,hibernate 5.0 as vendor ):

public class PaperSubjectType{
    @Id
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private PaperSetting paperSetting;
..
}

class PaperSetting{
  @Id
  @GeneratedValue
  private Long id;
..
}

first I tried the example:

PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);

paperSubjectTypeRepository.save(paperSubjectType);

error:detached entity passed to persist:PaperSetting. it seems hibernate take PaperSetting as detached when cascade

2 if I want to create both PaperSubjectType and PaperSetting together,do I need to do this:

PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSetting  paperSettingInDbNew = paperSettingRepository.save(paperSetting);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);

or I should use bidirectional in this situation? thank you!

Antrim answered 6/12, 2017 at 8:55 Comment(0)
P
2

I think you may have forgotten to wrap the logic in a @Transactional block

@Transactional
PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);

paperSubjectTypeRepository.save(paperSubjectType);

without that crudRepository.findOne() will open it's own short lived transaction so when you get the return of findOne() the entity is already detached, hence the error

Peers answered 6/12, 2017 at 9:21 Comment(2)
I add @ Transactional @ Commit in my test,this time " Field 'id' doesn't have a default value"Antrim
@Antrim your PaperSubjectType don't have autogeneration annotations on the id... so you either set it before save or add the autogenerationPeers
B
1

I tried it Hibernate 5.2 and it works like a charm.

Assuming you have these entities:

@Entity(name = "Person")
public static class Person  {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String registrationNumber;

    public Person() {}

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRegistrationNumber() {
        return registrationNumber;
    }
}

@Entity(name = "PersonDetails")
public static class PersonDetails  {

    @Id
    private Long id;

    private String nickName;

    @OneToOne
    @MapsId
    private Person person;

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}

And this data access logic:

Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = new Person( "ABC-123" );
    entityManager.persist( person );

    return person;
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = entityManager.find( Person.class, _person.getId() );

    PersonDetails personDetails = new PersonDetails();
    personDetails.setNickName( "John Doe" );
    personDetails.setPerson( person );

    entityManager.persist( personDetails );
} );

The test passes just fine in Hibernate ORM.

Maybe it was a bug in 5.0 that got fixed, so you are better of upgrading.

Beatnik answered 6/12, 2017 at 9:9 Comment(3)
,I find the reason: I have to add @ JoinColumn(name = "id") on private PaperSetting paperSetting; or it will create a extra "paper_setting_id" column in PaperSubjectType table when save PaperSetting . Do you know why?Antrim
There's no need for that if you use @MapsId.Beatnik
,I set hibernate.hbm2ddl.auto=none and try again. I have to use @ JoinColumn(name = "id") with @ MapsId or it complain can't find column 'paper_setting_id' when insertAntrim
M
0

1) Add cascading option:

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@MapsId
private PaperSetting paperSetting;

2) Having that in place, you can save only PaperSubjectType while creating both entities anew:

PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);
Maliamalice answered 6/12, 2017 at 9:10 Comment(1)
1) your config is the same as mine. 2) Field 'id' doesn't have a default valueAntrim
U
0

The reason is because the entity is in detached state. How does that happen? You pass the entity that is not in persistent state - meaning it is not associated with a persistent context. So one of the solutions would be:

PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSubjectType paperSubjectType = new PaperSubjectType();

// we create PaperSetting entity and we pass it before saving it,
// as it will bedetached from the persistent context as soon as we save it
paperSubjectType.setPaperSetting(paperSetting);

paperSubjectTypeRepository.save(paperSubjectType);
Unpin answered 23/10, 2023 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.