- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
本指南基于 Play Framework 2.4 迁移文档中关于 Anorm 的章节,系统讲解 Anorm 从 Play 核心中独立出来之后的依赖配置方式,以及 2.4 时代 Anorm 在BatchSql重构、行解析 API、流式处理、类型映射等方面的全部新特性。读完本文,你将掌握 Anorm 2.4 迁移所需的所有改动点,并能用索引取值、fold/foldWhile/withResult、数组与多值参数等新 API 编写更简洁、更安全的 SQL 访问代码。
背景:Anorm 从 Play 核心中独立出去
在 Play 2.4 中,Anorm 被从 Play 的核心中抽离出来,成为一个独立管理、拥有自己生命周期(可以独立发版、独立演进)的单独项目。这一变化与同版本中 Ebean、Play specs2 支持、数据库 evolutions 支持被拆分为独立模块的思路一致——让各个数据库相关组件能够按照自己的节奏演进,而不受 Play 主版本发布周期的约束。关于这次大版本迁移的整体背景,可参见 Play 2.4 迁移指南 与 Play 2.4 新特性概览(其中 Highlights24 的 Anorm 小节 以要点形式概括了本节介绍的全部新特性)。
添加 Anorm 依赖
由于 Anorm 已不再是 Play 核心的组成部分,迁移的第一步就是在构建配置中显式声明对它的依赖。在build.sbt中加入:
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.7"其中%%会根据项目的 Scala 版本自动选择对应的构建产物。Anorm 的完整版本列表可以从 Maven 中央仓库的org.playframework.anorm组下查询(注意:本文不提供外部链接,请在构建工具或 Maven 仓库页面中检索该坐标)。
版本兼容性提醒:Anorm 2.4.0 起要求 Java 8。最后一个兼容 JDK 1.6 或 1.7 的版本是 Anorm 2.3.9。如果你仍在旧版 JDK 上运行,必须停留在 2.3.9 及以下版本。这与 Play 2.4 整体弃用 Java 6/7、要求 Java 8 的决策是一致的。
值得一提的是,在 Play 2.4 及之前的时代,anorm还可以作为 Play 自带依赖通过PlayImport直接引入。仓库中的 build.sbt 示例 展示了这种经典写法:
libraryDependencies ++= Seq( jdbc, anorm, ehcache )从 Play 2.5 起官方推荐显式声明独立坐标(即上面的org.playframework.anorm写法),两种方式在你所迁移的目标版本中择一使用即可。
Changes:BatchSql 构造方式重构
新版本 Anorm 包含大量修复与改进。其中最重要的 API 破坏性变化源于 BatchSQL #3016 这一提交:SqlQuerycase class 被重构为 trait + 伴生对象的形式。随之而来的后果是,BatchSql不再接受预先构造的SqlQuery实例,而是直接传入原始 SQL 语句,由BatchSql内部完成创建与校验。
import anorm.BatchSql // 迁移前:直接传入 SqlQuery 实例(2.4 起不再被接受,无法编译) BatchSql(SqlQuery("SQL")) // 迁移后:传入原始 SQL 字符串 BatchSql("SQL") // 更简单、更安全,因为 SqlQuery 会在内部被创建并校验从源码结构看,这一重构把"SQL 语句是否合法、参数占位符是否匹配"的校验逻辑收敛到了BatchSql内部统一执行,从而避免了调用方绕过校验直接构造SqlQuery的隐患。迁移时只需把所有BatchSql(SqlQuery(...))写法改为BatchSql("...")即可。
Parsing:行解析能力增强
按列索引取值
现在可以从Row上通过列索引直接获取值,适合列名冗长或列位置固定的场景:
val res: (String, String) = SQL("SELECT * FROM Test").map(row => rowString -> rowString // 字符串列 #1 和 #2 )按标签(label)统一解析
列解析现在按标签统一处理,无论该标签是列名(name)还是别名(alias),都走同一套解析逻辑,不再区分两种来源:
val res: (String, Int) = SQL"SELECT text, count AS i".map(row => rowString -> rowInt )这里的count AS i是一个聚合函数别名列,在 2.4 之前这类标签的解析路径与普通列名不同,现在已完全统一。
新增 fold 与 foldWhile:面向结果流的折叠操作
这是 Anorm 2.4 新增的流式 API,用于对结果集逐行做累积计算,返回Either[List[Throwable], T]:左侧是所有执行过程中收集到的异常列表,右侧是折叠结果。
fold会处理结果流中的每一行:
val countryCount: Either[List[Throwable], Long] = SQL"Select count(*) as c from Country".fold(0l) { (c, _) => c + 1 }foldWhile则允许在满足条件时提前终止,例如只读取前 100 本书名:
val books: Either[List[Throwable], List[String]] = SQL("Select name from Books").foldWhile(List[String]()) { (list, row) => if (list.size == 100) (list -> false) // 停止,返回当前 list else (list :+ rowString) -> true // 继续,追加一个书名 }注意foldWhile的折叠函数返回值是一个二元组(acc, continue):第一元是累积值,第二元是布尔标记,false表示停止遍历。这种设计让"提前截断 + 部分处理"(如分页、限量加载)变得非常直接。
新增 withResult:自定义结果流解析器
如果fold/foldWhile仍不够灵活,可以用withResult提供完全自定义的流解析器。它把游标(Cursor)交给你的函数,由你自行决定何时停止、如何处理每一行:
import anorm.{ Cursor, Row } @annotation.tailrec def go(c: Option[Cursor], l: List[String]): List[String] = c match { case Some(cursor) => if (l.size == 100) l // 自定义上限,部分处理 else { val row = cursor.next() go(cursor.next(), l :+ rowString) } case _ => l } val books: Either[List[Throwable], List[String]] = SQL("Select name from Books").withResult(go(_, List.empty[String]))该示例演示了与游标配合的尾递归(@annotation.tailrec)写法:遍历过程中可以施加任意自定义逻辑(如数量上限、条件过滤),到达流末尾(None)时返回累积结果。withResult是fold/foldWhile之外面向高阶定制场景的通用入口。
类型映射(Type mappings):更丰富的列与参数转换
数组(Array / List)支持
当某列的类型是 JDBC 数组(java.sql.Array)时,Anorm 现在可以把它映射为数组或列表(Array[T]或List[T]),前提是元素类型T本身支持列映射:
import anorm.SQL import anorm.SqlParser.{ scalar, * } // 引入数组与元素解析器 import anorm.Column.{ columnToArray, stringToArray } val res: List[Array[String]] = SQL("SELECT str_arr FROM tbl").as(scalar[Array[String]].*)同时还提供了便捷的数组解析函数SqlParser.arrayT与SqlParser.listT,用于显式声明按数组或列表解析。
反过来,如果 JDBC 语句期望一个数组参数(java.sql.Array),也可以直接传入Array[T],只要元素类型T受支持:
val arr = Array("fr", "en", "ja") SQL"UPDATE Test SET langs = $arr".execute()多值参数(Multi-value parameter)
新增了将List[T]、Set[T]、SortedSet[T]、Stream[T]和Vector[T]作为多值参数传入的转换,配合IN ({param})写法可以避免手工拼接 SQL:
SQL("SELECT * FROM Test WHERE cat IN ({categories})") .on('categories -> List(1, 3, 4)) SQL("SELECT * FROM Test WHERE cat IN ({categories})") .on('categories -> Set(1, 3, 4)) SQL("SELECT * FROM Test WHERE cat IN ({categories})") .on('categories -> SortedSet("a", "b", "c")) SQL("SELECT * FROM Test WHERE cat IN ({categories})") .on('categories -> Stream(1, 3, 4)) SQL("SELECT * FROM Test WHERE cat IN ({categories})") .on('categories -> Vector("a", "b", "c"))on('categories -> ...)中的符号'categories对应 SQL 中的命名占位符{categories}。传入Set/SortedSet时元素的去重与排序由集合本身语义保证,实际展开为 SQL 列表参数。
数值与布尔类型转换的修订
基础类型(数值、布尔)的列转换得到改进。一些原本不合理的转换被移除,迁移后以下写法将不再被接受:
| 列类型(JDBC 类型) | (转为)JVM/Scala 类型 |
|---|---|
Double | Boolean |
Int | Boolean |
同时新增了大量数值/布尔转换,扩展了列类型支持:
| 列类型(JDBC 类型) | (转为)JVM/Scala 类型 |
|---|---|
BigDecimal | BigInteger |
BigDecimal | Int |
BigDecimal | Long |
BigInteger | BigDecimal |
BigInteger | Int |
BigInteger | Long |
Boolean | Int |
Boolean | Long |
Boolean | Short |
Byte | BigDecimal |
Float | BigDecimal |
Int | BigDecimal |
Long | Int |
Short | BigDecimal |
可以看出,新转换以"数值精度提升/缩窄"为主线(如Int↔Long、BigDecimal↔BigInteger互相转换),并允许布尔值与数值互转(Boolean→Int/Long/Short)。做迁移测试时应重点检查原先依赖Double/Int转Boolean的代码。
二进制与大对象(Binary and large data)
二进制列(bytes、stream、blob)新增了映射为Array[Byte]或InputStream的转换:
| ↓JDBC / JVM➞ | Array[Byte] | InputStream¹ |
|---|---|---|
| Array[Byte] | Yes | Yes |
| Blob² | Yes | Yes |
| Clob³ | No | No |
| InputStream⁴ | Yes | Yes |
| Reader⁵ | No | No |
- 类型
java.io.InputStream
- 类型
- 类型
java.sql.Blob
- 类型
- 类型
java.sql.Clob
- 类型
- 类型
java.io.Reader(表格原文如此,指流式输入源)
- 类型
- 类型
java.io.Reader(文本型字符流,不映射为二进制)
- 类型
二进制与大对象同样可以作为参数传入:
| JVM | JDBC |
|---|---|
| Array[Byte] | Long varbinary |
| Blob¹ | Blob |
| InputStream² | Long varbinary |
| Reader³ | Long varchar |
- 类型
java.sql.Blob
- 类型
- 类型
java.io.InputStream
- 类型
- 类型
java.io.Reader
- 类型
注意Clob/Reader这类字符型大对象不会转换为Array[Byte]/InputStream(二进制视角),而是作为文本参数以Long varchar传递,二者语义不可混用。
其他改进(Misc)
- Joda Time:新增从
Long、Date或Timestamp列到 JodaInstant或DateTime的转换。 - UUID 解析:可将文本列解析为
UUID值:SQL("SELECT uuid_as_text").as(scalar[UUID].single)。 - 可空参数:对可空参数传
None已标记为废弃,应改用类型安全的Option.empty[T],以便在编译期保留明确的元素类型信息:
// 废弃写法 .on('param -> None) // 推荐写法 .on('param -> Option.empty[String])实践提示:在 Play 应用中使用 Anorm
配合本次迁移,还有两点实践建议值得一并落实:
显式声明依赖并核对版本:确认目标 Anorm 版本满足 Java 8 要求,并将构建中的
anorm依赖更新为独立坐标(或按你所迁移的 Play 版本选用PlayImport的anorm条目,参考 build.sbt 示例)。使用自定义执行上下文执行 JDBC 操作:Anorm 的 SQL 执行属于阻塞式 JDBC 操作,官方数据库指南 AccessingAnSQLDatabase 明确建议通过
CustomExecutionContext(如DatabaseExecutionContext)来执行,避免占用 Play 的渲染线程池;文档中还给出了以物理核心数估算 JDBC 连接池大小的建议(连接数 ≈ 物理核心数 × 2 + 磁盘轴数)。
迁移检查清单
- 构建中添加
"org.playframework.anorm" %% "anorm"依赖(版本满足 Java 8 要求) - 所有
BatchSql(SqlQuery(...))改为BatchSql("...") - 检查
Double/Int转Boolean的列转换代码(2.4 起已移除) - 将
None可空参数改为Option.empty[T] - 新代码优先使用索引/标签取值、
fold/foldWhile/withResult、数组与多值参数等新 API - 数据库操作迁移到自定义
ExecutionContext上执行
- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
相关推荐
Play 2.3 新特性全解析:Activator、sbt-web 资产管线、Java 8 与 WS 独立化
Play 2.3 新特性全解析:Activator、sbt web 资产管线、Java 8 与 WS 独立化 Play 2.3 是 Play Framework
后端Web框架Wagtail 2.4 版本特性全解析:Starter Page、页面数量上限、image_url 与升级迁移指南
Wagtail 2.4 版本特性全解析:Starter Page、页面数量上限、image_url 与升级迁移指南 Wagtail 2.4(发布于 2018 年
CMS后端ComfyUI版本更新解析:新特性与迁移指南
ComfyUI版本更新解析:新特性与迁移指南 版本概览 ComfyUI当前最新版本为0.3.61,可通过 comfyui_version.py https://
人工智能大模型媒体生成本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考