2010-04-09 23 views
6

Tôi có một số mã để cập nhật một bảng cơ sở dữ liệu trông giống nhưCó một destructor có những hành động khác nhau tùy thuộc vào việc một ngoại lệ xảy ra

try 
{ 
    db.execute("BEGIN"); 
    // Lots of DELETE and INSERT  
    db.execute("COMMIT"); 
} 
catch (DBException&) 
{ 
    db.execute("ROLLBACK"); 
} 

Tôi muốn quấn logic giao dịch trong một lớp RAII vì vậy tôi có thể chỉ cần viết

{ 
    DBTransaction trans(db); 
    // Lots of DELETE and INSERT 
} 

nhưng làm cách nào để viết hàm hủy cho nó?

Trả lời

12

Sử dụng sau:

transaction tr(db); 
... 
tr.commit(); 

Khi tr.commit() hoàn thành nó đặt nhà nước để "cam kết thực hiện" và destructor không làm gì, nếu không nó sẽ quay trở lại.

Kiểm tra ngoại lệ là ý tưởng tồi, hãy xem xét:

transaction tr(db); 
... 
if(something_wrong) 
    return; // Not throw 
... 
tr.commit(); 

Trong trường hợp này bạn có thể mong đợi thay rollback sau đó cam kết nhưng cam kết sẽ được thực hiện.

Edit: nhưng nếu bạn vẫn muốn nó xấu, hãy xem trên std::uncaught_exception() nhưng đọc đầu tiên http://www.gotw.ca/gotw/047.htm

+1

+1 Đó là cách thực hiện những việc như vậy. Có một vấn đề mặc dù: nếu bạn quên gọi cam kết()? – sharptooth

+2

Và nếu bạn quên tạo biến giao dịch thì sao? Bạn không thể ngăn chặn tất cả các sai lầm. –

+2

@sharptooth: nếu bạn quên thực hiện thay đổi mà bạn muốn cam kết ngay từ đầu thì sao? Tôi không nghĩ bạn có thể làm gì để bảo vệ chống lại sự bất lực. – jalf

1

Cách dễ nhất tôi có thể nghĩ là đặt biến thành viên riêng trong lớp trong trường hợp ngoại lệ và thử nghiệm/thực hiện hành động thích hợp trong trình phá hủy.

3

Bạn có thể sử dụng logic sau đây:

  1. Thêm một commit_done giá trị boolean khởi tạo sai đến lớp giao dịch của bạn.
  2. Trong hàm tạo của bạn, "bắt đầu" giao dịch.
  3. Thêm phương thức để "cam kết" giao dịch và cập nhật cam kết tương ứng.
  4. Trong destructor của bạn, gọi là "rollback" chỉ khi commit_done vẫn là sai
2

này Bằng cách loại bỏ các xử lý ngoại lệ, bạn đang làm tê liệt RAII của bạn.

Mã nên được

try 
{ 
    DBTransaction trans(db) ; 

    // Lots of DELETE and INSERT 
    // should one fail, a DBTransactionRollback exception will be thrown 

    trans.commit() ; 
} 
catch(const DBTransactionRollback & e) 
{ 
    // If really needed, you could extract failure information from "e" 
} 

Sự khác biệt với mã ban đầu của bạn là những gì thúc đẩy câu trả lời của tôi:

  1. Không có gì cần thiết trong "bắt" là: Destructor sẽ đảm nhận một tự động rollback trừ khi phương thức commit() được gọi thành công (, ví dụ, có thể thiết lập một số thành viên boolean riêng của DBTransaction thành true). Việc nắm bắt là nơi mã sẽ tiếp tục, giả sử giao dịch không thành công.

  2. Bạn nên tạo ngoại lệ chuyên dụng (Tôi đặt tên nó là DBTransactionRollback) để ném khoảnh khắc xảy ra lỗi trong một trong các lệnh của bạn. Do đó, hoạt động đánh bắt sẽ chỉ bắt ngoại lệ có động cơ quay ngược lại và không ngoại lệ khác (như STL, v.v.)

  3. Việc sử dụng cơ chế ngoại lệ cho phép bạn đặt mã của mình bằng nhiều chức năng, được gọi từ lần thử này/bắt khối mã, mà không cần phải đối phó với trả về boolean và trả về mã lỗi khác.

Hy vọng điều này sẽ trả lời câu hỏi của bạn.

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