Мы рассмотрели, как задать составной ключ с помощью аннотации @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.
