Аннотация @SecondaryTable

Аннотация @SecondaryTable позволяет разбить содержимое одной сущности на две таблицы.

С одной стороны, это противоположность аннотации @Embedded (которая позволяет вынести часть полей сущности в другой класс, но сохранять данные в единой таблице). Тут наоборот — происходит разбивка одной сущности на две таблицы.

С другой стороны, @SecondaryTable — это альтернатива аннотации @OneToOne (особенно с @MapsId). Ниже мы увидим, что таблицы генерируются точь-в-точь такие, как в примере с @OneToOne+@MapsId:

Что сгенерируется в базе
Что сгенерируется в базе. Тут user_id  одновременно внешний и первичный ключ

Модель

Итак, есть всего одна сущность, но она будет разбита на две таблицы. Во вторую таблицу мы хотим вынести телефон пользователя:

@Entity
@SecondaryTable(name = "UserDetails",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "user_id", referencedColumnName = "id"))
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    private String name;

    @Column(name="phone", table="UserDetails")
    private String phone;

 //getters/setters/constructors
}

Внутри аннотации @SecondaryTable мы задаем имя второй таблицы user-details.

Поле phone аннотируем @Column, а внутри поясняем, что его надо положить в user-details.

@PrimaryKeyJoinColumn задает, что user_id будет первичным ключом в таблице user-details, и одновременно он будет внешним ключом, ссылающимся на поле id главной таблицы users (из которой мы выносим поля).

Вроде все, в итоге получается такая схема.

Таблицы в базе

Скрипт, генерируемый PostgreSQL. Видно, что user_id в таблице user_details одновременно первичный и внешний ключ:

CREATE TABLE public.user_details
(
    phone character varying(255) COLLATE pg_catalog."default",
    user_id bigint NOT NULL,
    CONSTRAINT user_details_pkey PRIMARY KEY (user_id),
    CONSTRAINT fkicouhgavvmiiohc28mgk0kuj5 FOREIGN KEY (user_id)
        REFERENCES public.users (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
)

Таблица users:

CREATE TABLE public.users
(
    id bigint NOT NULL,
    name character varying(255) COLLATE pg_catalog."default",
    CONSTRAINT users_pkey PRIMARY KEY (id)
)

Добавление пользователей и выборка

Если и имя, и поле телефона заполнить, то в базу добавятся две записи — по одной в каждую таблицу.

Если же телефон оставить пустым, то будет добавлена только строка в таблицу users.

Такой сервис:

@Service
public class UserService {
    @Autowired
    UserRepository userRepository;


    @Transactional
    public void addUser(String name, String phone) {
        User user = new User(name);
        if (phone != null)
            user.setPhone(phone);
        userRepository.save(user);
    }
}

Добавим пару пользователей, второго — с пустым телефоном:

userService.addUser("Jane1", "111-111");
userService.addUser("Jane2", null);

Результат (для пустого телефона строка в user_details не добавилась, только в users):

Выборка из UserRepository генерирует SQL с left join по двум таблицам:

List<User>  users=userRepository.findAll();

Генерируемый SQL:

SELECT user0_.id AS id1_1_,
       user0_.name AS name2_1_,
       user0_1_.phone AS phone1_0_
FROM users user0_
LEFT OUTER JOIN user_details user0_1_ ON user0_.id=user0_1_.user_id

Итоги

Код примера есть на GitHub.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *