2017-05-24 5 views
0

編集:私は私が私のために話すためのコードが必要だと思う...挿入の前にデータベースの一意性チェック?

私の問題についての簡単な例。

データベース内のユーザーを作成すると、電子メールとユーザー名は一意です。一意性チェックが合格しない場合は、エラーの原因となっているフィールド/列/を正確に知る必要があります。私が間違って何

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException 
{ 
    //TODO: Create the user or throw exception according to error. 
} 

悪いアプローチ

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException 
{ 
    //Do some query first. 
    if (emailExists(email)) throw EmailExistingException; 
    if (usernameExists(username)) throw UsernameExistingException; 

    //TODO: Create the user. 
}  

を実装したい何

- 挿入前に典型的なチェック、受け入れられるつもりはありません。あまりよくない何

それほど悪くアプローチ

//During database creation 
db.users.setUnique("email", true); 
db.users.setUnique("username", true); 

void createUser(String email, String username, String password) 
throws UserCreationException 
{ 
    try { 
     //TODO: Create the user. 
    } catch (SomeSortOfDatabaseException e) { 
     throw UserCreationException("some message", e); 
    } 
} 

- それは私がちょうどエラーコードと文字列メッセージと例外を取得する可能性があります。しかし、私は、ユーザが複写されたフィールドを知ってそれに応じてそれを修正できるようにする必要があります。私はこのコードではできません。たぶんDBドライバのエラーによって非常に具体的な詳細が得られるかもしれませんが、将来的にデータベースを変更したい場合は、ドライバに頼るのは良い方法ではないと思います。

私のアプローチ

//During database creation 
db.users.setUnique("email", true); 
db.users.setUnique("username", true); 

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException 
{ 
    try { 
     //TODO: Create the user. 
    } catch (SomeSortOfDatabaseException e) { 
     if (emailExists(email)) throw EmailExistingException; 
     if (usernameExists(username)) throw UsernameExistingException; 

     throw ServerException("some message", e); 
    } 
} 

それは実際に良くなっていますが、私は多くの(現実の世界では、これらのユニークなフィールドの多くは、あなたは私が言っているものを知って、変更することができる)というのが私の現在のアプローチは好きではありません。ですから、実際の生産でこの問題の一般的なアプローチは何か不思議です。すべての意見をいただければ幸いです。

+2

どのデータベースですか?これをネイティブにサポートするデータベースもあります。 – Bohemian

+0

'一部のデータベースがこれをネイティブにサポートしています。'私はそれほど悪い方法ではないと言いました。とにかく、どのデータベースがそれをサポートしているのか、それがどのように行われているのか、もちろんスレッドの安全性について聞きたいです。 – toyknight

+0

mysqlには、例えば、重複キーの更新...があります。 – Bohemian

答えて

0

使用アトミックチェックに内蔵されたSQLを挿入します。

insert into mytable (col1, col2, ...) 
select * 
from (select 'foo', 'bar', ... from dual) x -- postgress, mysql, etc doesn't need "from dual" 
left join mytable 
    on col1 = 'foo' 
    and col2 = 'bar' 
    ... 
where col1 is null -- only selects something when there's no match 
+0

ご返信ありがとうございますが、私は実際にはより一般的なアプローチを探しています。それに関係なく、SQLデータベースまたはNISQLデータベースです。 – toyknight

+1

@toyknightこれ以上の「一般的なアプローチ」はなく、専門化が必要です。特定の(SQL)データベース実装内の特定の実装の「安全性」さえ問題です。この問題の問題は、「任意の」データベースタイプに拡張する場合にのみ複合化されます。 – user2864740

+0

@toyknightこれ以上は一般的ではありません。これはすべてのデータベースで機能し、アトミックです。マルチスレッド環境(リレーショナルデータベースコントラクトによって保証されている)で心配する競合条件はありません。 – Bohemian

0

ただのJavaと解決策を見つけました。

//During database creation, make restrictions. 

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException 
{ 
    String email_intern = email.intern(); 
    String username_intern = username.intern(); 
    synchronized(email_intern) { 
     synchronized(username_intern) { 
      //Do some query first. 
      if (emailExists(email)) throw EmailExistingException; 
      if (usernameExists(username)) throw UsernameExistingException; 

      //TODO: Create the user. 
     } 
    } 
} 

かなり有望です。 String.intern()は、同じ文字列値が同じインスタンスで同期することを保証します。この方法は、この方法の可能なすべての並行性の問題を取り除きながら、スループットに与える影響はごくわずかです。

注目すべきもの

  • は、ユーザ名がここに渡す前に、電子メールのパターンになっていないことを確認してください。
  • ロックの順序は1つしかありません。デッドロックの問題はありません。
  • このようなロックが他の方法でも使用されている場合は、ロック順に注意する必要があります。
  • サーバに使用されているJVMをチェックして、GC中に内部文字列が収集されていることを確認してください。
+0

可能なすべての並行性の問題を取り除いたことはありません。あなたの 'emailExists'と' usernameExists'メソッドが両方ともテーブルに問い合わせを行っていると仮定します。呼び出しを行う2つのメソッド間で、別のスレッド/プロセス/アプリケーションが電子メール/パスワード行を挿入/変更/削除する可能性があります。このアプローチは大丈夫です。ほぼすべてのケースをキャッチしますが、ボヘミアン氏の回答に示されている原子検査の種類も実装する必要があります。 –

+0

@BasilBourque合意。しかし、存在チェック中に同じ電子メール/ユーザー名を挿入するのは、ロックのため不可能です(createUserが挿入が行われる唯一の場所であると仮定します)。変更のために、関連するすべてのメソッドについて同様のロックを行うことができます(ロック順を処理する必要があります)。とにかくデータベースの制限を行うことは常に良い方法です。 – toyknight

関連する問題