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é.