2010-10-19 126 views
43

在執行flush操作之前,有沒有簡單的方法在Doctrine 2中檢查重複密鑰?使用Doctrine 2檢查重複密鑰

+1

我真的不知道答案,但我不知道如何刷新之前,檢查是不是做沖洗和處理錯誤(假設重複鍵存在)不同。 – 2010-11-05 02:57:14

+0

在刷新時將會拋出數據庫特定的異常。 – tom 2010-11-05 08:12:50

+3

這裏介紹的大多數解決方案都沒有考慮到事實上你只是*不能*預先檢查重複項,因爲這不是一個原子操作,因此,如果其他線程插入,你仍然可以*重複值進入表格,例如。因此,我腦海中唯一可能的解決方案是手動處理失敗或使用鎖定。前者在教條上相當醜陋(因爲新興市場被關閉),如果你不小心,後者可能會有性能明智的後果。我想親自看到一個很好的答案。 – 2013-09-02 08:55:35

回答

32

您可以趕上UniqueConstraintViolationException這樣:

use Doctrine\DBAL\Exception\UniqueConstraintViolationException; 

// ... 

try { 
    // ... 
    $em->flush(); 
} 
catch (UniqueConstraintViolationException $e) { 
    // .... 
} 
+0

這已於2014年添加。這應該是現在如何做到這一點。 – tom 2016-03-04 07:48:07

+0

自從Doctrine DBAL 2.5 - UniqueConstraintViolationException從ConstraintViolationException繼承後,此功​​能已可用:https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Exception/ConstraintViolationException。php#L27 – 2016-03-16 17:35:25

+0

對於當前版本,請改爲:\ Doctrine \ DBAL \ Exception \ UniqueConstraintViolationException – nicolallias 2016-06-10 16:37:30

4

我前段時間也遇到過這個問題。主要問題不是數據庫特定的異常,但是當拋出PDOException時,EntityManager關閉。這意味着你不能確定你想要刷新的數據會發生什麼。但可能它不會保存在數據庫中,因爲我認爲這是在一個事務中完成的。

所以當我想到這個問題的時候,我想出了這個解決方案,但是我沒有時間寫下它。

  1. 它可以使用event listeners,特別是onFlush事件來完成。在數據發送到數據庫之前調用此事件(在計算變更集後 - 因此您已知道哪些實體已更改)。
  2. 在這個事件監聽器中,您將不得不瀏覽其所有鍵的變化實體(對於主鍵,它將查找@Id的類元數據)。
  3. 然後,您將不得不使用您的密鑰標準的查找方法。 如果您會找到結果,那麼您有機會拋出自己的異常,這將不會關閉EntityManager,並且您可以在模型中捕獲它並在再次嘗試刷新之前對數據進行一些更正。

該解決方案的問題在於它可能會對數據庫產生相當多的查詢,所以需要相當多的優化。如果您只想在很少的地方使用這種東西,我建議您在可能出現重複的地方進行檢查。因此,例如,你想創建一個實體並將其保存:

$user = new User('login'); 
$presentUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('login' => 'login')); 
if (count($presentUsers)>0) { 
    // this login is already taken (throw exception) 
} 
+0

這也不是併發安全的。如果你實現它,你仍然可以在flush上得到重複的異常。 – 2014-12-30 09:06:24

18

我使用這個策略之後的flush()檢查唯一約束,可能不是你想要的,但可能會幫助別人。


當你調用的flush(),如果是唯一約束失敗,PDOException與代碼拋出。

try { 
    // ... 
    $em->flush(); 
} 
catch(\PDOException $e) 
{ 
    if($e->getCode() === '23000') 
    { 
     echo $e->getMessage(); 

     // Will output an SQLSTATE[23000] message, similar to: 
     // Integrity constraint violation: 1062 Duplicate entry 'x' 
     // ... for key 'UNIQ_BB4A8E30E7927C74' 
    } 

    else throw $e; 
} 

如果你需要得到失效列名稱:

創建前綴名,如表索引。 「unique_」

* @Entity 
* @Table(name="table_name", 
*  uniqueConstraints={ 
*   @UniqueConstraint(name="unique_name",columns={"name"}), 
*   @UniqueConstraint(name="unique_email",columns={"email"}) 
*  }) 

不指定您的列在@Column定義

這似乎與一個隨機覆蓋索引名唯一...

**ie.** Do not have 'unique=true' in your @Column definition 

後重新生成表(您可能需要刪除它&重建),你應該能夠從異常消息中提取列名。

// ... 
if($e->getCode() === '23000') 
{ 
    if(\preg_match("%key 'unique_(?P<key>.+)'%", $e->getMessage(), $match)) 
    { 
     echo 'Unique constraint failed for key "' . $match[ 'key' ] . '"'; 
    } 

    else throw $e; 
} 

else throw $e; 

並不完美,但它的工作原理...

+3

我猜那個教條很久以前就改變了異常處理。在這種情況下,我在一個\ Doctrine \ DBAL \ DBALException中得到一個PDOException。上面的代碼會像catch(\ Doctrine \ DBAL \ DBALException $ e){if($ e-> getPrevious() - > getCode()==='23000'){/ * do stuff * /}}。注意捕獲這個異常是處理高併發情​​況的唯一方法。一個選擇查詢驗證它只是不夠的 – 2013-09-11 14:03:20

0

我想補充該特別關於PDOExceptions--

23000錯誤代碼是一個家庭的毯子代碼MySQL可以返回的完整性約束違規。

因此,處理23000錯誤代碼對於某些用例來說不夠具體。

例如,您可能希望對重複記錄衝突的反應不同於對缺少的外鍵衝突的反應。

這裏是如何應對這樣一個例子:

try { 
    $pdo -> executeDoomedToFailQuery(); 
} catch(\PDOException $e) { 
    // log the actual exception here 
    $code = PDOCode::get($e); 
    // Decide what to do next based on meaningful MySQL code 
} 

// ... The PDOCode::get function 

public static function get(\PDOException $e) { 
    $message = $e -> getMessage(); 
    $matches = array(); 
    $code = preg_match('/ (\d\d\d\d)/', $message, $matches); 
    return $code; 
} 

我知道這是不是儘可能詳細,這個問題是問,但我覺得這是在許多情況下是非常有用的,而不是Doctrine2具體。

0

最簡單的方法應該是這樣的:

$product = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name'])); 
if(!empty($product)){ 
// duplicate 
} 
+0

這是不是很安全的高併發環境,如檢查用戶名是否已經在流行的網站註冊。 – Andrew 2013-12-02 05:31:28

0

我用這個和它似乎工作。它返回特定的MySQL錯誤號 - 即1062重複條目 - 準備好讓你處理你喜歡的方式。

try 
{ 
    $em->flush(); 
} 
catch(\PDOException $e) 
{ 
    $code = $e->errorInfo[1]; 
    // Do stuff with error code 
    echo $code; 
} 

我與其他幾個場景測試,這一點,它會返回其他代碼太像1146(表不存在)和1054(未知列)。

2

如果你只是想捕獲重複的錯誤。你不應該只是檢查代碼

$e->getCode() === '23000' 

因爲這將捕獲其他錯誤,如字段'用戶'不能爲空。我的解決辦法是檢查錯誤信息,如果它包含文本 '重複條目'

   try { 
        $em->flush(); 
       } catch (\Doctrine\DBAL\DBALException $e) { 

        if (is_int(strpos($e->getPrevious()->getMessage(), 'Duplicate entry'))) { 
         $error = 'The name of the site must be a unique name!'; 
        } else { 
         //.... 
        } 
       } 
2

在Symfony的2,它實際上拋出一個\例外,而不是一個\ PDOException

try { 
    // ... 
    $em->flush(); 
} 
catch(\Exception $e) 
{ 
    echo $e->getMessage(); 
    echo $e->getCode(); //shows '0' 
    ### handle ### 

} 

$ E- >的getMessage()回聲類似以下內容:

在執行 '(?,?,?,)INSERT INTO(...)VALUES' 使用參數發生異常[...]:

SQLSTATE [23000]:誠信違反約束條件:1062重複項'...'鍵'PRIMARY'

3

如果您使用的是Symfony2,則可以使用UniqueEntity(…)form->isValid()在flush()之前捕獲重複項。

我在圍欄在這裏張貼這個答案,但它似乎有價值的,因爲學說用戶的很多還將使用Symfony2的。要清楚的是:這使用Symfony的驗證類,該類使用實體存儲庫進行檢查(可配置,但默認爲findBy)。

在你的實體,您可以添加註釋:

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 

/** 
* @UniqueEntity("email") 
*/ 
class YourEntity { 

然後在你的控制器,交給請求您可以檢查您的驗證後的形式。

$form->handleRequest($request); 

if (! $form->isValid()) 
{ 
    if ($email_errors = $form['email']->getErrors()) 
    { 
     foreach($email_errors as $error) { 
      // all validation errors related to email 
     } 
    } 
… 

我建議這與彼得的回答相結合,因爲你的數據庫架構應該強制唯一性太:

/** 
* @UniqueEntity('email') 
* @Orm\Entity() 
* @Orm\Table(name="table_name", 
*  uniqueConstraints={ 
*   @UniqueConstraint(name="unique_email",columns={"email"}) 
* }) 
*/ 
7

如果運行前插入一個SELECT查詢是不是你想要的,你可以只運行flush()並捕獲異常。

教義DBAL 2.3,你可以放心地通過看PDO異常錯誤代碼(23000爲MySQL,23050 Postgres的)理解的唯一約束錯誤,由學說DBALException包裹:

 try { 
      $em->flush($user); 
     } catch (\Doctrine\DBAL\DBALException $e) { 
      if ($e->getPrevious() && 0 === strpos($e->getPrevious()->getCode(), '23')) { 
       throw new YourCustomException(); 
      } 
     } 
+0

實際上代碼23000不是特定於唯一約束錯誤。取自[MySQL doc](https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html)的示例: 錯誤:1048 SQLSTATE:23000(ER_BAD_NULL_ERROR) 消息:列'%s'不能爲空 – Emilie 2017-08-03 18:03:03

+0

@emile是否會返回代碼爲23000的異常,或者它只是一個內部MySQL數字? – 2017-09-27 10:21:27