You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 09:40:22