Sayangnya, saya pikir sedikit perbedaan bahwa Anda hanya menyimpan satu meja adalah masalahnya di sini.
Lihat deklarasi PhoneId
kelas (yang saya sarankan lebih baik disebut PhoneOwner
atau semacamnya):
@Entity
@Table(name="Phones")
public class PhoneId {
Saat Anda mendeklarasikan bahwa kelas adalah entitas yang dipetakan ke tabel tertentu, Anda membuat serangkaian pernyataan, di mana dua di antaranya sangat penting di sini. Pertama, ada satu baris dalam tabel untuk setiap instance entitas, dan sebaliknya. Kedua, bahwa ada satu kolom dalam tabel untuk setiap bidang skalar entitas, dan sebaliknya. Keduanya merupakan inti dari ide pemetaan relasional objek.
Namun, dalam skema Anda, tidak satu pun dari pernyataan ini berlaku. Dalam data yang Anda berikan:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Ada dua baris yang sesuai dengan entitas dengan owner_id
1, melanggar pernyataan pertama. Ada kolom TYPE
dan NUMBER
yang tidak dipetakan ke bidang dalam entitas, melanggar pernyataan kedua.
(Agar jelas, tidak ada yang salah dengan pernyataan Anda tentang Phone
kelas atau phones
bidang - cukup PhoneId
entitas)
Akibatnya, ketika penyedia JPA Anda mencoba memasukkan instance PhoneId
ke dalam database, itu mengalami masalah. Karena tidak ada pemetaan untuk TYPE
dan NUMBER
kolom di PhoneId
, ketika menghasilkan SQL untuk sisipan, itu tidak menyertakan nilai untuk mereka. Inilah sebabnya mengapa Anda mendapatkan kesalahan yang Anda lihat - penyedia menulis INSERT INTO Phones (owner_id) VALUES (?)
, yang diperlakukan PostgreSQL sebagai INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, yang ditolak.
Bahkan jika Anda berhasil memasukkan baris ke dalam tabel ini, Anda akan mengalami masalah saat mengambil objek darinya. Katakanlah Anda meminta contoh PhoneId
dengan owner_id
1. Penyedia akan menulis SQL sebesar select * from Phones where owner_id = 1
, dan diharapkan untuk menemukan tepat satu baris, yang dapat dipetakan ke suatu objek. Tapi itu akan menemukan dua baris!
Solusinya, saya khawatir, adalah menggunakan dua tabel, satu untuk PhoneId
, dan satu untuk Phone
. Tabel untuk PhoneId
akan sangat sederhana, tetapi perlu untuk pengoperasian mesin JPA yang benar.
Dengan asumsi Anda mengganti nama PhoneId
ke PhoneOwner
, tabel harus terlihat seperti:
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)
)
(Saya sudah membuat (owner_id, number)
kunci utama untuk Phone
, dengan asumsi bahwa satu pemilik mungkin memiliki lebih dari satu nomor dari jenis tertentu, tetapi tidak akan pernah memiliki satu nomor yang tercatat di bawah dua jenis. Anda mungkin lebih suka (owner_id, type)
jika itu mencerminkan domain Anda dengan lebih baik.)
Entitasnya adalah:
@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;
}
Sekarang, jika Anda benar-benar tidak ingin memperkenalkan tabel untuk PhoneOwner
, maka Anda mungkin bisa keluar darinya menggunakan tampilan. Seperti ini:
create view PhoneOwner as select distinct owner_id from Phone;
Sejauh yang diketahui oleh penyedia JPA, ini adalah tabel, dan akan mendukung kueri yang perlu dilakukan untuk membaca data.
Namun, itu tidak akan mendukung sisipan. Jika Anda perlu menambahkan telepon untuk pemilik yang saat ini tidak ada dalam database, Anda harus memutar ke belakang dan menyisipkan baris langsung ke Phone
. Tidak terlalu bagus.