2013-08-27 35 views
13

Tôi có truy vấn sau đây và phương phápHibernate HQL tham gia Truy xuất không đệ quy lấy

private static final String FIND = "SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId"; 

@Override 
public Domain find(Long domainId) { 
    Query query = getCurrentSession().createQuery(FIND); 
    query.setLong("domainId", domainId); 
    return (Domain) query.uniqueResult(); 
} 

Với Domain như

@Entity 
@Table 
public class Domain { 
    @Id 
    @GenericGenerator(name = "generator", strategy = "increment") 
    @GeneratedValue(generator = "generator") 
    @Column(name = "domain_id") 
    private Long domainId; 

    @Column(nullable = false, unique = true) 
    @NotNull 
    private String name; 

    @Column(nullable = false) 
    @NotNull 
    @Enumerated(EnumType.STRING) 
    private DomainType type; 

    @OneToMany(cascade = { 
      CascadeType.PERSIST, 
      CascadeType.MERGE 
    }, fetch = FetchType.EAGER) 
    @JoinTable(joinColumns = { 
      @JoinColumn(name = "domain_id") 
    }, inverseJoinColumns = { 
      @JoinColumn(name = "code") 
    }) 
    @NotEmpty 
    @Valid // needed to recur because we specify network codes when creating the domain 
    private Set<NetworkCode> networkCodes = new HashSet<>(); 

    @ManyToMany(fetch = FetchType.EAGER) 
    @JoinTable(joinColumns = { 
      @JoinColumn(name = "parent", referencedColumnName = "domain_id") 
    }, inverseJoinColumns = { 
      @JoinColumn(name = "child", referencedColumnName = "domain_id") 
    }) 
    private Set<Domain> operators = new HashSet<>(); 
    // more 
} 

Tôi mong chờ truy vấn duy nhất này để lấy Set<NetworkCode>Set<Domain> mối quan hệ, nhưng nó không. Nói rằng Domain tôi truy vấn có hai nhà khai thác, Hibernate sẽ thực hiện 1 + 2 * 2 = 5 truy vấn

Hibernate: select distinct domain0_.domain_id as domain1_1_0_, domain2_.domain_id as domain1_1_1_, networkcod4_.code as code2_2_, domain0_.name as name1_0_, domain0_.type as type1_0_, domain2_.name as name1_1_, domain2_.type as type1_1_, operators1_.parent as parent1_0__, operators1_.child as child4_0__, networkcod3_.domain_id as domain1_1_1__, networkcod3_.code as code5_1__ from domain domain0_ left outer join domain_operators operators1_ on domain0_.domain_id=operators1_.parent left outer join domain domain2_ on operators1_.child=domain2_.domain_id inner join domain_network_codes networkcod3_ on domain0_.domain_id=networkcod3_.domain_id inner join network_code networkcod4_ on networkcod3_.code=networkcod4_.code where domain0_.domain_id=? 
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=? 
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=? 
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=? 
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=? 

Tôi đoán đây là bởi vì tôi đang tham gia các nhà khai thác Domain yếu tố nhưng họ phải tham gia vào bản thân. Có một truy vấn HQL tôi có thể thực hiện mà sẽ làm cả hai?

Trả lời

10

Nếu bạn biết rằng bạn chỉ có hai cấp độ trong cây của mình, bạn có nghĩ đến việc tham gia một cấp độ sâu hơn không. Một cái gì đó như dưới đây?

SELECT DISTINCT domain FROM Domain domain 
    LEFT OUTER JOIN FETCH domain.operators operators1 
    LEFT OUTER JOIN FETCH domain.networkCodes 
    LEFT OUTER JOIN FETCH operators1.operators operators2 
    LEFT OUTER JOIN FETCH operators1.networkCodes 
WHERE domain.domainId = :domainId 
3

Bạn đã đánh dấu các liên kết của mình là EAGER. Vì vậy, bất cứ điều gì bạn làm trong truy vấn của bạn, Hibernate sẽ tải tất cả các tên miền liên quan và mã mạng của các tên miền được tải. Và nó sẽ tải các tên miền và mã mạng của các tên miền bổ sung, v.v. cho đến khi tất cả các tải tập hợp trả về các bộ sưu tập rỗng hoặc các thực thể đã được tải.

Để tránh điều đó, hãy làm cho bộ sưu tập của bạn lười biếng (như chúng được mặc định). Sau đó tải một tên miền với các toán tử của nó và các mã mạng của nó sẽ tải chỉ.

+0

Các bộ sưu tập đó cần phải là 'EAGER' để chúng được khởi tạo trong giới hạn' Giao dịch' của tôi. Tôi sử dụng chúng trong lớp xem của tôi. Tôi tự hỏi nếu nó có thể tải tất cả các nhà khai thác/networkcodes trong một swoop, thay vì một truy vấn cho mỗi 'Domain'. –

+1

Không phải không sử dụng SQL độc quyền (xem http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm chẳng hạn), hoặc không điều chỉnh mô hình của bạn để giới thiệu, ví dụ: gốc của cây trong mỗi nút con và tải tất cả các tên miền có cùng một nút gốc. Bạn không thể đơn giản làm mọi thứ lười biếng và sử dụng OpenSessionInView? Làm cho hiệp hội tomany háo hức gần như luôn luôn là một ý tưởng tồi, đặc biệt là nếu đệ quy: điều này làm cho nó háo hức ** mỗi ** trường hợp sử dụng, bao gồm cả những người chỉ cần tải một thực thể cụ thể. –

+0

Vấn đề không phải là tải chậm. Tất cả dữ liệu đó sẽ được truy xuất bất kể. Tôi chỉ không muốn gửi tấn yêu cầu SQL. Tôi sẽ xem xét cẩn thận tài liệu bạn đã liên kết. Cấp cho nó vài ngày. Cảm ơn bạn. –

1

Nó không được ghi nhận là tốt, nhưng bạn đã thử đặt FetchMode? Bạn có thể làm như vậy bằng cách sử dụng API tiêu chí: domainCriteria.setFetchMode("operators", JOIN) hoặc sử dụng @Fetch(JOIN) theo định nghĩa quan hệ.

Chú thích (và chỉ chú thích có vẻ như) cũng cho phép đặt chế độ tìm nạp SUBSELECT, ít nhất nên hạn chế Hibernate để thực thi 3 truy vấn tối đa. Không biết tập dữ liệu của bạn, tôi cho rằng đây sẽ là cách để đi cho bạn, như một sự tham gia chất béo lớn trên những bảng đó dường như không quá lành mạnh. Tốt nhất để con số nó ra cho chính mình, tôi đoán ...

+0

Truy vấn HQL của tôi đã thực hiện 'FETCH JOIN'. Tôi sẽ thử 'SUBSELECT'. –

+0

Tôi không chắc liệu ký pháp HQL bạn sử dụng có giống với chế độ tìm nạp "" được hiểu bởi hibernate hay không. Ví dụ, HQL 'JOIN FETCH' chỉ là phương tiện để nói" oh và bằng cách này, khởi tạo quan hệ đầy đủ, và không chỉ lấy các ID "- Hibernate không giải thích điều này như là loại chiến lược cần áp dụng cho tìm nạp, vì tiền tố 'JOIN' chỉ đơn giản là cú pháp cần thiết để áp dụng' FETCH'. Tuy nhiên, vì đây là hư không được đề cập một cách rõ ràng, tôi có thể đã được trộn lên kinh nghiệm của tôi với HQL và sử dụng chú thích. – skirsch

+0

Oh và khi bạn đưa ra HQL của bạn: như bạn đã chú thích các giả thiết với 'EAGER', truy vấn của bạn có mang lại kết quả khác với một tên miền SELECT DISTINCT đơn giản miền miền WHERE domain.domainId =: domainId không? Và tại sao 'DISTINCT'? như 'domainId' là PK, làm thế nào có thể có nhiều hơn một kết quả? – skirsch

0

Vì bạn đã xác định FetchType.EAGER cho cả networkCodesoperators, bất cứ khi nào bạn sẽ truy vấn domain, hibernate sẽ được tải cả networkCodesoperators. Đó là toàn bộ ý tưởng của EAGER chế độ

lấy Vì vậy, bạn có thể thay đổi truy vấn của bạn đơn giản để sau:

private static final String FIND = "SELECT DISTINCT domain FROM Domain domain WHERE domain.domainId = :domainId"; 

chi tiết API here

Cheers !!

+1

Tìm nạp EAGER không có nghĩa là hibernate sẽ thực hiện một truy vấn sql đơn lẻ: nó có nghĩa là tất cả các đối tượng phải tìm nạp háo hức sẽ được tìm nạp cùng lúc với đối tượng gốc (tức là KHÔNG nhất thiết trong cùng một truy vấn) – ben75

+0

@ ben75 - Có bạn là đúng, không nhất thiết phải cùng một truy vấn nhưng chúng sẽ được nạp cùng một lúc. –

+0

Lưu ý rằng truy vấn của tôi không đơn giản là 'JOIN', nó là' LEFT OUTER JOIN'. Tôi không thể loại bỏ điều đó vì các thực thể được trả về sẽ không chính xác. –

0

Quan sát đầu tiên của tôi là bạn không cần phải viết truy vấn HQL có chứa tham gia nếu ánh xạ của bạn cho biết rằng chúng phải được tải háo hức.

Tuy nhiên, bạn có thể yêu cầu Hibernate sử dụng chiến lược tìm nạp làm phụ chọn nếu bạn không muốn sử dụng kết nối.

Hibernate tạo truy vấn SQL để tải các đối tượng trong khi khởi động dựa trên các ánh xạ được chỉ định và lưu trữ nó.Tuy nhiên, trong trường hợp của bạn, bạn có một mối quan hệ lồng nhau với chính mình và độ sâu tùy ý, vì vậy có vẻ như không thể để ngủ đông quyết định trước khi nhập sql để tìm nạp chính xác. Vì vậy, nó sẽ cần phải gửi nhiều truy vấn tham gia tùy thuộc vào độ sâu của tên miền mẹ bạn đang truy vấn khi chạy.

Với tôi có vẻ như bạn đang nghĩ rằng HQL và kết quả SQL/('s) trong trường hợp của bạn có thể có một đến một correpondence mà không phải là đúng. Với HQL bạn truy vấn đối tượng và orm quyết định cách tải đối tượng đó và quan hệ của nó (háo hức/lười) dựa trên ánh xạ hoặc bạn có thể chỉ định chúng trong thời gian chạy quá (ví dụ, một liên kết lười trong ánh xạ có thể bị ghi đè bởi truy vấn api nhưng không ngược lại). Bạn có thể nói cho orm những gì cần tải (đánh dấu của tôi háo hức hoặc lười biếng) và làm thế nào để tải háo hức (hoặc sử dụng tham gia/phụ chọn).

CẬP NHẬT

Khi tôi chạy truy vấn sau đây trên mô hình tên miền của bạn

SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId"; 

Tôi có thể thấy rằng các bộ sưu tập networkCode và điều hành là thẩm PersistentSet (đây là Hibernate wrapper) và cả hai đều có thuộc tính khởi tạo được đặt thành true. Cũng trong ngữ cảnh phiên bên dưới, tôi có thể thấy các tên miền có tên miền và toán tử được liệt kê. Vì vậy, điều gì khiến bạn nghĩ rằng họ không được háo hức nạp?

Đây là cách tên miền của tôi trông giống như

@Entity 
@Table 
public class Domain { 
    @Id 
    @GenericGenerator(name = "generator", strategy = "increment") 
    @GeneratedValue(generator = "generator") 
    @Column(name = "domain_id") 
    private Long domainId; 

    @Column(nullable = false, unique = true) 
    private String name; 

    @Column(nullable = false)  
    @Enumerated(EnumType.STRING) 
    private DomainType type; 

    @OneToMany(mappedBy = "domain",cascade = { 
      CascadeType.PERSIST, 
      CascadeType.MERGE 
    }, fetch = FetchType.EAGER) 
    private Set<NetworkCode> networkCodes = new HashSet<NetworkCode>(); 

    @ManyToMany(mappedBy="parent",fetch = FetchType.EAGER, cascade=CascadeType.ALL) 
    private Set<Domain> operators = new HashSet<Domain>(); 
    // more 

    @ManyToOne 
    private Domain parent; 

    public String getName() { 
     return name; 
    } 


    public void setName(String name) { 
     this.name = name; 
    } 


public DomainType getType() { 
     return type; 
    } 

    public void setType(DomainType type) { 
     this.type = type; 
    } 


    public Set<Domain> getOperators() { 
     return operators; 
    } 


    public Long getDomainId() { 
     return domainId; 
    } 


    public void setDomainId(Long domainId) { 
     this.domainId = domainId; 
    } 


    public void setOperators(Set<Domain> operators) { 
     this.operators = operators; 
    } 

    public void addDomain(Domain domain){ 
     getOperators().add(domain); 
     domain.setParent(this); 
    } 


    public Domain getParent() { 
     return parent; 
    } 


    public void setParent(Domain parent) { 
     this.parent = parent; 
    } 

    public void addNetworkCode(NetworkCode netWorkCode){ 
     getNetworkCodes().add(netWorkCode); 
     netWorkCode.setDomain(this); 
    } 

enter image description here

+0

'JOIN' trong HQL không chỉ cho tải' EAGER', nó còn cho 'LEFT OUTER'. Không phải mọi miền đều có 'operator' hoặc' networkCodes'. 'LEFT OUTER JOIN' là cần thiết cho việc ánh xạ chính xác. –

+0

Xem cập nhật của tôi ở trên. – Shailendra

+0

Vấn đề không phải là họ không được háo hức nạp. Đó là có IMO quá nhiều truy vấn SQL được tạo ra. Tôi nghĩ rằng Hibernate có thể làm điều đó với ít hơn. –

12

Quan Hệ Hibernate trình với nhau Fetch Chiến lược .. !!

Hibernate cung cấp 4 chiến lược để lấy dữ liệu:

CHỌN

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) 
@Column(name="id") 
@Fetch(FetchMode.SELECT) 

Trong phương pháp này có Nhiều SQLs bị sa thải. Cái đầu tiên được kích hoạt để truy xuất tất cả các bản ghi trong bảng Phụ huynh. Số còn lại là được kích hoạt để truy xuất hồ sơ cho từng Bản ghi gốc. Điều này về cơ bản là vấn đề N + 1. Truy vấn đầu tiên truy lục N bản ghi từ cơ sở dữ liệu, trong trường hợp này N Hồ sơ gốc. Đối với mỗi Phụ huynh, truy vấn mới sẽ truy xuất Trẻ em. Do đó đối với N N, N truy vấn lấy thông tin từ Bảng con.

THAM GIA

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) 
@Column(name="id") 
@Fetch(FetchMode.JOIN) 

này tương tự như SELECT lấy chiến lược ngoại trừ thực tế là rằng tất cả hồi cơ sở dữ liệu diễn ra trả trước trong JOIN lấy không giống như trong CHỌN nơi nó xảy ra trên một nhu cầu nền tảng. Điều này có thể trở thành một xem xét hiệu suất quan trọng .

subselect

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) 
@Column(name="id") 
@Fetch(FetchMode.SUBSELECT) 

Hai SQLs được sa thải. Một để truy xuất tất cả Phụ huynh và câu trả lời thứ hai sử dụng truy vấn SUBSELECT SUBSELECT trong mệnh đề WHERE để truy xuất tất cả các con có id chính phù hợp.

BATCH

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) 
@Column(name="id") 
@@BatchSize(size=2) 

Các bản đồ kích thước hàng loạt với số của Phụ huynh có con đang lấy. Vì vậy, chúng tôi có thể chỉ định số lượng bản ghi được tìm nạp tại một thời điểm.Nhưng Nhiều truy vấn sẽ được thực hiện. !!

one-to-many & nhiều-nhiều phép - tham gia, Chọn và subselect

đến nhiều-một & one-to-one cho phép - Tham gia và chọn


Hibernate cũng phân biệt giữa (Khi nào là hiệp hội được lấy)

1. Ngay lập tức lấy -

một hiệp hội, bộ sưu tập hoặc thuộc tính được lấy ngay lập tức, khi Phụ Huynh được tải. (Lười biếng = “false”)

2. bộ sưu tập Lazy lấy -

một bộ sưu tập được lấy khi ứng dụng gọi một hoạt động khi bộ sưu tập đó. (Đây là mặc định cho bộ sưu tập (lười biếng = “true”)

3. "Extra-lười biếng" bộ sưu tập quyến rũ -.

yếu tố cá nhân của bộ sưu tập được truy cập từ cơ sở dữ liệu khi cần thiết. Hibernate cố gắng không để lấy toàn bộ bộ sưu tập vào bộ nhớ trừ khi thật cần thiết (thích hợp cho các bộ sưu tập rất lớn) (lười biếng = “thêm”)

4.Proxy lấy -

một hiệp hội độc thân có giá trị được nạp khi một phương pháp khác với getter nhận dạng được gọi khi đối tượng liên quan. (lười biếng = “proxy”)

5. "Không-proxy" lấy -

một hiệp hội độc thân có giá trị được nạp khi biến dụ là truy cập. So với quyến rũ proxy, phương pháp này là ít lười biếng (lười biếng = “no-proxy”)

6. Lazy thuộc tính lấy -.

một thuộc tính hoặc hiệp hội có giá trị duy nhất là lấy khi Ví dụ: biến được truy cập. (Lười biếng = “true”)

one-to-many & nhiều-nhiều phép ngay lập tức, Layzy, Extra Lazy

đến nhiều-một & one-to-one cho phép ngay Proxy, Không Proxy

+0

Đây là một tài liệu tham khảo tốt, nhưng tôi không thể sử dụng '@ Fetch' với HQL. –

+0

bạn có thể thêm chiến lược tìm nạp dưới dạng Extra-lazy trong lớp JPA, để các truy vấn bên trong sẽ không được thực thi trừ khi bạn truy cập vào quan hệ bảng bên trong. Bằng cách này, bạn sẽ chỉ nhận được các giá trị từ bảng chính, phần còn lại sẽ bị bỏ qua và quá với truy vấn SINGLE. – Dileep

2

lập bản đồ háo hức của bạn sẽ chỉ được xem xét tự động bởi Hibernate nếu bạn sử dụng các API tiêu chuẩn cho truy vấn.

Nếu bạn sử dụng HQL, bạn sẽ cần phải thêm từ khóa FETCH vào JOIN của bạn để buộc Hibernate bao gồm các quan hệ trong truy vấn đầu tiên và tránh các truy vấn tiếp theo.

Đây là kiểu Hibernate cụ thể và có thể hoạt động khác trên các ORM khác.

Xem this question/answer cho một góc hơi khác một chút.

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