Автоматическое заполнение внешнего ключа двунаправленного сопоставления OneToOne

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

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

Мы сделали это правильно, как показано ниже:

@Entity
@Table(name = "user")
class User {
    @Id
    @Column(name = "user_id")
    private Long id;

    //...

    @OneToOne(cascade = CascadeType.MERGE, mappedBy = "user")
    private Address address; // this attribute is crucial
}

@Entity
@Table(name = "address")
class Address {
    @Id
    @Column(name = "address_id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user; // this attribute is not needed for the business at all, but mappedBy requires it

    //...

}

База данных:

-- SQL:
CREATE TABLE user
(
    user_id INT NOT NULL,
    -- ...
    CONSTRAINT user_pk PRIMARY KEY (user_id)
);

CREATE TABLE address
(
    address_id INT NOT NULL,
    user_id INT NOT NULL,
    -- ...
    CONSTRAINT address_pk PRIMARY KEY (address_id),
    CONSTRAINT address_user_id_fk FOREIGN KEY (user_id) REFERENCES user (user_id),
    CONSTRAINT address_user_id_uk UNIQUE (user_id) -- it says it's a one to one relation for now, if in the future it won't be anymore, just remove this constraint
);

Проблема заключается в том, что при сохранении экземпляра пользователя с новым экземпляром адреса пользовательский атрибут адреса равен null, поэтому я ожидал, что Hibernate достаточно умен, чтобы установить значение из экземпляра пользователя, из которого он исходит.

Я искал решение пару дней, но так и не нашел, как это решить, пока что я устанавливаю значение вручную, но я ожидаю, что мне это не нужно.


person Tiago Pimenta    schedule 20.07.2015    source источник


Ответы (3)


Стандартным решением является правильное обновление обеих сторон двунаправленная ассоциация (хотя для сохранения ассоциации в базе данных необходимо обновить только сторону-владельца). Добавьте в сеттер Address в классе User:

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

Кроме того, вы можете захотеть расширить параметры каскадирования для свойства address, чтобы включить также PERSIST, чтобы оно всегда сохранялось вместе со своим пользователем:

 @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "user")
 private Address address;

Затем вы можете установить адрес пользователя и сохранить оба:

user.setAddress(address);
session.persist(user);
person Dragan Bozanovic    schedule 20.07.2015
comment
Понятно, мне было интересно, Hibernate может быть достаточно умным, чтобы сделать это за меня, но это решение лучше, чем установка пользователем каждого фрагмента кода, создающего адрес. - person Tiago Pimenta; 20.07.2015

Если «частный пользователь» не нужен, удалите его и удалите также mappedBy в сущности «Пользователь». Используйте однонаправленное отношение.

В случае вашего примера mappedBy означает, что владельцем ассоциации является объект «Адрес», поэтому сохраните экземпляр адреса, а не пользователя. Нравиться :

adress.setUser(user);
session.save(adress);
person Bilal BBB    schedule 20.07.2015
comment
Как я могу это сделать, если внешний ключ находится по адресу? - person Tiago Pimenta; 20.07.2015
comment
поскольку внешний ключ находится по адресу, вам нужно сохранить адрес. Таким образом, значение внешнего ключа будет первичным ключом экземпляра пользователя. - person Bilal BBB; 20.07.2015
comment
К сожалению, он выдает ошибку: Missing column: address_address_id in public.user это то, что я имел в виду, когда сказал, что mappedBy необходим для указания того, что внешний ключ находится в другом объекте. - person Tiago Pimenta; 20.07.2015

Вам нужно добавить CasecadeType.PERSIST, чтобы сделать casecade создания Address с созданием пользователя.

В вашем Java-коде вам нужно сделать:

user.setAddress(address);
address.setUser(user);
session.persist(user);

Затем ваш пользователь будет создан с адресом.

Если вы просто хотите прочитать адрес нового созданного пользователя, вам нужно сделать:

// your code to persist a new User and Address
session.flush();
session.refresh(user);

Если это не то, что вы хотите, вам нужно поделиться своим собственным кодом Java и дать подробное описание того, что вы ожидаете.

person Qianlong    schedule 20.07.2015
comment
Могу ли я опустить строку address.setUser(user);? Если бы я мог, это ответ. - person Tiago Pimenta; 20.07.2015
comment
К сожалению, он выдает исключение: ERROR: null value in column "user_id" violates not-null constraint - person Tiago Pimenta; 20.07.2015