保持領先
VMware 提供培訓和認證,助您快速提升。
瞭解更多繼我的第一篇 Kotlin 部落格文章 之後,今天我想介紹我為即將到來的 Spring I/O 2016 大會演講“使用 Kotlin 和 Spring Boot 開發地理空間 Web 服務”而開發的新 Spring Boot + Kotlin 應用。
此應用的目標之一是展示如何利用原生資料庫功能,就像我們在 NoSQL 領域所做的那樣。這裡我們想使用由 PostGIS 提供的地理空間支援,PostGIS 是 PostgreSQL 的空間資料庫擴充套件。 原生 JSON 支援 也是一個不錯的用例。
這個 Geospatial Messenger 示例應用 可在 GitHub 上獲取,它提供了 2 種風格:
master
分支使用了 Exposed,這是一個由 JetBrains 建立的帶有型別安全 API 的 Kotlin SQL 庫。它可以與 Query DSL SQL 或 jOOQ 相媲美,但它提供了地道的 Kotlin API,並且不需要程式碼生成。spring-data-jdbc-repository
分支使用了 spring-data-jdbc-repository
,這是一個社群專案,允許使用 Spring Data PagingAndSortingRepository
API 執行原始 SQL 查詢,而無需 JPA。我使用的是 Jakub Jirutka 的這個分支,它是 Tomasz Nurkiewicz 原始專案 的改進版本。一個 Spring Data JPA + Hibernate Spatial 版本 會很有趣,所以歡迎提交 pull request 來貢獻 ;-) Kotlin Query DSL 支援也會很好,但目前尚不支援(如果您感興趣,請在 此 issue 上評論)。在這篇部落格文章中,我將重點介紹 Exposed 版本。
藉助以下這 2 個 Kotlin 類,我們的領域模型很容易描述
class Message(
var content : String,
var author : String,
var location : Point? = null,
var id : Int? = null
)
class User(
var userName : String,
var firstName : String,
var lastName : String,
var location : Point? = null
)
Exposed 允許我們使用型別安全的 SQL API 描述表的結構,使用起來非常方便(支援自動補全、重構且不易出錯)
object Messages : Table() {
val id = integer("id").autoIncrement().primaryKey()
val content = text("content")
val author = reference("author", Users.userName)
val location = point("location").nullable()
}
object Users : Table() {
val userName = text("user_name").primaryKey()
val firstName = text("first_name")
val lastName = text("last_name")
val location = point("location").nullable()
}
值得注意的是,Exposed 本身並不原生支援 PostGIS 功能,例如幾何型別或地理空間請求。這時 Kotlin 擴充套件 就大顯身手了,它只需幾行程式碼即可新增此類支援,而無需使用擴充套件類。
fun Table.point(name: String, srid: Int = 4326): Column<Point>
= registerColumn(name, PointColumnType())
infix fun ExpressionWithColumnType<*>.within(box: PGbox2d) : Op<Boolean>
= WithinOp(this, box)
更新:現在我們可以使用 Exposed 的 @Transactional
支援了!事務管理只需在 Application
類中配置 @EnableTransactionManagement
註解和一個 PlatformTransactionManager
bean 即可。
我們的倉庫也非常簡潔靈活,因為它們允許你使用型別安全的 SQL API 編寫任何型別的 SQL 請求,即使是帶有複雜 WHERE
子句的請求。
請注意,由於我們使用的是 Spring Framework 4.3,因此在單建構函式類中 我們不再需要指定 @Autowired
註解。
interface CrudRepository<T, K> {
fun createTable()
fun create(m: T): T
fun findAll(): Iterable<T>
fun deleteAll(): Int
fun findByBoundingBox(box: PGbox2d): Iterable<T>
fun updateLocation(userName:K, location: Point)
}
interface UserRepository: CrudRepository<User, String>
@Repository
@Transactional // Should be at @Service level in real applications
class DefaultUserRepository(val db: Database) : UserRepository {
override fun createTable() = SchemaUtils.create(Users)
override fun create(user: User): User {
Users.insert(toRow(user))
return user
}
override fun updateLocation(userName:String, location: Point) = {
location.srid = 4326
Users.update({Users.userName eq userName})
{ it[Users.location] = location }
}
override fun findAll() = Users.selectAll().map { fromRow(it) }
override fun findByBoundingBox(box: PGbox2d) =
Users.select { Users.location within box }
.map { fromRow(it) }
override fun deleteAll() = Users.deleteAll()
private fun toRow(u: User): Users.(UpdateBuilder<*>) -> Unit = {
it[userName] = u.userName
it[firstName] = u.firstName
it[lastName] = u.lastName
it[location] = u.location
}
private fun fromRow(r: ResultRow) =
User(r[Users.userName],
r[Users.firstName],
r[Users.lastName],
r[Users.location])
}
控制器也非常簡潔,並使用了 Spring Framework 4.3 即將推出的 @GetMapping
/ @PostMapping
註解,這些註解只是 @RequestMapping
註解針對特定方法的快捷方式。
@RestController
@RequestMapping("/user")
class UserController(val repo: UserRepository) {
@PostMapping
@ResponseStatus(CREATED)
fun create(@RequestBody u: User) { repo.create(u) }
@GetMapping
fun list() = repo.findAll()
@GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}")
fun findByBoundingBox(@PathVariable xMin:Double,
@PathVariable yMin:Double,
@PathVariable xMax:Double,
@PathVariable yMax:Double)
= repo.findByBoundingBox(
PGbox2d(Point(xMin, yMin), Point(xMax, yMax)))
@PutMapping("/{userName}/location/{x},{y}")
@ResponseStatus(NO_CONTENT)
fun updateLocation(@PathVariable userName:String,
@PathVariable x: Double,
@PathVariable y: Double)
= repo.updateLocation(userName, Point(x, y))
}
客戶端是純 HTML + Javascript 應用,使用 OpenLayers 地相簿開發(詳見 index.html 和 map.js),它可以對你進行地理定位,並透過 Server-Sent Events 與其他使用者收發地理位置資訊。
最後同樣重要的是,藉助出色的 Spring REST docs 專案,REST API 得到了充分的測試和文件化,詳見 MessageControllerTests 和 index.adoc。
開發這個應用給我的主要印象是它既有趣又高效,而且透過 SQL API、Kotlin 型別系統和 空安全 提供了高度的靈活性和安全性。生成的 Spring Boot 應用是一個 18 MB 的獨立可執行 jar 包,記憶體消耗很低(應用甚至可以在 -Xmx32m
下執行!!!)。使用 Spring REST docs 也令人愉快,再次展示了 Kotlin 良好的 Java 互操作性。
我遇到的一些痛點(陣列註解屬性、Java 8 Stream 支援、完整可呼叫引用支援)計劃在 Kotlin 1.1 中修復。Exposed 庫尚處於早期階段,需要進一步成熟,但從我的角度來看,它很有前景,並展示瞭如何使用 Kotlin 構建型別安全的 DSL API(這個 HTML 型別安全構建器 也是一個很好的例子)。
請記住,官方支援的 Spring Data 專案 與 Kotlin 配合得很好,正如我在 上一篇部落格文章 中的 spring-boot-kotlin-demo 專案所示。
如果您恰巧在五月中旬身處巴塞羅那(無論如何,巴塞羅那都是一個不錯的選擇!),請不要錯過參加 Spring I/O 大會 的機會。此外,SpringOne Platform(八月初,拉斯維加斯)的註冊最近也已開放,如果您想享受早鳥票價格,請儘快註冊。後者也仍在接受演講提案。因此,如果您有興趣就 Spring 或 Pivotal 相關技術發表演講,請隨時提交!