Laravel: Sử dụng try… catch với DB :: transaction ()


81

Tất cả chúng ta đều sử dụng DB::transaction()cho nhiều truy vấn chèn. Khi làm như vậy, nên try...catchđặt bên trong nó hay gói nó? Thậm chí có cần thiết phải bao gồm try...catchthời điểm một giao dịch sẽ tự động thất bại nếu có sự cố xảy ra không?

Mẫu try...catchgói một giao dịch:

// try...catch
try {
    // Transaction
    $exception = DB::transaction(function() {

        // Do your SQL here

    });

    if(is_null($exception)) {
        return true;
    } else {
        throw new Exception;
    }

}
catch(Exception $e) {
    return false;
}

Ngược lại, một DB::transaction()gói một thử ... bắt:

// Transaction
$exception = DB::transaction(function() {
    // try...catch
    try {

        // Do your SQL here

    }
    catch(Exception $e) {
        return $e;
    }

});

return is_null($exception) ? true : false;

Hoặc đơn giản là một giao dịch có thử ... bắt

// Transaction only
$exception = DB::transaction(function() {

    // Do your SQL here

});

return is_null($exception) ? true : false;

Câu trả lời:


179

Trong trường hợp bạn cần 'thoát' một giao dịch thông qua mã theo cách thủ công (có thể là thông qua một ngoại lệ hoặc chỉ đơn giản là kiểm tra trạng thái lỗi), bạn không nên sử dụng DB::transaction()mà thay vào đó hãy bọc mã của mình trong DB::beginTransactionDB::commit/ DB::rollback():

DB::beginTransaction();

try {
    DB::insert(...);
    DB::insert(...);
    DB::insert(...);

    DB::commit();
    // all good
} catch (\Exception $e) {
    DB::rollback();
    // something went wrong
}

Xem tài liệu giao dịch .


Sau khi xem lại lần nữa, đây là câu trả lời tôi đang tìm kiếm. :)
mê mẩn

@alexrussell - Cơ sở dữ liệu không tạo ra khác \Exception? Tôi có thể nắm bắt nó với cái chung này \Exception? Tuyệt vời nếu là nó!
Artur Mamedov

Sự khác biệt giữa DB::beginTransaction()và là DB:transaction()gì?
Hamed Kamrava

2
Câu hỏi đơn giản: Điều gì xảy ra nếu bạn không khôi phục sau ngoại lệ hoặc nếu bạn không bắt được ngoại lệ? Tự động khôi phục sau khi kết thúc tập lệnh?
neoteknic

2
@HengSopheak câu hỏi này là về cơ sở dữ liệu Laravel 4 nên rất có thể câu trả lời của tôi không còn đúng cho 5.3. Có thể bạn nên đặt một câu hỏi mới với thẻ Laravel 5.3 để nhận được sự hỗ trợ phù hợp của cộng đồng.
alexrussell

24

Nếu bạn sử dụng PHP7, sử dụng Throwable trong catchđể bắt ngoại lệ người sử dụng và các lỗi gây tử vong.

Ví dụ:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}

Nếu mã của bạn phải tương thích với PHP5, hãy sử dụng ExceptionThrowable:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Exception $e) {
    DB::rollback();
    throw $e;
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}

Còn thực tế là DB :: beginTransaction () cũng có thể ném \ Exception? Nó có nên được đưa vào thử / bắt không?
Michael Pawlowsky

4
Nếu giao dịch chưa được bắt đầu, chúng tôi không cần khôi phục bất kỳ thứ gì. Hơn nữa, sẽ không tốt nếu thử khôi phục giao dịch chưa bắt đầu trong catchkhối. Vì vậy, nơi tốt cho DB::beginTransaction()là trước trykhối.
mnv

11

Bạn có thể gói giao dịch bằng try..catch hoặc thậm chí đảo ngược chúng, đây là mã ví dụ của tôi mà tôi đã sử dụng trong laravel 5, nếu bạn nhìn sâu vào bên DB:transaction()trong Illuminate\Database\Connectionnó giống như bạn viết giao dịch thủ công.

Giao dịch Laravel

public function transaction(Closure $callback)
    {
        $this->beginTransaction();

        try {
            $result = $callback($this);

            $this->commit();
        }

        catch (Exception $e) {
            $this->rollBack();

            throw $e;
        } catch (Throwable $e) {
            $this->rollBack();

            throw $e;
        }

        return $result;
    }

vì vậy bạn có thể viết mã của mình như thế này và xử lý ngoại lệ của bạn như ném thông báo trở lại biểu mẫu của bạn qua flash hoặc chuyển hướng đến một trang khác. HÃY NHỚ trả lại bên trong sự đóng cửa được trả về trong giao dịch (), vì vậy nếu bạn trả lại, redirect()->back()nó sẽ không chuyển hướng ngay lập tức, vì nó trả về tại biến xử lý giao dịch.

Kết thúc giao dịch

$result = DB::transaction(function () use ($request, $message) {
   try{

      // execute query 1
      // execute query 2
      // ..

      return redirect(route('account.article'));

   } catch (\Exception $e) {
       return redirect()->back()->withErrors(['error' => $e->getMessage()]);
    }
 });

// redirect the page
return $result;

thì giải pháp thay thế là ném biến boolean và xử lý chuyển hướng bên ngoài chức năng giao dịch hoặc nếu bạn cần truy xuất lý do tại sao giao dịch không thành công, bạn có thể lấy nó từ $e->getMessage()bên trongcatch(Exception $e){...}


Tôi đã từng giao dịch mà không cần khối try-catch và Nó làm việc tốt quá
hamidreza samsami

@hamidrezasamsami vâng, cơ sở dữ liệu tự động khôi phục, nhưng đôi khi bạn cần biết là tất cả các truy vấn có thành công hay không ..
Angga Ari Wijaya

6
Ví dụ về "Giao dịch kết thúc" là sai. Điều này sẽ luôn cam kết, ngay cả khi một trong các truy vấn không thành công vì tất cả các ngoại lệ đều bị bắt trong lệnh gọi lại giao dịch. Bạn muốn đặt thử / bắt bên ngoài giao dịch DB ::.
redmallard

2

Tôi đã quyết định đưa ra câu trả lời cho câu hỏi này vì tôi nghĩ rằng nó có thể được giải quyết bằng một cú pháp đơn giản hơn so với khối try-catch phức tạp. Tài liệu Laravel khá ngắn gọn về chủ đề này.

Thay vì sử dụng try-catch, bạn chỉ có thể sử dụng DB::transaction(){...}wrapper như sau:

// MyController.php
public function store(Request $request) {
    return DB::transaction(function() use ($request) {
        $user = User::create([
            'username' => $request->post('username')
        ]);

        // Add some sort of "log" record for the sake of transaction:
        $log = Log::create([
            'message' => 'User Foobar created'
        ]);

        // Lets add some custom validation that will prohibit the transaction:
        if($user->id > 1) {
            throw AnyException('Please rollback this transaction');
        }

        return response()->json(['message' => 'User saved!']);
    });
};

Sau đó, bạn sẽ thấy rằng Người dùng và bản ghi Nhật ký không thể tồn tại nếu không có sự khác biệt.

Một số lưu ý khi triển khai trên:

  • Đảm bảo returngiao dịch, để bạn có thể sử dụng số tiền response()bạn trả lại trong lần gọi lại của nó.
  • Đảm bảo throwmột ngoại lệ nếu bạn muốn giao dịch được khôi phục (hoặc có một hàm lồng nhau tự động ném ngoại lệ cho bạn, chẳng hạn như ngoại lệ SQL từ bên trong Eloquent).
  • Các id, updated_at, created_atvà các lĩnh vực bất kỳ khác SẴN TẠO SAU cho các $userđối tượng (trong suốt thời gian thực hiện giao dịch này). Giao dịch sẽ chạy qua bất kỳ logic tạo nào mà bạn có. TUY NHIÊN, toàn bộ bản ghi sẽ bị loại bỏ khi AnyExceptionném. Điều này có nghĩa là ví dụ, một cột tự động tăng lên đối với idcác giao dịch không thành công sẽ tăng lên.

Đã thử nghiệm trên Laravel 5.8


Thật điên rồ khi không ai đề cập đến cách tiếp cận rõ ràng này
Adam
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.