Exposed框架邮箱校验异常:saveUserInfo重复创建用户问题
问题分析
使用同一账号登录时,saveUserInfo方法每次都会在数据库中创建新用户条目,尽管代码逻辑中已包含邮箱存在性检查,但未起到预期效果。
相关代码
override suspend fun saveUserInfo(user: UserResponse): Boolean = withContext(dispatcher) { try { val existingUser = transaction { UserEntity.find { UserTable.emailAddress eq user.emailAddress }.singleOrNull() } if (existingUser == null) { logger.debug("User does not exist, creating a new one") transaction { UserEntity.new { name = user.name emailAddress = user.emailAddress profilePhoto = user.profilePhoto } } logger.debug("User successfully created") true } else { logger.debug("User already exists") false } } catch (e: Exception) { logger.error("Error in saveUserInfo: ${e.message}", e) false } }
现象佐证
- 日志显示每次登录都会输出"User does not exist, creating a new one"和"User successfully created",说明存在性检查未匹配到已有用户。
- 数据库截图显示同一邮箱对应多条用户记录,确认重复创建问题属实。
可能原因及解决方案
1. 邮箱大小写不匹配
若数据库中存储的邮箱为小写形式(如test@example.com),但登录传入的邮箱包含大写字母(如Test@Example.com),默认数据库查询会区分大小写,导致检查不到已有用户。
解决方法:
- 数据库层面:给
emailAddress字段设置不区分大小写的唯一约束,比如MySQL使用COLLATE utf8mb4_general_ci,PostgreSQL使用citext类型。 - 代码层面:查询时统一转换为小写(或大写):
val existingUser = transaction { UserEntity.find { UserTable.emailAddress.lower() eq user.emailAddress.lowercase() }.singleOrNull() }
2. 并发事务导致幻读
多个请求同时触发该方法时,可能出现幻读:第一个请求检查用户不存在,在创建用户前,第二个请求也检查到用户不存在,最终导致重复创建。
解决方法:
- 将检查与创建逻辑合并到同一个事务中,避免并发间隙:
override suspend fun saveUserInfo(user: UserResponse): Boolean = withContext(dispatcher) { try { transaction { val existingUser = UserEntity.find { UserTable.emailAddress eq user.emailAddress }.singleOrNull() if (existingUser == null) { logger.debug("User does not exist, creating a new one") UserEntity.new { name = user.name emailAddress = user.emailAddress profilePhoto = user.profilePhoto } logger.debug("User successfully created") true } else { logger.debug("User already exists") false } } } catch (e: Exception) { logger.error("Error in saveUserInfo: ${e.message}", e) false } }
- 同时给数据库的
emailAddress字段添加唯一约束,从底层阻止重复插入,即使代码出现并发问题,数据库也会抛出异常拦截重复数据。
3. 实体映射或查询DSL错误
检查UserTable.emailAddress是否正确映射到数据库表的邮箱字段,确认查询条件使用的列名无误。
解决方法:
- 核对实体类与数据库表的字段映射关系,确保
emailAddress对应正确的数据库列。 - 开启SQL日志,查看实际执行的查询语句,验证查询条件是否符合预期。
内容的提问来源于stack exchange,提问作者Morozov
相关产品推荐
相关产品推荐

