2009-05-26 42 views
23

MySQL hỗ trợ cú pháp "INSERT ... ON DUPLICATE KEY UPDATE ..." cho phép bạn "chèn" một cách mù quáng vào cơ sở dữ liệu và quay trở lại cập nhật bản ghi hiện có nếu có.Hibernate có thể làm việc với cú pháp "ON DUPLICATE KEY UPDATE" của MySQL không?

Điều này hữu ích khi bạn muốn cách ly giao dịch nhanh và các giá trị bạn muốn cập nhật phụ thuộc vào các giá trị đã có trong cơ sở dữ liệu.

Ví dụ: giả sử bạn muốn đếm số lần một câu chuyện được xem trên blog. Một cách để làm điều đó với cú pháp này có thể là:

INSERT INTO story_count (id, view_count) VALUES (12345, 1) 
ON DUPLICATE KEY UPDATE set view_count = view_count + 1 

này sẽ hiệu quả hơn và hiệu quả hơn so với bắt đầu một giao dịch, và xử lý các trường hợp ngoại lệ không thể tránh khỏi xảy ra khi những câu chuyện mới nhấn trang nhất.

Làm cách nào để chúng tôi có thể làm như vậy hoặc hoàn thành cùng một mục tiêu, với Hibernate?

Đầu tiên, trình phân tích cú pháp HQL của Hibernate sẽ ném một ngoại lệ vì nó không hiểu các từ khóa cụ thể cho cơ sở dữ liệu. Trong thực tế, HQL không thích bất kỳ chèn nào rõ ràng trừ khi nó là một "INSERT ... SELECT ....".

Thứ hai, Hibernate giới hạn SQL để chỉ chọn. Hibernate sẽ ném một ngoại lệ nếu bạn cố gắng gọi session.createSQLQuery("sql").executeUpdate().

Thứ ba, Hibernate's saveOrUpdate không phù hợp với hóa đơn trong trường hợp này. Các bài kiểm tra của bạn sẽ vượt qua, nhưng sau đó bạn sẽ bị lỗi sản xuất nếu bạn có nhiều khách truy cập mỗi giây.

Tôi có thực sự phải lật đổ Hibernate không?

Trả lời

22

Bạn đã xem Chú thích Hibernate @SQLInsert chưa?

@Entity 
@Table(name="story_count") 
@SQLInsert(sql="INSERT INTO story_count(id, view_count) VALUES (?, ?) 
ON DUPLICATE KEY UPDATE view_count = view_count + 1") 
public class StoryCount 
+1

Điều gì sẽ xảy ra nếu bạn muốn sử dụng trường id tăng tự động (nói trong mysql)? Tôi đang nhận được lỗi lạ về cơ sở dữ liệu không trả lại một ID –

2

Đây là câu hỏi cũ, nhưng tôi gặp sự cố tương tự và tôi đã hình dung được chủ đề này. Tôi cần thêm một bản ghi vào một bộ ghi nhật ký kiểm tra StatelessSession hiện có. Việc triển khai hiện tại đã sử dụng StatelessSession vì hành vi lưu trữ bộ đệm của phiên triển khai phiên chuẩn là không cần thiết trên không chúng tôi không muốn người nghe ngủ đông của chúng tôi kích hoạt tính năng ghi nhật ký kiểm tra. Triển khai này là về việc đạt được hiệu suất ghi càng cao càng tốt mà không có tương tác.

Tuy nhiên, loại nhật ký mới cần sử dụng loại hành vi chèn khác, nơi chúng tôi dự định cập nhật các mục nhật ký hiện có với thời gian giao dịch là loại hành vi "gắn cờ". Trong một StatelessSession, saveOrUpdate() không được cung cấp vì vậy chúng tôi cần thiết để thực hiện chèn-else-update theo cách thủ công.

Trong ánh sáng của những yêu cầu:

Bạn thể sử dụng mysql "chèn ... vào cập nhật bản sao chìa khóa" hành vi thông qua một tùy chỉnh sql-chèn cho đối tượng dai dẳng hibernate. Bạn có thể xác định các khoản tùy chỉnh sql-chèn hoặc thông qua chú thích (như trong câu trả lời ở trên) hoặc thông qua một tổ chức sql-chèn một ánh xạ xml ngủ đông, ví dụ .:

<class name="SearchAuditLog" table="search_audit_log" persister="com.marin.msdb.vo.SearchAuditLog$UpsertEntityPersister"> 

    <composite-id name="LogKey" class="SearchAuditLog$LogKey"> 
     <key-property 
      name="clientId" 
      column="client_id" 
      type="long" 
     /> 
     <key-property 
      name="objectType" 
      column="object_type" 
      type="int" 
     /> 
     <key-property 
      name="objectId" 
      column="object_id" 
     /> 
    </composite-id> 
    <property 
     name="transactionTime" 
     column="transaction_time" 
     type="timestamp" 
     not-null="true" 
    /> 
    <!-- the ordering of the properties is intentional and explicit in the upsert sql below --> 
    <sql-insert><![CDATA[ 
     insert into search_audit_log (transaction_time, client_id, object_type, object_id) 
     values (?,?,?,?) ON DUPLICATE KEY UPDATE transaction_time=now() 
     ]]> 
    </sql-insert> 

Những poster ban đầu hỏi về MySQL đặc biệt. Khi tôi thực hiện hành vi chèn-else-update với mysql, tôi đã nhận được ngoại lệ khi 'đường dẫn cập nhật' của sql được triển khai.Cụ thể, mysql đã báo cáo 2 hàng đã được thay đổi khi chỉ có 1 hàng được cập nhật (bề ngoài vì hàng hiện tại bị xóa và hàng mới được chèn vào). Xem this issue để biết thêm chi tiết về tính năng cụ thể đó.

Vì vậy, khi cập nhật trả về 2x số hàng bị ảnh hưởng đến ngủ đông, hibernate đã ném một BatchedTooManyRowsAffectedException, sẽ quay trở lại giao dịch và đẩy lùi ngoại lệ. Ngay cả khi bạn đã nắm bắt được ngoại lệ và xử lý nó, giao dịch đã được khôi phục vào thời điểm đó.

Sau một số lần đào, tôi thấy rằng đây là vấn đề với thực thể mà hibernate đang sử dụng. Trong trường hợp của tôi hibernate đã được sử dụng SingleTableEntityPersister, trong đó xác định một kỳ vọng rằng số lượng hàng cập nhật phải phù hợp với số hàng được xác định trong hoạt động hàng loạt.

Tinh chỉnh cuối cùng cần thiết để hành vi này hoạt động là xác định persister tùy chỉnh (như được hiển thị trong bản đồ xml ở trên). Trong trường hợp này, tất cả những gì chúng ta phải làm là mở rộng SingleTableEntityPersister và 'override' chèn Expectation. Ví dụ. Tôi chỉ tacked lớp tĩnh này vào đối tượng kiên trì và xác định nó như là persister tùy chỉnh trong các bản đồ ngủ đông:

public static class UpsertEntityPersister extends SingleTableEntityPersister { 

    public UpsertEntityPersister(PersistentClass arg0, EntityRegionAccessStrategy arg1, SessionFactoryImplementor arg2, Mapping arg3) throws HibernateException { 
     super(arg0, arg1, arg2, arg3); 
     this.insertResultCheckStyles[0] = ExecuteUpdateResultCheckStyle.NONE; 
    } 

} 

Phải mất nhiều thời gian đào qua mã hibernate để tìm thấy điều này - tôi đã không thể tìm thấy bất kỳ các chủ đề trên mạng với giải pháp cho điều này.

+3

Bạn không thể hoàn thành điều tương tự bằng cách chỉ định thuộc tính "kiểm tra"? Xem http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/querysql.html#example-custom-crdu-via-xml Ví dụ: ' ... 'hoặc' @SQLInsert (sql = "...", check = "none") ' – Shannon

+0

Chỉnh sửa ở trên, chú thích sẽ giống như sau:' @SQLInsert (sql = "...", check = ResultCheckStyle .NONE) ' – Shannon

1

Nếu bạn đang sử dụng Grails, tôi thấy giải pháp này mà không đòi hỏi phải di chuyển lớp miền của bạn vào thế giới JAVA và sử dụng @SQLInsert chú thích:

  1. Tạo một tùy chỉnh Hibernate Cấu hình
  2. Override các PersistentClass Bản đồ
  3. Thêm INSERT sql tùy chỉnh của bạn vào các Lớp liên tục mà bạn muốn sử dụng KEY DUPLICATE KEY.

Ví dụ, nếu bạn có một đối tượng tên miền được gọi là Người và bạn muốn chèn được INSERT ON DUPLICATE KEY CẬP NHẬT bạn sẽ tạo ra một cấu hình như vậy:

public class MyCustomConfiguration extends GrailsAnnotationConfiguration { 

    public MyCustomConfiguration() { 
     super(); 

     classes = new HashMap<String, PersistentClass>() { 
      @Override 
      public PersistentClass put(String key, PersistentClass value) { 
       if (Person.class.getName().equalsIgnoreCase(key)) { 
        value.setCustomSQLInsert("insert into person (version, created_by_id, date_created, last_updated, name) values (?, ?, ?, ?, ?) on duplicate key update id=LAST_INSERT_ID(id)", true, ExecuteUpdateResultCheckStyle.COUNT); 
       } 
       return super.put(key, value); 
      } 
     }; 
    } 

và thêm này như Hibernate của bạn cấu hình trong DataSource.groovy:

dataSource { 
    pooled = true 
    driverClassName = "com.mysql.jdbc.Driver" 
    configClass = 'MyCustomConfiguration' 
} 

Chỉ cần một lưu ý cẩn thận sử dụng LAST_INSERT_ID, vì điều này sẽ KHÔNG được thiết lập một cách chính xác nếu UPDATE được thực thi thay vì INSERT trừ khi bạn đặt nó một cách rõ ràng trong stat ement, ví dụ: id = LAST_INSERT_ID (id). Tôi đã không kiểm tra GORM lấy ID từ đâu, nhưng tôi giả sử ở đâu đó đang sử dụng LAST_INSERT_ID.

Hy vọng điều này sẽ hữu ích.

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