如何在Slick中使用UUID作为VARCHAR列?

8

我有一个MySQL 5.7数据库,其中有几个表在VARCHAR数据类型的列中存储UUID。我正在尝试将我们的代码库转换为使用Slick 3.1.1,但我遇到了UUID转换为字符串的问题。这是我的列定义:

def myCol: Rep[UUID] = column[UUID]("myCol", SqlType("VARCHAR"))

当我运行查询时,会出现以下异常:
Got the exception java.sql.SQLException: Incorrect string value: '\xDA\xFD\xDAuOL...' for column 'myCol' at row 1

据我所知,Slick 假设 UUID 应存储为二进制列类型,因此我尝试使用以下代码将其隐式转换为字符串:
implicit val uuidToString = MappedColumnType.base[UUID, String](_.toString, UUID.fromString)
def myCol: Rep[UUID] = column[UUID]("myCol", SqlType("VARCHAR"))(uuidToString)

这可以用于插入,但是当我使用myCol选择值时,结果为空。有人知道如何强制Slick使用VARCHAR而不是BINARY(16)进行数据转换吗?
编辑:
使用隐式的查询没有返回任何结果:
db.run { table.filter(_.myCol === val)).result }

但是这个可以:
db.run { table.result }.map(_.filter(_.myCol == val))

编辑2:

我有一个小项目,这里有一个演示: https://github.com/nmatpt/slick-uuid-test


只是一个提示,你也可以存储高/低位比特,因为 UUID 只是两个“long”值(将这两列配对成单个唯一键)。如果你仍然想使用字符串,那么不需要 VARCHAR(因为 UUID 字符串始终为36个字符,去掉连字符后为32个字符)。 - Rogue
2个回答

7
你确定数据库中有空值吗?我刚刚检查了我的实现(在一个项目中,完全相同的解决方案),它似乎能够完美地工作。请确保你的物理列(在数据库中)实际上是VARCHAR类型(而不是一些二进制类型)。
编辑: 好的,我们遇到的问题是MySQL配置文件中已经将UUID定义为BINARY类型。重新定义它的快速方法(虽然可能有点粗略)如下:
package profile

import java.sql.{PreparedStatement, ResultSet}

import slick.ast._
import slick.jdbc.MySQLProfile

object CustomMySqlProfile  extends MySQLProfile {
  import java.util.UUID

  override val columnTypes = new JdbcTypes

  class JdbcTypes extends super.JdbcTypes {
    override val uuidJdbcType = new UUIDJdbcType {
      override def sqlTypeName(sym: Option[FieldSymbol]) = "UUID"
      override def valueToSQLLiteral(value: UUID) = "'" + value + "'"
      override def hasLiteralForm = true

      override def setValue(v: UUID, p: PreparedStatement, idx: Int) = p.setString(idx, toString(v))
      override def getValue(r: ResultSet, idx: Int) = fromString(r.getString(idx))
      override def updateValue(v: UUID, r: ResultSet, idx: Int) = r.updateString(idx, toString(v))

      private def toString(uuid: UUID) = if(uuid != null) uuid.toString else null
      private def fromString(uuidString: String) = if(uuidString != null) UUID.fromString(uuidString) else null
    }
  }
}

然后导入它而不是常规的api

import profile.CustomMySqlProfile.api._

这个方法确实可行 - 但请记住,现在 UUID 将在整个应用程序中映射为字符串(而不是默认的二进制格式)。基本上,我需要覆盖配置文件只是因为 UUID 转换已经内置。通常,您会使用类型映射。

类似的问题在这里以类似的方式解决:https://github.com/slick/slick/issues/1446 (同样是针对 Oracle 驱动程序的 UUID 问题)。


另外,“results come empty”是什么意思?如果该字段为空(因为您将使用无效值转换UUID.fromString),则可能应该抛出异常。将映射拆分为单独的函数可能是值得的(一个用于映射UUID->字符串,另一个用于字符串->UUID)。在第二个函数中,您可以添加一些日志记录/断点以查看发生了什么。 - Paul Dolega
是的,写完评论后我意识到了。我有一个想法-尝试将您的版本更改为此版本:https://mvnrepository.com/artifact/com.typesafe.slick/slick_2.11/3.2.0-M1它非常稳定,但更重要的是它具有新的日志记录级别。添加此内容:<logger name="slick.jdbc.JdbcBackend.parameter" level="DEBUG"/> <-它会精确地打印参数(而不是问号)对于您来说,更相关的更改之一可能是这样的:他们在3.2中更改了导入slick.driver.MySQLDriver.api._slick.jdbc.MySQLProfile.api._ - Paul Dolega
你能把你的查询代码和这个隐式一起粘贴吗? - Paul Dolega
测试通过了,应该之前就这样做。已经更新了上面的答案,更改了“CustomMySqlProfile”。看一下PR吧,现在两个测试都通过了。 - Paul Dolega
为了让它正常工作,我将 override def sqlTypeName(sym: Option[FieldSymbol]) = "UUID" 更改为 override def sqlTypeName(sym: Option[FieldSymbol]) = "VARCHAR" - Omer Levi Hevroni
显示剩余7条评论

1
implicit val uuidToString = MappedColumnType.base[UUID, String](_.toString, UUID.fromString)

@QP,对于选择器,这也是有效的,但重要的是,在构建包含“UUID”的VARCHAR列的查询时,必须在所有地方范围内进行此隐式转换。否则,带有WHERE条件的选择将永远不会匹配并返回空结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接