Slick 3.5.1中Oracle LocalDateTime仅支持3位纳秒解析导致ORA-01830错误的全局解决方案问询
Slick 3.5.1中Oracle LocalDateTime仅支持3位纳秒解析导致ORA-01830错误的全局解决方案问询
我之前也碰到过类似的问题,Slick默认的OracleProfile对LocalDateTime的处理确实有点坑——它硬把纳秒精度编码成了3位,和Java LocalDateTime默认的9位纳秒完全不匹配,直接就触发ORA-01830错误了。不过不用慌,我们可以通过全局重写OracleProfile的类型映射来彻底解决这个问题,不用在每个查询里手动改时间格式。下面给你两个靠谱的方案:
方案一:自定义OracleProfile,支持9位纳秒全精度
这个方案适合需要保留完整纳秒精度的场景,核心是重写LocalDateTime的类型映射,让Slick生成的SQL使用FF9格式(支持9位纳秒),而不是默认的FF3。
首先创建一个自定义的OracleProfile:
import java.time.LocalDateTime import java.time.format.DateTimeFormatter import slick.jdbc.OracleProfile import slick.jdbc.PositionedParameters import slick.jdbc.PositionedResult import slick.ast.FieldSymbol object CustomOracleProfile extends OracleProfile { override val api: API = new API {} trait API extends super.API { // 重写LocalDateTime的全局类型映射 implicit override lazy val localDateTimeTypeMapper: BaseTypeMapper[LocalDateTime] = new BaseTypeMapper[LocalDateTime] { // 用于格式化Java LocalDateTime为9位纳秒的字符串 private val javaDateTimeFormatter = DateTimeFormatter.ofPattern("dd-MMM-yy HH:mm:ss.SSSSSSSSS") // Oracle TO_TIMESTAMP对应的格式串(支持9位纳秒) private val oracleTimestampPattern = "DD-Mon-RR HH24:MI:SS.FF9" override def sqlType: Int = java.sql.Types.TIMESTAMP override def sqlTypeName(sym: Option[FieldSymbol]): String = "TIMESTAMP(9)" // 把LocalDateTime转成Oracle能识别的SQL字面量 override def valueToSQLLiteral(value: LocalDateTime): String = { val formattedTime = javaDateTimeFormatter.format(value) s"TO_TIMESTAMP('$formattedTime', '$oracleTimestampPattern')" } // 处理参数绑定,确保写入数据库时保留全精度 override def setParameter(v: LocalDateTime, pp: PositionedParameters): Unit = { pp.setTimestamp(java.sql.Timestamp.valueOf(v)) } // 处理结果集读取,确保从数据库读回时保留全精度 override def getResult(r: PositionedResult): LocalDateTime = { r.nextTimestamp().toLocalDateTime } } } }
然后,把项目中所有引用默认OracleProfile的地方,全部替换成这个自定义的Profile:
// 原来的导入要注释掉 // import slick.jdbc.OracleProfile.api._ // 换成自定义的Profile import CustomOracleProfile.api._
这样之后,Slick生成的SQL就会自动用FF9格式,和Java LocalDateTime的9位纳秒完全匹配,不会再出现格式不匹配的错误。
方案二:全局截断纳秒到3位(适合不需要全精度的场景)
如果你的业务不需要9位纳秒精度,也可以选择全局把LocalDateTime的纳秒截断到3位,和Slick默认的格式兼容。同样通过重写Profile实现:
import java.time.LocalDateTime import java.time.format.DateTimeFormatter import slick.jdbc.OracleProfile import slick.jdbc.PositionedParameters import slick.jdbc.PositionedResult import slick.ast.FieldSymbol object TruncatedOracleProfile extends OracleProfile { override val api: API = new API {} trait API extends super.API { implicit override lazy val localDateTimeTypeMapper: BaseTypeMapper[LocalDateTime] = new BaseTypeMapper[LocalDateTime] { private val javaDateTimeFormatter = DateTimeFormatter.ofPattern("dd-MMM-yy HH:mm:ss.SSS") private val oracleTimestampPattern = "DD-Mon-RR HH24:MI:SS.FF3" override def sqlType: Int = java.sql.Types.TIMESTAMP override def sqlTypeName(sym: Option[FieldSymbol]): String = "TIMESTAMP(3)" override def valueToSQLLiteral(value: LocalDateTime): String = { // 把纳秒截断到3位(除以1000000后取整再乘回去) val truncatedTime = value.withNano((value.getNano / 1000000) * 1000000) val formattedTime = javaDateTimeFormatter.format(truncatedTime) s"TO_TIMESTAMP('$formattedTime', '$oracleTimestampPattern')" } override def setParameter(v: LocalDateTime, pp: PositionedParameters): Unit = { val truncatedTime = v.withNano((v.getNano / 1000000) * 1000000) pp.setTimestamp(java.sql.Timestamp.valueOf(truncatedTime)) } override def getResult(r: PositionedResult): LocalDateTime = { r.nextTimestamp().toLocalDateTime } } } }
同样,替换项目中的Profile导入即可,这种方案的好处是完全兼容Slick原来的逻辑,只是截断了精度,不会引入新的格式问题。
小提示
不管用哪个方案,一定要确保项目中所有使用OracleProfile的地方都替换成自定义的版本,包括DAO层、查询定义的地方,这样才能真正实现全局生效。
备注:内容来源于stack exchange,提问作者M. Barabas
相关产品推荐
相关产品推荐

