2009-10-26 29 views
10

Một thử thách khi sử dụng ngủ đông là các lớp được mang phải có một hàm tạo mặc định . Vấn đề là không có điểm rõ ràng nơi lớp được khởi tạo và bất biến có thể được kiểm tra.Kiểm tra các bất biến trong các lớp được ánh xạ hibernate

Nếu một lớp có bất biến phụ thuộc vào nhiều hơn một thuộc tính thì thiết kế lớp sẽ trở nên phức tạp. Hãy bắt đầu với thiết kế trường xanh giả định:

public class A { 
    private int x; 
    private int y; 

    public A(int x, int y) { 
     this.x = x; 
     this.y = y; 
     checkInvariants(this.x, this.y); 
    } 

    private void checkInvariants(int x, int y) { 
     if (x + y « 0) throw new IllegalArgumentException(); 
    } 
} 

Đây là cơ sở triển khai không đáp ứng các yêu cầu ngủ đông. Bất biến được kiểm tra trong hàm tạo. (Phương pháp Nội dung của checkInvariants() không quan trọng nó chỉ được trình bày để minh họa rằng bất biến lớp có thể phụ thuộc vào nhiều hơn một thuộc tính.)

Lớp có thể được sử dụng như sau:

new A(0, 0); 
new A(-1, 0); //invalid 

Để đáp ứng các yêu cầu hibernate một workaround là thêm một private constructor mặc địnhsử dụng truy nhập trường. (Tôi bỏ qua ánh xạ hibernate.)

public class H { 
    int x; 
    int y; 

    public H(int x, int y) { 
     this.x = x; 
     this.y = y; 
     checkInvariants(this.x, this.y); 
    } 

    H(){} 

    private void checkInvariants(int x, int y) { 
     if (x + y « 0) throw new IllegalArgumentException(); 
    } 
} 

này có hai nhược điểm chính: * Bạn đang bắt đầu triển khai mã mà phụ thuộc vào khách hàng (Hibernate). Lý tưởng nhất, một lớp không biết người gọi của nó. * Một vấn đề cụ thể với cách giải quyết này là các trường hợp bắt đầu bằng ngủ đông là chưa được kiểm tra nếu đáp ứng các bất biến. Bạn đang tin tưởng dữ liệu được tải từ cơ sở dữ liệu có vấn đề. Ngay cả khi ứng dụng của bạn là người duy nhất sử dụng lược đồ cơ sở dữ liệu cụ thể này, luôn có khả năng các thay đổi đặc biệt của các quản trị viên.

Một cách giải quyết thứ hai là để bất biến kiểm tra trong người dùng mã:

Rõ ràng, điều này làm cho các mã sử dụng phức tạp hơndễ bị lỗi. Thiết kế này không đáp ứng được kỳ vọng rằng một cá thể nhất quán sau khi tạo và duy trì sự nhất quán sau mỗi lần thay đổi trạng thái (gọi phương thức). Mỗi người dùng phải kiểm tra các bất biến cho mỗi cá thể mà anh ta tạo ra (có thể gián tiếp với hibernate).

Có một giải pháp tốt hơn cho vấn đề này đó là:

  • không quá phức tạp
  • mà bạn không biết rõ ràng về người sử dụng
  • mà không có một sự phụ thuộc vào khuôn khổ ngủ đông?

Tôi cho rằng một số ràng buộc phải được nới lỏng để có được giải pháp thực dụng. Khó khăn duy nhất là không có sự phụ thuộc vào khung công tác ngủ đông. (Hibernate mã cụ thể bên ngoài các đối tượng miền là okay).

(Ngừng tò mò: có khung ORM hỗ trợ “công cụ tiêm xây dựng” không?)

+0

Loại quy tắc này nên được thực thi bởi ràng buộc kiểm tra trong cơ sở dữ liệu, điều này sẽ tránh được các trường hợp được tải trái với quy tắc. – araqnid

+0

Câu hỏi không hiệu quả. Bạn đang cố gắng áp đặt một 'triết lý tùy ý' của việc thực thi ràng buộc vĩnh viễn, vào dữ liệu có thể thay đổi theo định nghĩa - không thể cập nhật trong một bản cập nhật nguyên tử duy nhất. Bạn không cố gắng giải quyết một vấn đề kinh doanh hoặc thiết kế hợp lệ, chỉ cần đẩy BS vào một xe đẩy. –

+1

@ThomasW Mọi người đều có một ngày tồi tệ mọi lúc rồi. Chúc mừng! (Có lẽ nó có thể giúp nếu bạn tìm ra một bất biến là gì.) –

Trả lời

5

Đầu tiên, hãy để tôi giải quyết những "nhược điểm" bạn đã liệt kê cách tiếp cận đầu tiên của bạn:

Bạn đang bắt đầu triển khai mã mà phụ thuộc vào khách hàng (Hibernate). Lý tưởng nhất, một lớp học không biết số người gọi của nó.

Bạn đang sử dụng từ "phụ thuộc" hơi nhanh và lỏng lẻo ở đây. Hibernate không phải là "khách hàng"; đó là một khung bạn (với tư cách là nhà phát triển/kiến ​​trúc sư/những gì bạn có) đã chọn để thực hiện sự kiên trì của bạn. Vì vậy, bạn sẽ có một số mã ở đâu đó sử dụng (và, do đó, phụ thuộc vào) Hibernate. Điều đó nói rằng, có NO phụ thuộc vào Hibernate trong đối tượng miền của bạn ở trên. Có một constructor no-arg là một yêu cầu ngữ nghĩa nếu bạn muốn; nó không giới thiệu một sự phụ thuộc thực sự. Chuyển đổi Hibernate thành JPA/TopLink/jdbc thô/những gì có bạn và bạn sẽ không phải thay đổi một thứ duy nhất trong mã đối tượng miền của bạn.

Một vấn đề cụ thể với cách giải quyết này là trường hợp khởi xướng bởi ngủ đông không được kiểm tra nếu đáp ứng các bất biến. Bạn đang tin cậy dữ liệu được tải từ cơ sở dữ liệu mà là có vấn đề. Ngay cả khi ứng dụng của bạn là ứng dụng duy nhất sử dụng lược đồ cơ sở dữ liệu cụ thể có luôn có khả năng các thay đổi về quản trị viên ad-hoc.

Bạn không phải "tin tưởng" dữ liệu (chi tiết bên dưới). Tuy nhiên, tôi không nghĩ rằng lập luận này có giá trị. Nếu bạn đang sửa đổi dữ liệu của mình trong nhiều ứng dụng, bạn nên thực hiện xác thực ở một số lớp thấp hơn phổ biến thay vì dựa vào từng ứng dụng riêng lẻ để xác thực dữ liệu. Lớp chung cho biết có thể là chính cơ sở dữ liệu (trong các trường hợp đơn giản) hoặc một lớp dịch vụ cung cấp API chung được nhiều ứng dụng của bạn sử dụng.

Hơn nữa, khái niệm về quản trị làm thay đổi trực tiếp cơ sở dữ liệu như một phần của thói quen hàng ngày là hoàn toàn vô lý.Nếu bạn đang nói về các trường hợp đặc biệt (sửa lỗi, bạn có gì) họ nên được đối xử như vậy (có nghĩa là, có lẽ một cái gì đó như thế này xảy ra rất hiếm khi và gánh nặng để xác nhận những thay đổi "quan trọng" đó là ai thay đổi; không phải trên mỗi ứng dụng của bạn trong ngăn xếp).

Tất cả những gì đã nói, nếu bạn muốn xác thực đối tượng của mình khi chúng là được tải, điều này là khá đơn giản để đạt được. Xác định giao diện Valid có phương thức validate() và yêu cầu mọi đối tượng miền có liên quan triển khai nó. Bạn có thể gọi phương thức đó từ:

  1. DAO/dịch vụ của bạn sau khi đối tượng đã được tải.
  2. Hibernate Interceptor or Listener - cả hai đều được thiết lập trong cấu hình Hibernate; tất cả những gì bạn cần làm là thực hiện một trong hai để kiểm tra xem đối tượng đang được nạp có thực hiện Valid hay không, và nếu có, hãy gọi phương thức.
  3. Hoặc bạn có thể sử dụng Hibernate Validator, tuy nhiên S W sẽ buộc đối tượng miền của bạn vào Hibernate như bạn muốn chú thích chúng.

Cuối cùng, theo "tiêm xây dựng" đi - Tôi không biết bất kỳ khung nào hỗ trợ nó trực tiếp. Lý do cho điều này khá đơn giản - nó chỉ có ý nghĩa đối với bất biến thực thể (bởi vì một khi bạn đã định cư bạn phải đối phó với xác thực anyway) và do đó có nghĩa là rất nhiều công việc (xử lý ánh xạ tham số constructor, v.v ...) cho hầu như không có hiệu ứng ròng. Trong thực tế, nếu bạn là rằng quan tâm đến việc có một constructor không có arg cho các đối tượng bất biến, bạn có thể luôn luôn không bản đồ chúng như những thực thể và thay vào đó tải chúng qua HQL mà không hỗ trợ constructor injection:

select new A(x, y) from ... 

Cập nhật (để giải quyết các điểm từ nhận xét của Thomas):

  1. Tôi chỉ đề cập đến trình chặn đối với tính đầy đủ; người nghe thích hợp hơn nhiều trong trường hợp này. PostLoadEventListener được gọi sau khi thực thể được khởi tạo hoàn toàn.
  2. Một lần nữa, không có hàm tạo-arg nào không phụ thuộc. Đó là một hợp đồng, vâng, nhưng nó không gắn mã của bạn với Hibernate theo bất kỳ cách nào. Và theo như hợp đồng đi, nó là một phần của đặc tả javabean (trên thực tế, nó ít hạn chế bởi vì constructor không phải là công khai) vì vậy trong phần lớn các trường hợp bạn sẽ làm theo nó anyway.
  3. Truy cập cơ sở dữ liệu. "Tái cấu trúc và di chuyển cơ sở dữ liệu là phổ biến" - có, chúng được. Nhưng nó chỉ là mã; bạn viết nó, bạn thử nghiệm nó, bạn chạy thử nghiệm tích hợp, bạn triển khai nó để sản xuất. Nếu đối tượng mô hình miền của bạn sử dụng một số phương pháp StringUtil.compare() bạn đã viết trong một số lớp tiện ích, bạn không có nó kiểm tra lại kết quả, phải không? Tương tự, đối tượng miền không cần phải kiểm tra xem tập lệnh di chuyển của bạn có bị hỏng hay không - bạn nên có các kiểm tra thích hợp cho điều đó. "Khả năng thực hiện các truy vấn đặc biệt ... là một trong những tính năng" - tuyệt đối. Truy vấn. Như trong các truy vấn "chỉ đọc" được sử dụng để báo cáo, ví dụ (mặc dù vậy, trong nhiều trường hợp, nó thích hợp hơn để đi qua API). Nhưng dữ liệu thủ công thao tác là không khẩn cấp - tuyệt đối không.
  4. Tính đột biến làm cho việc xây dựng nhà máy phun không liên quan. Tôi không nói rằng bạn không thể có constructor không mặc định - bạn có thể, và bạn có thể sử dụng nó trong mã của bạn.Nhưng nếu bạn có các phương thức setter bạn không thể sử dụng hàm tạo của bạn để xác nhận hợp lệ, cho dù đó là hay không có thực sự không quan trọng.
  5. tiêm và xây dựng liên kết HQL. Nested các nhà xây dựng sẽ không hoạt động như tôi biết (ví dụ bạn không thể viết select new A(x, y, new B(c, d)); vì vậy để tìm nạp các liên kết bạn sẽ cần phải truy xuất chúng dưới dạng các thực thể trong mệnh đề chọn có nghĩa là chúng cần phải có arg constructors mình :-) Hoặc bạn có thể có một constructor trên thực thể "chính" mà sẽ đưa tất cả các thuộc tính lồng nhau cần thiết như các đối số và xây dựng/populates các hiệp hội trong nội bộ nhưng đó là đường biên giới điên :-)
+0

Sự kết hợp giữa các máy đánh chặn hibernate và một giao diện xác thực là một giải pháp tốt đẹp. Mã người dùng có thể được giữ nguyên không thay đổi bằng cách sử dụng hàm tạo A (x, y) và hàm tạo riêng + truy cập trường là + xác nhận hợp lệ được truy cập bằng hibernate. (Việc thực thi trình chặn sẽ không dễ dàng như vậy. Thực thể không được khởi tạo khi onLoad được gọi.) –

+0

Sự phụ thuộc: Mã miền của bạn không độc lập với khung công tác ORM. Có một hợp đồng gạch dưới bạn phải thực hiện để làm việc với khung công tác và đó là điều bạn phụ thuộc. (Nó không quan trọng là hợp đồng có thể giống nhau cho các khuôn khổ khác nhau. Nó vẫn còn đó.) –

+0

Truy cập trực tiếp vào cơ sở dữ liệu: Một lần là đủ. Ứng dụng của bạn không phù hợp với tất cả các hậu quả. Vì vậy, nó là công việc của tôi để kiểm tra xem tất cả các dữ liệu là phù hợp (nếu không doanh nghiệp sẽ được nhân đôi trong kịch bản cơ sở dữ liệu của bạn). Cơ sở dữ liệu tái cấu trúc và di chuyển là phổ biến và không phải là một trường hợp góc. Khả năng thực hiện các truy vấn đặc biệt đối với một cơ sở dữ liệu quan hệ là một trong các tính năng tại sao các cơ sở dữ liệu quan hệ vẫn được sử dụng. –

2

Một cách tiếp cận sẽ dựa trên "lớp I" của bạn, nhưng với chính lớp đó, hãy gọi checkInvariants() lần đầu tiên các trường thực sự được sử dụng. Điều này cho phép các trường tạm thời không hợp lệ trong khi gán (sau khi setX() được gọi nhưng trước khi setY() chẳng hạn). Nhưng nó vẫn đảm bảo rằng chỉ có dữ liệu hợp lệ mới được sử dụng.

lớp dụ bạn không bao giờ sử dụng các giá trị, vì vậy tôi sẽ đoán các giá trị có thể được truy cập thông qua GetX(), GetY(), và doSomething(), chẳng hạn như:

public int getX(){ 
    return x; 
} 

public int getY(){ 
    return y; 
} 

public void doSomething(){ 
    x = x + y; 
} 

Bạn muốn thay đổi nó như thế này:

private boolean validated = false; 

public int getX(){ 
    if (!validated) { 
     checkInvariants(); 
    } 

    return x; 
} 

public int getY(){ 
    if (!validated) { 
     checkInvariants(); 
    } 
    return y; 
} 

public void doSomething(){ 
    if (!validated) { 
     checkInvariants(); 
    } 
    x = x + y; 
} 

public void checkInvariants() { 
    validated = true; 
    if (x + y « 0) throw new IllegalArgumentException(); 
} 

Bạn có thể di chuyển 'if (! validated)' vào checkInvariants() nếu muốn.

Bạn cũng có thể xem xét Trình xác thực Hibernate, mặc dù nó có thể không đáp ứng yêu cầu về khung độc lập của bạn. http://docs.huihoo.com/hibernate/annotations-reference-3.2.1/validator.html

3

các Coherence của mô hình quan hệ là khái niệm cực kỳ quan trọng đối với tiếng thở hổn hển. Do tính ổn định toán học của các nguyên tắc vốn có của các nguyên tắc tuân theo mô hình quan hệ , chúng tôi hoàn toàn có thể tự tin rằng kết quả của bất kỳ truy vấn nào của chúng tôi thực sự tạo ra thực tế hợp lệ. - Từ cuốn sách The Art of Sql - Stephane Faroult

cơ sở dữ liệu của bạn nên bao gồm các sự kiện hợp lệ hay sự thật, dữ liệu mà bạn tải từ cơ sở dữ liệu không nên đòi hỏi quy trình kiểm soát thêm, thực tế là bạn kéo nó ra từ cơ sở dữ liệu phải đủ tốt.

Nhưng nếu bạn lo ngại rằng có dữ liệu xấu, thì tôi sẽ đề xuất rằng có một vấn đề nghiêm trọng hơn.

Tôi đã thấy các giải pháp khác nhau, từ việc có cơ sở dữ liệu dàn dựng, nơi tất cả dữ liệu được quét, xác thực trước khi nó vào cơ sở dữ liệu thực để xem xét bất kỳ sửa chữa thủ công nào trước khi nhập.

Dù bằng cách nào, khi bạn đã làm hỏng dữ liệu hoặc báo cáo sai trong cơ sở dữ liệu của mình, bạn có thể tin tưởng điều gì khác?

+0

Xin lỗi nếu tôi được rao giảng – GDR

+0

Amen to that (and +1) – ChssPly76

Các vấn đề liên quan