Monday, June 7, 2010

Kết nối thế giới hướng đối tượng và quan hệ

Từ lâu ước mơ của lập trình viên là làm sao kết hợp được sức mạnh mô phỏng thế giới của hướng đối tượng và logic lưu trữ của hệ cơ sở dữ liệu quan hệ. Hibernate đã ra đời để thỏa mãn giấc mơ đó. Tuy Hibernate không phải là framework duy nhất có thể làm được công việc này nhưng Hibernate là framework phổ biến nhất và được dùng nhiều nhất. Rod Johnson khi viết Spring đã chừa không viết luôn framework mới cho ORM (Object Relational Mapping) vì ông cho rằng Hibernate đã quá đủ.

Hibernate là một framework rất đầy đủ tuy nhiên khá phức tạp để sử dụng nhất là đối với người mới bắt đầu. Thiết kế cơ sở dữ liệu là công việc khó. Thiết kế class cũng không dễ dàng. Thiết kế mapping giữa 2 thế giới này lại càng phức tạp hơn. Như vậy, làm thế nào để giải được bài toán có 3 biến số như trong trường hợp này?

Kinh nghiệm cho thấy đối với 1 yêu cầu đầu vào R, hầu như chỉ có 1 đáp án thiết kế cơ sở dữ liệu mang tính tối ưu. Nhưng sẽ có nhiều hơn 1 thiết kế class: đơn hướng và đa hướng. Và do đó, sẽ có nhiều hơn 1 cách mapping. Bài toán này giải được dễ dàng nếu ta nhìn nhận 1 thực tế là: về mặt bản chất, chỉ tồn tại 3 kiểu quan hệ: quan hệ 1-1, 1-n, và n-n. Và trong thiết kế hướng đối tượng, ta cố gắng đưa về 3 kiểu quan hệ trên.

1. Quan hệ 1-1 và 1-n:
Thực chất quan hệ 1-1 và 1-n có cách xử lý gần như tương đương nhau đó là dùng khóa ngoại để tham khảo tới khóa chính của bảng quan hệ.
Ví dụ, quan hệ giữa bệnh nhân và dân tộc sẽ là quan hệ n-1: một bệnh nhân sẽ thuộc về 1 dân tộc và 1 dân tộc sẽ có nhiều bệnh nhân. Cách xử lý: trong bảng bệnh nhân sẽ có 1 cột mã_dân_tộc và liên kết với khóa chính trong bảng dân_tộc.

Trong thế giới hướng đối tượng ta phải thiết kế class như sau:
public class Patient
{
private int id;
private String lastName;
private String firstName;
private Ethnic ethnic;
}

với Ethnic được thiết kế như sau: (a)
public class Ethnic
{
private int id;
private String name;
}

Như đã nói ở trên, thế giới hướng đối tượng cho phép nhiều hơn 1 đáp án cho thiết kế, ví dụ ta có thể thiết kế class Ethnic như sau: (b)

public class Ethnic
{
private int id;
private String name;
private Set patients;
}

Trên thực tế, cách thiết kế này không phổ biến vì nhu cầu truy xuất danh sách các bệnh nhân từ 1 dân tộc không cao và hoàn toàn có thể thực hiện được thông qua 1 query. Do đó, việc thiết kế phụ thuộc rất nhiều vào bản mô tả yêu cầu.

Xây dựng mapping cho thiết kế trên:
- Trong file Patient.hbm.xml ta thực hiện như sau:

- Trong file Ethnic.hbm.xml ta thực hiện như sau: đối với thiết kế (a) và Đối với thiết kế (b)



2. Quan hệ n-n:
Kiểu quan hệ này nghe có vẻ lãng mạn nhưng ít tồn tại trên thực tế. Một số ví dụ liên quan đến quan hệ này. Trong một dự án sẽ có nhiều nhân viên tham gia làm, một nhân viên sẽ tham gia cùng lúc nhiều dự án. Để có thể lưu trữ được yêu cầu này, ta cần thêm 1 bảng trung gian tạm gọi là duan_nhanvien và lấy 2 khóa chính từ 2 bảng nhanvien và duan lam khóa ngoại.
Theo yêu cầu này ta có thiết kế class và hibernate mapping như sau:
- Thiết kế class:
public class Staff
{
private int id;
private String name;
private Date birthDate;
private Set projects;
}

public class Project
{
private int id;
private String name;
private Date startDate;
private Set staffs;
}

- Mapping:


Khi thực hiện thao tác trên project hoặc staff, Hibernate sẽ tự động tạo bảng trung gian staff_project và cập nhật dữ liệu trên bảng này. NHƯ VẬY, ta không cần phải tạo lớp trung gian để quản lý staff_project.
Ví dụ để thêm mới 1 project với 2 nhân viên ta viết như sau:

Staff staffA = new Staff("David Beckham");
staffManager.save(staffA);

Staff staffB = new Staff("C Ronaldo");
staffManager.save(staffB);

Project project1 = new Project("World cup 2010");
project1.getStaffs().add(staffA);
project1.getStaffs().add(staffB);

projectManager.save(project1);
//////thao tác này sẽ vừa insert 1 dòng vô bảng project, đồng thời 2 dòng vào bảng trung gian staff_project./////

3. Thế giới nhị nguyên: lưỡng nghi sinh tứ tượng
Con số 2 phát sinh ra thế giới, sự kết hợp giữa âm và dương làm cuộc sống sinh sôi. Sự kết hợp của 2 trạng thái 0 và 1 phát sinh ra kỉ nguyên thông tin.
Trong hibernate mapping cũng vậy, điều gì xảy ra nếu trong ví dụ trên ta thêm vào yêu cầu: mỗi nhân viên làm việc cho 1 dự án sẽ có chức vụ khác nhau và số giờ làm việc hằng tuần khác nhau. Như vậy trong bảng trung gian staff_project ta cần phải thêm vào 2 thuộc tính: position và hours. Do đó, mapping n-n trong trường hợp trên sẽ phải được chuyển thành 2 liên kết n-1:

public class Staff
{
....
private Set staff_projects;
}

public class Project
{
....
private Set staff_project;
}

và 1 class trung gian:

public class Staff_Project
{
private Project project;
private Staff staff;
private int hours;
private String position;
}

Hibernate mapping cho trường hợp này sẽ trở lại thành trường hợp n-1 như đã nói ở trên.


4. Thao tác với hibernate:
Hibernate cung cấp đầy đủ các API giúp cho lập trình viên thực hiện các thao tác tương tự như với jdbc. Lập trình viên chỉ cần gọi hàm save hoặc update, Hibernate sẽ "take care" việc lưu trữ xuống database. API của Hibernate cũng cực kỳ phức tạp nhưng có thể tóm tắt lại như sau:
- Configuration: là lớp dùng để khai báo thông tin về database, các file mapping
- SessionFactory: hibernate có 1 nhà máy sản xuất các session
- Session: đây chính là lớp cung cấp các thao tác thêm, xóa, sửa, và truy vấn đối tượng.

Qui trình để thao tác với hibernate: (mã giả)
Configuration conf = new Configuration();
SessionFactory sf = conf.createSessionFactory();
Session session = sf.openSession();
session.save(new Project("World cup"));

5. Spring và Hibernate:
Rod Johnson khi viết Spring đã ưu ái hỗ trợ Hibernate rất đầy đủ mặc dù các phiên bản Hibernate có cách hiện thực khác nhau.
Spring giúp tích hợp với Hibernate thông qua 2 lớp: org.springframework.jdbc.datasource.DriverManagerDataSource và org.springframework.orm.hibernate.LocalSessionFactoryBean.

6. Hibernate Query Language (HQL):
Là một ngôn ngữ tương tự như SQL nhưng dùng riêng cho hướng đối tượng. Đặc điểm:
- Góp phần tăng sức mạnh của Hibernate
- Lưu ý đặc biệt: trả về đối tượng chứ không trả về record set.
- Điều kiện của truy vấn: nối chuỗi bằng String chứ không nối với đối tượng do đó điều kiện truy vấn phải lấy thuộc tính của đối tượng để so sánh.

Ví dụ:
Cho mô hình lớp như sau:
public class Ethnic
{
private int id;
private String name;
private Set patients;
}

public class Patient
{
private int id;
private String lastName;
private String firstName;
private Ethnic ethnic;
}

a. Lấy danh sách các bệnh nhân
query = "from Patient"

b. Lấy danh mục các dân tộc
query = "from Ethnic"

c. Lấy danh sách bệnh nhân người Hoa (phải thực hiện join trên đối tượng)
query = "from Patient as patient inner join patient.Ethnic as ethnic where ethnic.name='Hoa' "

d. Lấy danh sách bệnh nhân có năm sinh là 1985
query = "from Patient as patient where year(patient.birthdate)=1985


Bài tập:
1. Hiện thực mô hình class cho bệnh nhân và dân tộc ở ví dụ 1 như trên.
- Viết hibernate mapping file
- Viết interface PatientManager và hiện thực bằng Hibernate dưới sự hỗ trợ của Spring

2. Hiện thực mô hình class cho ví dụ nhân viên và dự án ở trên (cho cả 2 trường hợp a và b)
- Viết hibernate mapping file
- Viết interface StaffManager và ProjectManager và hiện thực bằng Hibernate dưới sự hỗ trợ của Spring

No comments:

Post a Comment