Хранение сущностей с сопоставлением OneToOne с помощью Hibernate

У меня есть следующие две сущности:

Человек.java:

@Entity
@Table(name="PERSON")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private int id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "person", optional = false)
    private Address address;

    public Person(final String firstName, final String lastName, final Address address)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.address = address;
    }

    //Required by hibernate
    public Person()
    {
    }

    public int getId()
    {
        return id;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public String getLastName()
    {
        return lastName;
    }

    public Address getAddress() {
        return address;
    }

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

    public void setFirstName(final String firstName)
    {
        this.firstName = firstName;
    }

    public void setLastName(final String lastName)
    {
        this.lastName = lastName;
    }

    public void setAddress(final Address address)
    {
        this.address = address;
    }

}

Адрес.java:

@Entity
@Table(name = "ADDRESS")
public class Address
{
    @Id
    private int personId;

    @MapsId
    @OneToOne()
    private Person person;

    @Column(name = "street")
    private String street;

    @Column(name = "town")
    private String town;

    @Column(name = "postcode")
    private String postcode;

    public Address()
    {
    }

    public Address(final String street, final String town, final String postcode)
    {
        this.street = street;
        this.town = town;
        this.postcode = postcode;
    }

    public Person getPerson()
    {
        return person;
    }

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

    public String getStreet()
    {
        return street;
    }

    public void setStreet(final String street)
    {
        this.street = street;
    }

    public String getTown()
    {
        return town;
    }

    public void setTown(final String town)
    {
        this.town = town;
    }

    public String getPostcode()
    {
        return postcode;
    }

    public void setPostcode(final String postcode)
    {
        this.postcode = postcode;
    }

}

Я хотел бы сопоставить эти два объекта со следующей схемой:

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(50) NULL DEFAULT NULL,
    last_name VARCHAR(50) NULL DEFAULT NULL,
    PRIMARY KEY (id)
);
CREATE TABLE address (
    person_id INT(10) NOT NULL,
    street VARCHAR(50) NULL DEFAULT NULL,
    town VARCHAR(50) NULL DEFAULT NULL,
    postcode VARCHAR(50) NULL DEFAULT NULL,
    FOREIGN KEY (person_id) REFERENCES person(id) ON DELETE CASCADE
);

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

К сожалению, аннотации Hibernate работают некорректно. Это код, который я использую для проверки вышеизложенного:

public static Person save(Person person) {
    SessionFactory sf = new AnnotationConfiguration().configure().buildSessionFactory();
    Session session = sf.openSession();
    session.beginTransaction();
    Integer id = (Integer)session.save(person);
    person.setId(id);
    session.getTransaction().commit();
    session.close();
    return person;
}

@Test
public void testWritePerson() {
    Person person = new Person("Jack", "Bauer", new Address("123 Fake Street", "Springfield", "SP1F 123"));
    person.getAddress().setPerson(person);

    Person savedPerson = PersonTestUtil.save(person);
    assertFalse(
}

Но ошибка, которую я получаю:

org.hibernate.id.IdentifierGenerationException: null id generated for:class Address

Я пробовал много перестановок различных аннотаций без успеха. Любые идеи, как я могу исправить аннотации в приведенном выше коде, чтобы он правильно сохранял объекты Person и Address? Полный код, если вы хотите протестировать его самостоятельно, находится здесь вместе с инструкциями по настройке базы данных:

https://github.com/alexspurling/hibernate-test


person Alex Spurling    schedule 16.11.2012    source источник


Ответы (3)


Я обнаружил, что решение состоит в том, чтобы удалить атрибут «необязательный = false» из аннотации OneToOne человека в поле адреса:

@OneToOne(cascade = CascadeType.ALL, mappedBy = "person")
private Address address;

Я считаю, что это работает, потому что это предотвращает попытки Hibernate сохранить адрес до того, как будет сгенерирован идентификатор для человека.

Полный рабочий пример можно найти в обновленном репозитории github: https://github.com/alexspurling/hibernate-test

person Alex Spurling    schedule 19.11.2012

Попробуйте изменить отображение (все остальное в порядке). прочитайте это

http://viralpatel.net/blogs/hibernate-one-to-one-mapping-tutorial-using-annotation/

человек в порядке

Адрес должен быть изменен таким образом

public class Address {

  @Id
  @Column(name="personId", unique=true, nullable=false)
  @GeneratedValue(generator="gen")
  @GenericGenerator(name="gen", strategy="foreign"
           , parameters=@Parameter(name="property", value="person"))
  private int personId;

  @OneToOne
  @PrimaryKeyJoinColumn
  private Person person
  ...

При вставке нового значения мы можем явно установить оба конца:

person Vlad    schedule 17.11.2012
comment
Привет, Влад, я полагаю, вы не закончили свой ответ, но в любом случае использование этих аннотаций, а затем назначение поля человека объекту адреса дает точно такую ​​​​же ошибку. По какой-то причине Hibernate не может сгенерировать значение для поля person_id. - person Alex Spurling; 19.11.2012
comment
При дальнейшем исследовании использование предложенных вами аннотаций работает так же, как и аннотации @Id и @MapsId, которые я использовал в своем исходном вопросе. Причину отказа я показал в своем собственном ответе на этот вопрос. - person Alex Spurling; 19.11.2012

Я думаю, что поле personID в объекте Address должно отражать состояние атрибута объекта person. Попробуйте изменить объект адреса:

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

Вы должны убедиться, что когда вы устанавливаете один, другой синхронизируется.

EDIT
Хорошо, теперь я вижу, что мое предложение не сработает. Основная проблема заключается в том, что вам нужно установить идентификатор адреса с идентификатором человека, но идентификатор человека автоматически генерируется при сохранении, поэтому у вас еще нет этого значения. Идентификатор по адресу не имеет аннотации, сообщающей JPA, что на самом деле это внешний ключ для Person, поэтому он не может установить его с помощью сгенерированного идентификатора человека.
Что вам нужно сделать, так это предоставить JPA достаточно информации, чтобы он знал, что сохраните человека, а затем используйте этот идентификатор для сохранения адреса. Я бы попробовал что-то вроде этого.

@Entity
@Table(name = "ADDRESS")
public class Address
{
    //*** Use the Person object to get and set this value
    //*** @Id
    //*** private int personId;

    @Id
    @OneToOne()
    private Person person;

Теперь у JPA должно быть достаточно аннотированной информации, чтобы выяснить, в каком порядке сохранять объекты и как установить personId для Address.
Наконец, строку, в которой вы устанавливаете Person для Address в тестовом примере, следует переместить в конструктор Person. так как это важный шаг. Без этой строки JPA не будет иметь ссылку на объект, чтобы связать их.

public Person(final String firstName, final String lastName, final Address address)
{
     this.firstName = firstName;
     this.lastName = lastName;
     this.address = address;
     this.address.setPerson(this);
} 

Я думаю, что это должно сделать это.

person Ben Thurley    schedule 16.11.2012
comment
Спасибо, Бен, я внес это изменение, но все равно получаю ту же ошибку. - person Alex Spurling; 16.11.2012
comment
Еще раз спасибо, Бен. Не знаю почему, но ваше предложение не сработало. Однако я нашел решение, которое действительно работает, которое включает простое удаление атрибута optional = false из аннотации OneToOne человека в поле адреса. Я обновил пример кода на github с помощью этого решения (вместе с несколькими изменениями в тестах). Если вы хотите взглянуть и, возможно, помочь мне понять, почему это работает, это было бы здорово. - person Alex Spurling; 19.11.2012