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

Модель
Итак, есть всего одна сущность, но она будет разбита на две таблицы. Во вторую таблицу мы хотим вынести телефон пользователя:
@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.
