A chave composta define que vários atributos serão utilizados para definir a chave de uma entidade, com isso, acabamos tendo uma restrição onde os atributos da chave composta não podem ser repetidos.
No JPA quando precisamos definir uma chave composta precisamos criar uma classe separada apenas com os atributos que fazem parte da chave composta e precisamos utilizar a anotação javax.persistence.Embeddable.
Neste exemplo vamos criar uma entidade Telefone que possui uma chave composta pelos atributos DDD e número do telefone, pois não pode ter o mesmo número de telefone para mais de 1 cliente.
Para declarar a chave composta vamos criar uma classe chamada TelefonePK com os atributos DDD e numero:
Sobrescrevi o método toString() para imprimir de forma mais amigável a chave composta.
Note que adicionamos a anotação @Embeddable na classe TelefonePK para informar que está classe será adicionado em outra entidade.
Para adicionarmos a chave composta na entidade, vamos criar um atributo do tipo da classe que possui a anotação @Embeddable e vamos adicionar a anotação javax.persistence.EmbeddedId neste atributo.
Na entidade Telefone vamos declarar um atributo chamado id do tipo TelefonePK para representar a chave composta:
Exemplo de um CRUD utilizando a chave composta, a classe TelefoneDAO possui as operações de Salvar, Alterar, Consultar por chave composta e Apagar o telefone:
No método consultarPorId precisamos agora passar um atributo da chave composta TelefonePK para que possamos localizar um telefone.
No método salvar vamos receber o objeto Telefone que será salvo, note que estamos tratando a exceção javax.persistence.EntityExitsException que será lançada caso tentamos salvar duas entidades com o mesmo id, neste caso com o mesmo DDD e número de telefone.
Criamos agora um método atualizar separado apenas para atualizar o telefone, pois podemos criar um Telefone e alterar o nome do cliente, mas não podemos criar dois telefones com o mesmo DDD e número de telefone.
No método apagar precisamos receber um objeto que representa a chave composta TelefonePK, desta forma podemos localizar o objeto Telefone para que possamos apagá-lo.
Nesta classe TelefoneDAOTeste vamos testar todas as operações da classe TelefoneDAO declarada anteriormente: