OneToOne помогите с файлом для liquibase в случае двухстронней связи
Изучаю тему связывания таблиц. Пока разбираюсь с OneToOne. Таблицу Person сократил ради экономии места. Понятно, что одно имя не сможет однозначно указать на конкретного человека.
Первый вариант - односторонняя связь У человека есть ИНН и он может быть у него только один. И каждому ИНН прявязан конкретный человек и он тоже может быть только один, но нам не надо хранить в базе инн сведения о его владельце.
Person.java:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "persons", schema = "date")
public class Person {
@Id
@GeneratedValue
private long id;
@Column
private String name;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "inn_id", unique = true)
private InnBase innBase;
}
InnBase.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "inn_base", schema = "date")
public class InnBase {
@Id
@GeneratedValue
private long id;
@Column(name = "inn_number")
private int innNumber;
}
файл для liquibase
<changeSet id="create_person" author="alex">
<createTable tableName="person" schemaName="date">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="id_inn" type="INT">
<constraints nullable="false" foreignKeyName="fk_inn" referencedTableSchemaName="date"
referencedTableName="types" referencedColumnNames="id"/>
</column>
</createTable>
</changeSet>
<changeSet id="create_inn" author="alex">
<createTable tableName="inn_base" schemaName="date">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="inn_number" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
Второй вариант - двустороняя связь.
У человека есть ИНН и он может быть у него только один. К каждому ИНН привязан конкретный человек и он тоже может быть только один. Но нам необходимо, что бы можно было найти по ИНН его владельца. В этом случае необходимо изменить класс InnBase.
Person.java:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class Person {
@Id
@GeneratedValue
private long id;
@Column
private String name;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "inn_id", unique = true)
private InnBase innBase;
}
InnBase.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class InnBase {
@Id
@GeneratedValue
private long id;
@Column(name = "inn_number")
private int innNumber;
@OneToOne (optional=false, mappedBy="innBase", fetch = FetchType.LAZY)
private Person owner;
}
На сколько правильно я понял эту тему?
И подскажите как мне во втором случае надо изменить файл liquibase для таблицы inn_base.
Я так понимаю, что во втором случае надо создать третью таблицу для связи:
id | person_id | inn_id
Но как тогда измениться код сущностей?
Создал таблицы через hibernate. И получилось, что для второго варианта структура таблиц совсем не поменялась.
Ответы (1 шт):
В первом случае все правильно, но излишне будет делать отдельный FK,тоесть
@JoinColumn(name = "inn_id", unique = true)
в Person entity, потому как у нас не может быть другого InnBase для Preson, мы можем связывать их через id Person, то есть
FK_PERSON_ON_INNBASE FOREIGN KEY (id) REFERENCES InnBase (id);
вместо
FK_PERSON_ON_INNBASE FOREIGN KEY (inn_id) REFERENCES InnBase (id);
еще если ddl будет формировать фреймворк, что бы он сделал правильное связывание FK через Person id вместо
@JoinColumn(name = "id", unique = true)
нужно будет использовать аннотацию
@MapsId в Person например
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "persons", schema = "date")
public class Person {
@Id
private long id;
@Column
private String name;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@MapsId
private InnBase innBase;
}
@GeneratedValue при этом уже не нужен потому как столбец id будет заполнятся идентификатором InnBasse id
если же ddl будете формиваться не фреймворком, а через другие инструменты, например: liquiBase,flyWay,или же вы захотите связывать FK написав запрос вручную, достаточно будет
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "id", unique = true)
private InnBase innBase;
Во втором же случаи у вас двунаправленая связь, и тут очень важно понимать кто является родительской стороной, а кто дочерней, в вашем случаи, родителем будет InnBase, соответственно Person, в таком случае принято написать метод синхронизации на стороне родителя, например:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class InnBase {
@Id
@GeneratedValue
private long id;
@Column(name = "inn_number")
private int innNumber;
@OneToOne (optional=false, mappedBy="innBase", fetch = FetchType.LAZY)
private Person owner;
public void setPerson(Person person) {
if (person == null) {
if (this.person != null) {
this.person.innBase(null);
}
}
else {
person.innBase(this);
}
this.person = person;
}
}
и это еще не все, есть еще нюансы зависящие с помощью каких инструментов вы будете делать выборку, и вставку и нюансы связанные с ленивой загрузкой стороны родителя, и по поводу доп. таблицы для связей OneToOne она не нужна,если вы пользуетесь liquiBase для создания ddl для второго варианта, не будет отличатся от первого, главное что бы связывание было в changeSet для таблицы на дочерней стороне, потому как FK будет на дочерней стороне