插入方法之前的数据库检查唯一性?

时间:2017-05-24 22:38:45

标签: database concurrency

编辑:我想我需要代码代替我...

关于我的问题的一个简单例子。

在数据库中创建用户,电子邮件和用户名是唯一的。如果未通过唯一性检查,我需要确切地知道哪个字段/列/导致错误的任何内容。

我想实施的内容

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 个答案:

答案 0 :(得分: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

答案 1 :(得分: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期间收集实习字符串。