За съжаление мисля, че малката разлика, че поддържате само една таблица, е проблемът тук.
Вижте декларацията на PhoneId
клас (който бих предложил да се нарича по-добре PhoneOwner
или нещо подобно):
@Entity
@Table(name="Phones")
public class PhoneId {
Когато декларирате, че даден клас е обект, картографиран към определена таблица, вие правите набор от твърдения, две от които са особено важни тук. Първо, че има един ред в таблицата за всеки екземпляр на обекта и обратно. Второ, че има една колона в таблицата за всяко скаларно поле на обекта и обратно. И двете са в основата на идеята за обектно-релационно картографиране.
Във вашата схема обаче нито едно от тези твърдения не е валидно. В предоставените от вас данни:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Има два реда, съответстващи на обекта с owner_id
1, нарушавайки първото твърдение. Има колони TYPE
и NUMBER
които не са съпоставени с полета в обекта, нарушавайки второто твърдение.
(За да бъде ясно, няма нищо лошо в декларацията ви за Phone
клас или phones
поле - само PhoneId
обект)
В резултат на това, когато вашият JPA доставчик се опита да вмъкне екземпляр на PhoneId
в базата данни, се натъква на проблеми. Тъй като няма съпоставяния за TYPE
и NUMBER
колони в PhoneId
, когато генерира SQL за вмъкване, той не включва стойности за тях. Ето защо получавате грешката, която виждате - доставчикът пише INSERT INTO Phones (owner_id) VALUES (?)
, което PostgreSQL третира като INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, което е отхвърлено.
Дори и да сте успели да вмъкнете ред в тази таблица, ще имате проблеми при извличането на обект от нея. Да кажем, че сте поискали екземпляра на PhoneId
с owner_id
1. Доставчикът ще напише SQL в размер на select * from Phones where owner_id = 1
, и би очаквало, че ще намери точно един ред, който може да картографира към обект. Но ще намери два реда!
Опасявам се, че решението е да се използват две таблици, едната за PhoneId
и един за Phone
. Таблицата за PhoneId
ще бъде тривиално просто, но е необходимо за правилното функциониране на машината на JPA.
Ако приемем, че преименувате PhoneId
до PhoneOwner
, таблиците трябва да изглеждат така:
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)
)
(Направих (owner_id, number)
първичен ключ за Phone
, при допускането, че един собственик може да има повече от един номер от даден тип, но никога няма да има един номер, записан под два типа. Може да предпочетете (owner_id, type)
ако това отразява по-добре вашия домейн.)
Тогава обектите са:
@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;
}
Сега, ако наистина не искате да въведете таблица за PhoneOwner
, тогава може да успеете да излезете от него с помощта на изглед. Като това:
create view PhoneOwner as select distinct owner_id from Phone;
Доколкото може да разбере доставчикът на JPA, това е таблица и ще поддържа заявките, които трябва да направи, за да прочете данни.
Той обаче няма да поддържа вмъквания. Ако някога ви се наложи да добавите телефон за собственик, който в момента не е в базата данни, ще трябва да отидете отзад и да вмъкнете ред директно в Phone
. Не е много хубаво.