Мы рассмотрели, как задать составной ключ с помощью аннотации @EmbeddedId. С @ClassId все очень похоже. Таблица в базе генерируется такая же (см. картинку ниже). Но поля ключа не просто вынесены в отдельны класс, а дублируются в основном.
Тот же пример — клетка шахматной доски. Генерируемая таблица — chess_board_cell (первичный ключ состоит из двух полей — horizontal и vertical, таблица такая же):
Модель
Но теперь в классе клетки доски ключ не внедрен, а заново прописываются его поля vertical и horizontal. Класс теперь аннотирован @IdClass:
@Entity @IdClass(ChessBoardCellKey.class) public class ChessBoardCell { @Id private int vertical; @Id private char horizontal; private String color; public ChessBoardCellKey getId() { return new ChessBoardCellKey( vertical, horizontal ); } public void setId(ChessBoardCellKey id) { this.vertical = id.getVertical(); this.horizontal = id.getHorizontal(); } ... }
К классу ключа такие же требования, отличие выделено жирным:
- должен быть сериализуемым
- должен быть public и иметь public no-arg constructor (как и сущности)
- аннотирован @IdClass
- а также в нем должен быть переопределен equals() и hashcode()
Класс ключа теперь аннотирован @IdClass
@IdClass(ChessBoardCellKey.class) @Data @NoArgsConstructor @AllArgsConstructor public class ChessBoardCellKey implements Serializable { private int vertical; private char horizontal; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ChessBoardCellKey that = (ChessBoardCellKey) o; return vertical == that.vertical && horizontal == that.horizontal; } @Override public int hashCode() { return Objects.hash(vertical, horizontal); } }
Таблица
В итоге генерируется таблица, как показано на рисунке в начале статьи, скрипт ее в PostgreSQL такой же:
CREATE TABLE public.chess_board_cell ( horizontal character(1) COLLATE pg_catalog."default" NOT NULL, vertical integer NOT NULL, color character varying(255) COLLATE pg_catalog."default", CONSTRAINT chess_board_cell_pkey PRIMARY KEY (horizontal, vertical) )
Первичный ключ состоит из двух NOT NULL полей horizontal и vertical.
Добавление и выборка
Репозиторий в Spring по составному ключу делается так же, как по простому:
public interface ChessBoardCellRepository extends JpaRepository<ChessBoardCell, ChessBoardCellKey> { }
И в findById() передается не Long, а составной ключ.
Создадим сервис:
@Service public class ChessBoardCellService { @Autowired private ChessBoardCellRepository chessBoardCellRepository; void addChessBoardCell(ChessBoardCell chessBoardCell){ chessBoardCellRepository.save(chessBoardCell); } }
Добавим пару клеток и сделаем выборку:
@DataJpaTest @Import(ChessBoardCellService.class) @AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE) @Commit public class ChessBoardServiceTest { @Autowired ChessBoardCellService chessBoardCellService; @Autowired ChessBoardCellRepository chessBoardCellRepository; @BeforeEach void init(){ ChessBoardCell cell1=new ChessBoardCell(1, 'A', "white"); chessBoardCellService.addChessBoardCell(cell1); ChessBoardCell cell2=new ChessBoardCell(2, 'B', "white"); chessBoardCellService.addChessBoardCell(cell2); } @Test void shouldGetCells(){ List<ChessBoardCell> cells=chessBoardCellRepository.findAll(); Assertions.assertEquals(2, cells.size()); } @Test void shouldGetCell(){ ChessBoardCellKey id=new ChessBoardCellKey(1, 'A'); Optional<ChessBoardCell> cell=chessBoardCellRepository.findById(id); Assertions.assertTrue(cell.isPresent()); Assertions.assertEquals(1, cell.get().getVertical()); } }
Итоги
Полный код примера доступен на GitHub.