有一个用户表,字段分别是, id, username, password ,分别记录用户的ID(主键),用户名,和密码。我的业务逻辑中,要求用户名字段,绝对不能有重复。 否则整个系统就完全乱套了。
我之前的办法是,控制层中,在插入新用户之前,先判断一下这个用户名,是否已经存在?如果已经存在,就报错提示给用户,如果不存在,则执行插入操作。伪代码如下:事务开始
if(xxxDao.isUsernameExist("love")){
return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
/** 使用的SQL语句为:
select * from user where username = 'love'
*/
}else{
xxxDao.insertUser(user);
return "用户注册成功,请返回登录页面登录!";
/** 使用的SQL语句为:
INSERT INTO user(id,username,password) VALUES('xxxx','love','xxxxxxx')
*/
}
事务结束一开始我自己测试,这段程序运行的灰常完美,什么问题也没有!
但是后来,问题,无情的来了当有多个人,比如100个人,并发访问的时候,就出现问题了:这100个同志,同时注册 "love" 这个用户名,那么他们首先执行 if(xxxDao.isUsernameExist("love")) 这条语句,来判断user表里,是不是存在 love 这个用户名,那么他们同时得到的结果,是不存在,可以注册这个用户名,然后紧接着开始执行 INSERT INTO...操作了。 后果就是我的user表里,出现了100个用户名是 love的记录。
这一下就完蛋了。。 那么我想请教一下各位大神,如果我把以上代码稍微做下修改,修改成下面这样:事务开始
if(xxxDao.isUsernameExist("love")){
return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
/** 使用的SQL语句为:
select * from user where username = 'love'
*/
}else{
xxxDao.insertUser(user);
return "用户注册成功,请返回登录页面登录!";
/** 使用的SQL语句为:
INSERT INTO user(id,username,password) SELECT 'xxxx','love','xxxxxxx' FROM DUAL WHERE NOT EXISTS ( SELECT * FROM user WHERE username = 'love' )
*/
}
事务结束
这样的话,还会不会存在以上那个并发问题?求大神指点我,尽量的能不用锁就不去用锁,能不去修改事务隔离级别,就尽量不修改,除非实在没办法了
我之前的办法是,控制层中,在插入新用户之前,先判断一下这个用户名,是否已经存在?如果已经存在,就报错提示给用户,如果不存在,则执行插入操作。伪代码如下:事务开始
if(xxxDao.isUsernameExist("love")){
return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
/** 使用的SQL语句为:
select * from user where username = 'love'
*/
}else{
xxxDao.insertUser(user);
return "用户注册成功,请返回登录页面登录!";
/** 使用的SQL语句为:
INSERT INTO user(id,username,password) VALUES('xxxx','love','xxxxxxx')
*/
}
事务结束一开始我自己测试,这段程序运行的灰常完美,什么问题也没有!
但是后来,问题,无情的来了当有多个人,比如100个人,并发访问的时候,就出现问题了:这100个同志,同时注册 "love" 这个用户名,那么他们首先执行 if(xxxDao.isUsernameExist("love")) 这条语句,来判断user表里,是不是存在 love 这个用户名,那么他们同时得到的结果,是不存在,可以注册这个用户名,然后紧接着开始执行 INSERT INTO...操作了。 后果就是我的user表里,出现了100个用户名是 love的记录。
这一下就完蛋了。。 那么我想请教一下各位大神,如果我把以上代码稍微做下修改,修改成下面这样:事务开始
if(xxxDao.isUsernameExist("love")){
return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
/** 使用的SQL语句为:
select * from user where username = 'love'
*/
}else{
xxxDao.insertUser(user);
return "用户注册成功,请返回登录页面登录!";
/** 使用的SQL语句为:
INSERT INTO user(id,username,password) SELECT 'xxxx','love','xxxxxxx' FROM DUAL WHERE NOT EXISTS ( SELECT * FROM user WHERE username = 'love' )
*/
}
事务结束
这样的话,还会不会存在以上那个并发问题?求大神指点我,尽量的能不用锁就不去用锁,能不去修改事务隔离级别,就尽量不修改,除非实在没办法了
这个锁有个技巧,加“动态的锁”。用username这个变量来做锁synchronized(username.intern()){xxxxx},在username后加上intern()方法的调用。这样就可以把相同用户名并发的列为排队,不相同的不用排队。(加在查询判断上面)
这个说的准确 。 唯一约束。另外,你的代码也得改下 ,改成如下代码,没必要去else了if(xxxDao.isUsernameExist("love")){
return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
}
xxxDao.insertUser(user);
return "用户注册成功,请返回登录页面登录!";
至于为什么有数据库约束还要之前用户名的判断的逻辑,个人觉得,主要还是用户体验和性能的问题吧,我们在输入用户名的时候一般这个用户名会通过ajax传送到后台校验,而不至于等用户信息表单填完提交之后再告诉用户说用户名已经存在,表单得重新填。。再者,在第一层面过滤了不合法(用户名重复)的信息的insert,不至于insert之后才发现unique了,在一定的程度上减轻了数据库的负担吧
根据返回的异常类型来处理就可以啦