Аннотация @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.