Составной ключ с @ClassId

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

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

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