Đáng buồn thay, tôi nghĩ rằng sự khác biệt nhỏ mà bạn chỉ giữ một bảng là vấn đề ở đây.
Xem phần khai báo của PhoneId
lớp (mà tôi muốn đề xuất tốt hơn nên được gọi là PhoneOwner
hoặc cái gì đó tương tự):
@Entity
@Table(name="Phones")
public class PhoneId {
Khi bạn tuyên bố rằng một lớp là một thực thể được ánh xạ tới một bảng nhất định, bạn đang thực hiện một tập hợp các xác nhận, trong đó hai xác nhận là đặc biệt quan trọng ở đây. Thứ nhất, có một hàng trong bảng cho mỗi phiên bản của thực thể và ngược lại. Thứ hai, có một cột trong bảng cho mỗi trường vô hướng của thực thể và ngược lại. Cả hai điều này đều là trọng tâm của ý tưởng về ánh xạ quan hệ đối tượng.
Tuy nhiên, trong lược đồ của bạn, không có khẳng định nào trong số này được giữ nguyên. Trong dữ liệu bạn đã cung cấp:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Có hai hàng tương ứng với thực thể có owner_id
1, vi phạm khẳng định đầu tiên. Có các cột TYPE
và NUMBER
không được ánh xạ tới các trường trong thực thể, vi phạm khẳng định thứ hai.
(Nói rõ hơn, không có gì sai khi bạn khai báo về Phone
lớp học hoặc phones
trường - chỉ PhoneId
thực thể)
Do đó, khi nhà cung cấp JPA của bạn cố gắng chèn một bản sao của PhoneId
vào cơ sở dữ liệu, nó gặp sự cố. Vì không có ánh xạ nào cho TYPE
và NUMBER
trong PhoneId
, khi nó tạo SQL cho phần chèn, nó không bao gồm các giá trị cho chúng. Đây là lý do tại sao bạn gặp lỗi mà bạn thấy - nhà cung cấp ghi INSERT INTO Phones (owner_id) VALUES (?)
, PostgreSQL được coi là INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, bị từ chối.
Ngay cả khi bạn đã quản lý để chèn một hàng vào bảng này, sau đó bạn sẽ gặp rắc rối khi lấy một đối tượng từ nó. Giả sử bạn đã yêu cầu phiên bản của PhoneId
với owner_id
1. Nhà cung cấp sẽ ghi số lượng SQL vào select * from Phones where owner_id = 1
và nó sẽ mong đợi rằng sẽ tìm thấy chính xác một hàng, hàng mà nó có thể ánh xạ tới một đối tượng. Nhưng nó sẽ tìm thấy hai hàng!
Tôi e rằng giải pháp là sử dụng hai bảng, một bảng cho PhoneId
và một cho Phone
. Bảng cho PhoneId
sẽ rất đơn giản, nhưng nó cần thiết để vận hành chính xác bộ máy JPA.
Giả sử bạn đổi tên PhoneId
tới PhoneOwner
, các bảng cần có dạng:
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)
)
(Tôi đã tạo (owner_id, number)
khóa chính cho Phone
, với giả định rằng một chủ sở hữu có thể có nhiều hơn một số thuộc một loại nhất định, nhưng sẽ không bao giờ có một số được ghi dưới hai loại. Bạn có thể thích (owner_id, type)
nếu điều đó phản ánh tốt hơn miền của bạn.)
Các thực thể sau đó là:
@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;
}
Bây giờ, nếu bạn thực sự không muốn giới thiệu một bảng cho PhoneOwner
, thì bạn có thể thoát ra khỏi nó bằng cách sử dụng một chế độ xem. Như thế này:
create view PhoneOwner as select distinct owner_id from Phone;
Theo như nhà cung cấp JPA có thể nói, đây là một bảng và nó sẽ hỗ trợ các truy vấn cần thực hiện để đọc dữ liệu.
Tuy nhiên, nó sẽ không hỗ trợ chèn. Nếu bạn cần thêm điện thoại cho chủ sở hữu hiện không có trong cơ sở dữ liệu, bạn cần phải quay lại và chèn một hàng trực tiếp vào Phone
. Không đẹp lắm.