2015-10-13 15 views
10

Tôi có một truy vấn Tiêu chí bằng cách sử dụng một số phép nối và SQL được tạo ra liệt kê các bảng theo thứ tự sao cho mệnh đề ON đề cập đến một bảng chưa được khai báo.Truy vấn Tiêu chí Hibernate liệt kê các bảng theo thứ tự sai trong SQL được tạo

Để tái tạo sự cố, tôi đã tạo mô hình dữ liệu nhỏ với ba bảng: Bill, Event và bảng nối tiếp BillEvent (Tôi đã liệt kê một kiểm tra JUnit có thể chạy được với các định nghĩa thực thể ở cuối câu hỏi). Truy vấn Tiêu chí sau không thành công với lỗi cú pháp vì event1 được khai báo sau khi được tham chiếu. Làm thế nào tôi có thể viết lại truy vấn này để các bảng được khai báo theo đúng thứ tự?

// Get the most recent BillEvent for a bill 
final Criteria criteria = session.createCriteria(BillEvent.class, "be1") 
        .createCriteria("event", "event1") 
        .createCriteria("be1.bill") 
        .add(Restrictions.eq("id", billId)) 
        .createCriteria("billEvents", "be2") 
        .createCriteria("event", "event2", JoinType.LEFT_OUTER_JOIN, 
          Restrictions.ltProperty("event1.time", "time")) 
        .add(Restrictions.isNull("event2.id")); 

Lỗi:

Caused by: org.h2.jdbc.JdbcSQLException: Column "EVENT1X1_.TIME" not found; SQL statement: 

select 
    this_.id as id1_1_4_, 
    this_.billId as billId3_1_4_, 
    this_.eventId as eventId4_1_4_, 
    this_.note as note2_1_4_, 
    hibernatej2_.id as id1_0_0_, 
    hibernatej2_.label as label2_0_0_, 
    be2x3_.id as id1_1_1_, 
    be2x3_.billId as billId3_1_1_, 
    be2x3_.eventId as eventId4_1_1_, 
    be2x3_.note as note2_1_1_, 
    event2x4_.id as id1_2_2_, 
    event2x4_.time as time2_2_2_, 
    event1x1_.id as id1_2_3_, 
    event1x1_.time as time2_2_3_ 
from 
    test.billEvent this_ 
    inner join test.bill hibernatej2_ on this_.billId=hibernatej2_.id 
    inner join test.billEvent be2x3_ on hibernatej2_.id=be2x3_.billId 
    left outer join test.event event2x4_ 
     on be2x3_.eventId=event2x4_.id 
     and (event1x1_.time<event2x4_.time) 
    inner join test.event event1x1_ on this_.eventId=event1x1_.id 
where 
    hibernatej2_.id=? 
    and event2x4_.id is null 

thử JUnit sử dụng Hibernate 5 và H2:

package com.stackoverflow.repro; 

import static javax.persistence.GenerationType.IDENTITY; 

import java.sql.Timestamp; 
import java.util.HashSet; 
import java.util.List; 
import java.util.Set; 

import javax.persistence.CascadeType; 
import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.FetchType; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 
import javax.persistence.JoinColumn; 
import javax.persistence.ManyToOne; 
import javax.persistence.OneToMany; 
import javax.persistence.Table; 
import javax.persistence.UniqueConstraint; 

import org.h2.Driver; 
import org.hibernate.Criteria; 
import org.hibernate.Session; 
import org.hibernate.SessionFactory; 
import org.hibernate.Transaction; 
import org.hibernate.boot.Metadata; 
import org.hibernate.boot.MetadataSources; 
import org.hibernate.boot.registry.StandardServiceRegistry; 
import org.hibernate.boot.spi.MetadataImplementor; 
import org.hibernate.cfg.Configuration; 
import org.hibernate.cfg.Environment; 
import org.hibernate.criterion.Restrictions; 
import org.hibernate.dialect.H2Dialect; 
import org.hibernate.sql.JoinType; 
import org.hibernate.tool.hbm2ddl.SchemaExport; 
import org.junit.Assert; 
import org.junit.Rule; 
import org.junit.Test; 
import org.junit.rules.TestName; 

public class HibernateJoinTest { 
    private static final String TEST_CATALOG = "test"; 

    @Rule public TestName name = new TestName(); 

    @Entity 
    @Table(name = "bill", catalog = TEST_CATALOG) 
    public static class Bill implements java.io.Serializable { 
     private Integer id; 
     private String label; 
     private Set<BillEvent> billEvents = new HashSet<BillEvent>(0); 

     public Bill() { 
     } 

     public Bill(String label) { 
      this.label = label; 
     } 

     public Bill(String label, Set<BillEvent> billEvents) { 
      this.label = label; 
      this.billEvents = billEvents; 
     } 

     @Id 
     @GeneratedValue(strategy = IDENTITY) 
     @Column(name = "id", unique = true, nullable = false) 
     public Integer getId() { 
      return this.id; 
     } 

     public void setId(Integer id) { 
      this.id = id; 
     } 

     @Column(name = "label", unique = true, nullable = false, length = 45) 
     public String getLabel() { 
      return this.label; 
     } 

     public void setLabel(String label) { 
      this.label = label; 
     } 

     @OneToMany(fetch = FetchType.LAZY, mappedBy = "bill", cascade = { CascadeType.ALL }) 
     public Set<BillEvent> getBillEvents() { 
      return this.billEvents; 
     } 

     public void setBillEvents(Set<BillEvent> billEvents) { 
      this.billEvents = billEvents; 
     } 
    } 

    @Entity 
    @Table(name = "event", catalog = TEST_CATALOG) 
    public static class Event implements java.io.Serializable { 
     private Integer id; 
     private Timestamp time; 
     private Set<BillEvent> billEvents = new HashSet<>(0); 

     public Event() { 
     } 

     public Event(Timestamp time) { 
      this.time = time; 
     } 

     public Event(Timestamp time, Set<BillEvent> billEvents) { 
      this.time = time; 
      this.billEvents = billEvents; 
     } 

     @Id 
     @GeneratedValue(strategy = IDENTITY) 
     @Column(name = "id", unique = true, nullable = false) 
     public Integer getId() { 
      return this.id; 
     } 

     public void setId(Integer id) { 
      this.id = id; 
     } 

     @Column(name = "time", nullable = false) 
     public Timestamp getTime() { 
      return this.time; 
     } 

     public void setTime(Timestamp time) { 
      this.time = time; 
     } 

     @OneToMany(fetch = FetchType.LAZY, mappedBy = "event", cascade = { CascadeType.ALL }) 
     public Set<BillEvent> getBillEvents() { 
      return this.billEvents; 
     } 

     public void setBillEvents(Set<BillEvent> billEvents) { 
      this.billEvents = billEvents; 
     } 
    } 

    @Entity 
    @Table(name = "billEvent", catalog = TEST_CATALOG, uniqueConstraints = @UniqueConstraint(columnNames = {"billId", "eventId"})) 
    public static class BillEvent implements java.io.Serializable { 

     private Integer id; 
     private Bill bill; 
     private Event event; 
     private String note; 

     public BillEvent() { 
     } 

     public BillEvent(Bill bill, Event event) { 
      this.bill = bill; 
      this.event = event; 
     } 

     public BillEvent(Bill bill, Event event, String note) { 
      this.bill = bill; 
      this.event = event; 
      this.note = note; 
     } 

     @Id 
     @GeneratedValue(strategy = IDENTITY) 
     @Column(name = "id", unique = true, nullable = false) 
     public Integer getId() { 
      return this.id; 
     } 

     public void setId(Integer id) { 
      this.id = id; 
     } 

     @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }) 
     @JoinColumn(name = "billId", nullable = false) 
     public Bill getBill() { 
      return this.bill; 
     } 

     public void setBill(Bill bill) { 
      this.bill = bill; 
     } 

     @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }) 
     @JoinColumn(name = "eventId", nullable = false) 
     public Event getEvent() { 
      return this.event; 
     } 

     public void setEvent(Event event) { 
      this.event = event; 
     } 

     @Column(name = "note", unique = true, nullable = false, length = 120) 
     public String getNote() { 
      return this.note; 
     } 

     public void setNote(String note) { 
      this.note = note; 
     } 
    } 

    @Test 
    public void testOuterJoin() { 
     final SessionFactory sessionFactory = createSessionFactory(); 

     final String label = "B0001"; 
     final Timestamp ts = new Timestamp(System.currentTimeMillis()); 
     final Timestamp ts2 = new Timestamp(ts.getTime() + 1000); 
     final String note1 = "First note"; 
     final String note2 = "Second note"; 

     final int billId; 

     try (final Session session = sessionFactory.openSession();) { 
      final Transaction tx = session.beginTransaction(); 

      final Bill bill = new Bill(label); 
      session.save(bill); 
      billId = bill.getId(); 

      final Event event1 = new Event(ts); 
      session.save(event1); 

      final Event event2 = new Event(ts2); 
      session.save(event2); 

      session.save(new BillEvent(bill, event1, note1)); 
      session.save(new BillEvent(bill, event2, note2)); 

      session.flush(); 
      tx.commit(); 
     } 

     try (final Session session = sessionFactory.openSession()) { 
      final Criteria criteria = session.createCriteria(BillEvent.class, "be1") 
        .createCriteria("event", "event1") 
        .createCriteria("be1.bill") 
        .add(Restrictions.eq("id", billId)) 
        .createCriteria("billEvents", "be2") 
        .createCriteria("event", "event2", JoinType.LEFT_OUTER_JOIN, 
          Restrictions.ltProperty("event1.time", "time")) 
        .add(Restrictions.isNull("event2.id")); 


      @SuppressWarnings("unchecked") 
      final List<BillEvent> results = criteria.list(); 

      Assert.assertEquals(1, results.size()); 

      final BillEvent billEvent = results.get(0); 
      Assert.assertEquals(note2, billEvent.getNote()); 
      Assert.assertEquals(ts2, billEvent.getEvent().getTime()); 
     } 
    } 

    private SessionFactory createSessionFactory() { 
     final String dialectClassName = H2Dialect.class.getName(); 
     final Configuration config = 
       new Configuration() 
       .addAnnotatedClass(Bill.class) 
       .addAnnotatedClass(Event.class) 
       .addAnnotatedClass(BillEvent.class); 

     final String dbName = name.getMethodName(); 

     config.setProperty(Environment.DIALECT, dialectClassName); 
     config.setProperty(Environment.DRIVER, Driver.class.getName()); 
     config.setProperty(Environment.URL, "jdbc:h2:mem:"+dbName+";DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS TEST\\; SET SCHEMA TEST"); 
     config.setProperty(Environment.USER, "SA"); 
     config.setProperty(Environment.PASS, ""); 
     config.setProperty(Environment.SHOW_SQL, "true"); 
     config.setProperty(Environment.FORMAT_SQL, "true"); 

     final StandardServiceRegistry serviceRegistry = config.getStandardServiceRegistryBuilder().applySettings(config.getProperties()).build(); 

     final MetadataSources sources = 
       new MetadataSources(serviceRegistry) 
       .addAnnotatedClass(Bill.class) 
       .addAnnotatedClass(Event.class) 
       .addAnnotatedClass(BillEvent.class); 

     final Metadata metadata = sources.buildMetadata(); 

     final SchemaExport export = new SchemaExport((MetadataImplementor) metadata); 
     export.create(false, true); 

     final SessionFactory sessionFactory = config.buildSessionFactory(); 
     return sessionFactory; 
    } 
} 

Edit: Vấn đề ở đây dường như là Hibernate liệt kê các bảng theo thứ tự abc bằng tên thích hợp của họ. Vì vậy, nếu có những điều sau đây tham gia:

from root 
inner join root.z 
inner join root.z.b 
inner join root.z.a 
inner join root.a on (... and root.z.prop = root.a.prop) 

Trình tự tạo ra sẽ là

from root 
inner join root.a on (... and root.z.prop = root.a.prop) 
inner join root.z 
inner join root.z.a 
inner join root.z.b 

Đổi tên BillEvent.bill-BillEvent.zBill (hoặc bất cứ điều gì theo thứ tự abc sau event) sửa lỗi cú pháp trong truy vấn này. Tuy nhiên, đây không phải là khả năng mở rộng: nếu bạn muốn truy vấn từ phía bên kia của bảng giao diện, truy vấn đó sẽ không thành công vì giờ đây nó bị sắp xếp theo thứ tự bảng chữ cái.

+0

Hãy thử sử dụng "createAlias" thay vì "createCriteria" – richarbernal

+1

@richarbernal Điều đó không có tác dụng. Ít nhất là không sử dụng testcase được cung cấp tại derby: memory-db. – flo

+0

Tôi cho rằng bạn không quan tâm đến giải pháp cho truy vấn cụ thể đó nhưng trong một giải pháp chung? – flo

Trả lời

4

Khi tiêu chí được sử dụng ngủ đông thực sự đi qua cây tổ chức theo chiều sâu Tìm kiếm đầu tiên để xây dựng các kết nối theo định nghĩa trường từ cấu hình. Trong trường hợp của bạn BillEvent bị vượt qua hóa đơn trước, sau đó là trường con của Hóa đơn lớp. Vì vậy, về cơ bản nó tạo ra sự kiện sự kiện tham gia sau khi tạo tất cả các kết nối từ số liên kết hóa đơn. Bạn có thể xác định thứ tự trong hbm.xml nhưng như bạn đã đề cập, nó không phải là rất khả năng mở rộng.

Vì vậy, bạn có ít nhất hai lựa chọn ở đây:

tiêu chí Thay đổi
  1. nên thực thể gốc sẽ khác nhau, sau đó thêm chiếu và gây biến áp để lấy BillEvent thực thể. Ví dụ:

    final Criteria criteria = session.createCriteria(Event.class, "event1") 
           .createCriteria("event1.billEvents", "be1") 
           .createCriteria("be1.bill", "bill1") 
           .createCriteria("bill1.billEvents", "be2") 
           .createCriteria("be2.event", "event2", JoinType.LEFT_OUTER_JOIN, 
             Restrictions.ltProperty("event1.time", "event2.time")) 
           .add(Restrictions.eq("be1.id", billId)) 
           .add(Restrictions.isNull("event2.id")) 
           .setProjection(Projections.projectionList() 
            .add(Projections.property("be1.event"), "event") 
            .add(Projections.property("be1.note"), "note")) 
           .setResultTransformer(Transformers.aliasToBean(BillEvent.class)); 
    
  2. Một tùy chọn khác có thể được sử dụng nĐể HQL thay vì sau đó tiêu chí api. Nó sẽ cho phép bạn kiểm soát thứ tự tham gia trực tiếp từ yêu cầu vì nó sử dụng một cơ chế xây dựng sql khác.
Các vấn đề liên quan