2017-05-24 55 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); 
    } 
} 

什么不是那么好 - 它很可能我只是得到错误代码和串消息例外。但我需要让用户知道重复的字段,以便他们可以相应地进行修改,这是我无法通过此代码实现的。也许一些数据库驱动程序的错误可以给你非常具体的细节,但我认为依靠驱动程序不是一个好方法,因为如果你想在将来改变数据库,它会杀了你。

我的做法

//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具有'update ... on duplicate key ...'。 – Bohemian

回答

0

使用INSERT 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还是nosql数据库。 – 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()确保相同的字符串值在同一个实例上同步。这种方法对吞吐量的影响非常小,但它消除了此方法的所有可能的并发问题

值得注意的事项

  • 确保用户名不是电子邮件方式路过这里之前。
  • 它只有一个可能的锁定顺序,不用担心死锁问题。
  • 如果此类锁也用于其他方法,则必须注意锁定顺序。
  • Tripple检查用于服务器的JVM,确保在GC期间收集实例字符串。
+0

你有*没有*摆脱了所有可能的并发问题。我假设你的'emailExists'和'usernameExists'方法都在对表进行查询。在这两种调用方法之间,另一个线程/进程/应用程序可以插入/修改/删除电子邮件/密码行。这种方法可以做,因为它几乎捕获所有的情况,但是你还必须执行波希米亚语答案中显示的那种原子检查。 –

+0

@BasilBourque同意。但是,在存在检查期间插入相同的电子邮件/用户名是不可能的,因为锁(假设createUser是插入发生的唯一地方)。对于修改,我们可以为所有相关方法做类似的锁定(必须处理锁定顺序)。无论如何,制定数据库限制总是很好的做法。 – toyknight

相关问题