Bohužel si myslím, že malý rozdíl v tom, že si necháte jen jeden stůl, je problém.
Podívejte se na deklaraci PhoneId třída (kterou bych navrhoval, aby se lépe nazývala PhoneOwner nebo něco takového):
@Entity
@Table(name="Phones")
public class PhoneId {
Když deklarujete, že třída je entita mapovaná do určité tabulky, vytváříte sadu tvrzení, z nichž dvě jsou zde obzvláště důležitá. Za prvé, že v tabulce existuje jeden řádek pro každou instanci entity a naopak. Za druhé, že pro každé skalární pole entity je v tabulce jeden sloupec a naopak. Oba tyto jsou jádrem myšlenky objektově-relačního mapování.
Ve vašem schématu však neplatí ani jedno z těchto tvrzení. V údajích, které jste uvedl:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Entitě s owner_id odpovídají dva řádky 1, v rozporu s prvním tvrzením. Jsou zde sloupce TYPE a NUMBER které nejsou mapovány na pole v entitě, což porušuje druhé tvrzení.
(Aby bylo jasno, na vašem prohlášení o Phone není nic špatného třída nebo phones pole – pouze PhoneId entity)
Výsledkem je, že když se váš poskytovatel JPA pokusí vložit instanci PhoneId do databáze, dostane se do potíží. Protože pro TYPE neexistují žádná mapování a NUMBER sloupce v PhoneId , když generuje SQL pro vložení, nezahrnuje pro ně hodnoty. Proto se zobrazí chyba, kterou vidíte – poskytovatel zapíše INSERT INTO Phones (owner_id) VALUES (?) , které PostgreSQL považuje za INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null) , což je zamítnuto.
I kdyby se vám podařilo vložit řádek do této tabulky, narazili byste na potíže při načítání objektu z ní. Řekněme, že jste požádali o instanci PhoneId s owner_id 1. Poskytovatel zapíše SQL ve výši select * from Phones where owner_id = 1 , a očekával by, že najde právě jeden řádek, který může mapovat na objekt. Ale najde dva řádky!
Řešením je, obávám se, použít dvě tabulky, jednu pro PhoneId a jeden pro Phone . Tabulka pro PhoneId bude triviálně jednoduchý, ale je nezbytný pro správný chod strojů JPA.
Za předpokladu, že přejmenujete PhoneId na PhoneOwner , tabulky musí vypadat takto:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(Vytvořil jsem (owner_id, number) primární klíč pro Phone , za předpokladu, že jeden vlastník může mít více než jedno číslo daného typu, ale nikdy nebude mít jedno číslo evidované pod dvěma typy. Můžete preferovat (owner_id, type) pokud to lépe odpovídá vaší doméně.)
Entity jsou pak:
@Entity
@Table(name="PhoneOwner")
public class PhoneOwner {
@Id
@Column(name="owner_id")
long id;
@ElementCollection
@CollectionTable(name = "Phone", joinColumns = @JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
@Embeddable
class Phone {
@Column(name="type", nullable = false)
String type;
@Column(name="number", nullable = false)
String number;
}
Nyní, pokud opravdu nechcete zavést tabulku pro PhoneOwner , pak se z toho možná budete moci dostat pomocí pohledu. Takhle:
create view PhoneOwner as select distinct owner_id from Phone;
Pokud poskytovatel JPA může říci, jedná se o tabulku a bude podporovat dotazy, které potřebuje ke čtení dat.
Nepodporuje však vložky. Pokud byste někdy potřebovali přidat telefon pro vlastníka, který aktuálně není v databázi, museli byste přejít zadní část a vložit řádek přímo do Phone . Ne moc hezké.