java、Kotlin、Exposed ORM经验
`object` 和 `class` 的关键区别在于:**`class` 是蓝图,用于创建多个实例;而 `object` 是单一实体,本身就是一个唯一的、全局可用的实例**。 为了更直观地理解,我们先来看一个对比表格,然后进行详细解释。 ### `object` 和 `class` 主要区别对比 | 特性 | `class` (类) | `object` (对象声明) | `companion object` (伴生对象) | | :--- | :--- | :--- | :--- | | **核心概念** | 创建对象的**蓝图**或模板 | 实现**单例模式**的语法糖 | 属于类的**单例对象**,相当于 Java 的 `static` | | **实例化** | 必须手动实例化 (`val obj = MyClass()`) | **无需且无法**实例化,直接通过名称访问 | **无需且无法**实例化,通过类名直接访问 | | **数量** | 可以创建**多个**实例 | **有且只有一个**全局实例 | **有且只有一个**(依附于所属类) | | **用途** | 定义需要多次实例化的数据结构或行为 | 工具类、全局状态容器、服务访问点 | 工厂方法、类常量、替代 Java 的静态成员 | --- ### 详细解释和代码示例 #### 1. `class` - 需要实例化的模板 一个 `class` 只是一个模板或蓝图。要使用它,你必须创建它的实例(对象)。你可以根据需要创建多个实例,每个实例都有自己的状态。 ```kotlin // 定义一个类 (蓝图) class Car(val brand: String, var speed: Int) { fun accelerate() { speed += 10 } } fun main() { // 创建类的多个实例 val car1 = Car("Toyota", 0) // 使用构造函数创建实例 val car2 = Car("BMW", 50) car1.accelerate() println(car1.speed) // 输出: 10 println(car2.speed) // 输出: 50 // car1 和 car2 是两个独立的对象 println(car1 == car2) // 输出: false } ``` #### 2. `object` - 单例(只有一个实例的对象) 一个 `object` 声明同时定义了一个类**和它的一个唯一实例**。它用于当你需要且只需要一个某种类型的对象时(单例模式)。你无法创建它的实例,直接通过对象名访问。 ```kotlin // 定义一个对象声明 (单例) object ApplicationConfig { // 这些属性在整个程序中只有一份 val version: String = "1.0.0" var isDebug: Boolean = true fun setup() { println("Application version $version is configured. Debug: $isDebug") } } fun main() { // 直接通过对象名访问,无需实例化 println(ApplicationConfig.version) // 输出: 1.0.0 ApplicationConfig.setup() // 输出: Application version 1.0.0 is configured. Debug: true ApplicationConfig.isDebug = false // 修改全局状态 // 在任何地方访问的都是同一个实例 println(ApplicationConfig.isDebug) // 输出: false } ``` **常见用途**:工具类(如 `JsonUtils`)、全局配置、服务访问点(如 `DatabaseManager`)、实现接口的单例。 #### 3. `companion object` - 类内部的单例(伴生对象) 这是一个定义在类内部的 `object`。它允许你调用其成员时**像调用 Java 的静态方法一样**,使用类名作为限定符,但这些成员仍然是真实对象的实例成员。 ```kotlin class MyClass { // 实例属性,每个实例都有自己的副本 val instanceProperty: String = "I belong to an instance" // 伴生对象 companion object { // 属于伴生对象的属性,全局只有一份 const val CLASS_CONSTANT: String = "I belong to the class" fun create(): MyClass { // 可以访问私有构造函数 return MyClass() } } } fun main() { // 通过类名直接访问伴生对象的成员,无需实例化 MyClass println(MyClass.CLASS_CONSTANT) // 输出: I belong to the class val obj = MyClass.create() // 调用伴生对象的工厂方法 // 实例成员仍需通过实例访问 println(obj.instanceProperty) // 输出: I belong to an instance // 错误示例:无法通过类名访问实例成员 // println(MyClass.instanceProperty) // 编译错误! } ``` **常见用途**: - **工厂方法** (`MyClass.create()`) - **类常量** (`MyClass.CLASS_CONSTANT`) - **替代 Java 的静态方法和字段** - 实现接口(伴生对象可以实现接口,而 Java 的静态方法不能) --- ### 如何选择? - **需要创建多个具有相同结构但不同状态的对象?** → 使用 `class`。 ```kotlin class User(val name: String) // 每个用户都有不同的名字 ``` - **只需要一个全局对象来管理某些状态或提供工具函数?** → 使用 `object`。 ```kotlin object Logger { fun log(message: String) { ... } } // 使用:Logger.log("Something happened") ``` - **需要一些与类相关而不是与实例相关的功能(如常量、工厂方法)?** → 在类内部使用 `companion object`。 ```kotlin class ApiClient { companion object { const val BASE_URL = "https://api.example.com" fun newInstance(): ApiClient { ... } } } // 使用:ApiClient.BASE_URL, ApiClient.newInstance() ``` ### 总结 | 关键词 | 核心思想 | 类比 | | :--- | :--- | :--- | | **`class`** | **蓝图** | 饼干模具 | | **`object`** | **唯一的实物** | 唯一的、特殊的饼干 | | **`companion object`** | **放在蓝图画旁边的唯一实物** | 和饼干模具放在一起的那个唯一的标准参考饼干 | 简单来说:用 `class` 来“批量生产”,用 `object` 来“独一无二”。
Exposed
# 代码逐行注释及 `it` 解释 ```kotlin // 定义包名,表示这个文件位于 com.example.service 包中 package com.example.service // 导入自定义的 User 数据类 import com.example.model.User // 导入 Exposed 框架中定义的 Users 表对象 import com.example.model.Users // 导入 Exposed SQL DSL 的相关类 import org.jetbrains.exposed.sql.* // 导入 Exposed 的条件表达式构建器(eq 用于创建相等比较) import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq // 导入 Exposed 的事务处理功能 import org.jetbrains.exposed.sql.transactions.transaction // 定义一个对象声明(单例模式),包含所有用户相关的数据库操作 object UserService { // 创建用户函数:接收姓名和邮箱,返回新用户的ID fun createUser(name: String, email: String): Int = transaction { // 向 Users 表插入新记录 Users.insert { // 这里的 it 是 Exposed 提供的 InsertStatement 类型对象 // 它代表当前正在构建的插入语句,我们可以通过它设置要插入的字段值 it[Users.name] = name // 设置 name 字段的值 it[Users.email] = email // 设置 email 字段的值 }[Users.id] // 获取插入操作返回的id字段值 } // 获取所有用户函数:返回用户列表 fun getAllUsers(): List<User> = transaction { // 查询 Users 表中的所有记录 Users.selectAll().map { // 这里的 it 是 ResultRow 类型对象,代表查询结果中的一行数据 // 我们将每一行数据转换为 User 对象 toUser(it) } } // 根据ID获取用户函数:接收用户ID,返回对应的用户对象或null fun getUserById(id: Int): User? = transaction { // 查询 Users 表中id等于指定值的记录 Users.select { Users.id eq id }.singleOrNull()?.let { // 这里的 it 是 ResultRow 类型对象,代表查询到的单行数据 // 使用 let 函数将查询结果转换为 User 对象 toUser(it) } } // 更新用户函数:根据ID更新用户的姓名和邮箱,返回是否更新成功 fun updateUser(id: Int, name: String, email: String): Boolean = transaction { // 更新 Users 表中id等于指定值的记录 Users.update({ Users.id eq id }) { // 这里的 it 是 Exposed 提供的 UpdateStatement 类型对象 // 它代表当前正在构建的更新语句,我们可以通过它设置要更新的字段值 it[Users.name] = name // 设置 name 字段的新值 it[Users.email] = email // 设置 email 字段的新值 } > 0 // 更新操作返回受影响的行数,大于0表示更新成功 } // 删除用户函数:根据ID删除用户,返回是否删除成功 fun deleteUser(id: Int): Boolean = transaction { // 删除 Users 表中id等于指定值的记录 Users.deleteWhere { Users.id eq id } > 0 // 删除操作返回受影响的行数,大于0表示删除成功 } // 私有辅助函数:将数据库查询结果行转换为 User 对象 private fun toUser(row: ResultRow): User = User( id = row[Users.id], // 从结果行中提取id字段值 name = row[Users.name], // 从结果行中提取name字段值 email = row[Users.email], // 从结果行中提取email字段值 createdAt = row[Users.createdAt] // 从结果行中提取createdAt字段值 ) } ``` ## 关于 `it` 的解释 在 Kotlin 中,`it` 是一个**隐式参数名**,当 lambda 表达式只有一个参数时,可以使用 `it` 来引用这个参数,而不需要显式声明参数名。 在这段代码中,`it` 出现在几个不同的上下文中,但都是作为 lambda 表达式的单一参数: 1. **在 `insert` 和 `update` 操作中**: ```kotlin Users.insert { it[Users.name] = name it[Users.email] = email } ``` 这里的 `it` 是 `InsertStatement` 类型(对于插入)或 `UpdateStatement` 类型(对于更新),它代表了当前正在构建的 SQL 语句。通过 `it[字段] = 值` 的语法,我们设置了要插入或更新的字段值。 2. **在 `map` 和 `let` 函数中**: ```kotlin Users.selectAll().map { toUser(it) } ``` 这里的 `it` 是 `ResultRow` 类型,代表了数据库查询结果中的一行数据。`map` 函数对集合中的每个元素应用给定的 lambda 表达式,这里的 `it` 就是当前正在处理的元素。 3. **在 `singleOrNull()?.let` 中**: ```kotlin Users.select { Users.id eq id }.singleOrNull()?.let { toUser(it) } ``` 这里的 `it` 也是 `ResultRow` 类型。`let` 函数将调用它的对象作为参数传递给 lambda 表达式,这里的 `it` 就是 `singleOrNull()` 返回的结果(可能为 null)。 总结:`it` 不是固定的类型,它的类型取决于它所处的上下文。在 Kotlin 中,使用 `it` 可以简化只有一个参数的 lambda 表达式的书写,使代码更加简洁。
📦 项目构建命令 ./gradlew build: 这是最常用的命令。它会执行完整的构建生命周期:编译源代码 → 处理资源文件 → 运行单元测试 → 打包(如生成 JAR 或 WAR 文件)。当你需要验证代码是否能完整构建并通过测试时,就用它。 ./gradlew assemble: 这个命令只进行编译和打包,不会运行测试。如果你只是想快速生成一个可部署的包(例如在 CI/CD 流程中,测试已在另一阶段完成),这个命令更快。 ./gradlew clean: 这个命令会删除项目根目录下的 build 文件夹,清理掉所有之前的构建产物。当你遇到一些奇怪的构建问题,或者想确保下一次构建绝对从零开始时,可以先执行它,然后再 build。 🧪 测试相关命令 ./gradlew test: 运行项目中的所有单元测试。Gradle 会执行测试任务并输出结果摘要,告诉你哪些测试通过,哪些失败。 ./gradlew test --tests "*MyTest*": 你可以使用 --tests 参数来指定运行特定的测试类或测试方法,支持通配符 *。这在调试单个测试时非常高效,例如 ./gradlew test --tests "*MyServiceTest*"。 📦 依赖管理命令 ./gradlew dependencies: 这个命令会显示项目的依赖树。你可以清楚地看到所有直接依赖和传递性依赖,以及它们的版本。这对于排查依赖冲突(如不同版本的同名库)至关重要。 你还可以查看特定子模块的依赖(如 ./gradlew :app:dependencies)或特定配置的依赖(如 ./gradlew dependencies --configuration compileClasspath)。 ./gradlew --refresh-dependencies: Gradle 会缓存下载的依赖项以提升效率。这个命令会强制 Gradle 忽略缓存,重新从远程仓库检查并下载依赖。当你更新了版本号或清理缓存后,确保获取的是最新依赖时使用。 🚀 项目运行与调试命令 ./gradlew run: 如果你的 Kotlin 项目配置了 application 插件并在 build.gradle.kts 中指定了主类(mainClass),这个命令会直接编译并运行你的应用程序。 ./gradlew bootRun: 对于 Spring Boot 项目(应用了 org.springframework.boot 插件),这个命令是运行应用的首选方式。它集成了 Boot 的特性,如智能类路径加载和实时热加载(如果配置了 spring-boot-devtools)。 🔍 任务查询与信息命令 ./gradlew tasks: 刚接触一个项目时,运行这个命令可以列出所有可用的 Gradle 任务及其简要说明,帮你快速了解项目的构建能力。 ./gradlew --version: 显示当前项目使用的 Gradle 版本、Groovy 版本、Kotlin 版本(如果用了 Kotlin DSL)以及 JVM 信息,用于环境确认。 📝 代码质量与分析命令 许多 Kotlin 项目会集成代码质量工具,这些工具通常通过 Gradle 任务调用: ./gradlew ktlintCheck: 如果你配置了 Ktlint,这个命令会检查 Kotlin 代码风格是否符合规范。 ./gradlew detekt: 如果你配置了 Detekt,这个命令会进行静态代码分析,查找代码中的潜在问题、错误模式或复杂度问题。 💡 高效使用 Gradle 的技巧 使用 --daemon (守护进程): Gradle 守护进程可以显著提升后续构建的速度,因为它会缓存项目信息。现代版本的 Gradle 默认启用。 使用 --parallel: 如果你的项目包含多个子模块,这个选项可以让 Gradle并行构建这些模块,从而加快整体构建速度。 使用 --console=plain 或 --console=verbose: 在 CI/CD 环境中,你可能希望输出更简洁或更详细的日志,这个参数可以控制控制台输出的样式和内容。 使用 --info 或 --debug: 当构建失败且默认错误信息不足时,添加 --info 或 --debug 可以获取更详细的日志输出,帮助定位问题。 在 IDE 中操作: IntelliJ IDEA 等 IDE 提供了出色的 Gradle 工具窗口,你可以可视化地查看和执行任务,通常比手动输入命令更便捷。 ⚠️ 常见问题与解决 依赖下载失败或缓慢: 可以考虑配置国内镜像源(如在 build.gradle.kts 或 settings.gradle.kts 的 repositories 块中添加阿里云、腾讯云等镜像仓库地址)。 构建缓存问题: 如果遇到奇怪的构建行为,尝试先运行 ./gradlew clean,然后再构建。 任务未找到: 确保你在项目的根目录下执行命令,或者使用了正确的任务路径(例如 :subproject-name:taskName)。
要生成可执行的JAR,有几种方法: 使用Application插件的分发任务:它会生成一个包含所有依赖的压缩包(tar或zip),并在其中包含一个启动脚本。但是您可能只想有一个可执行的JAR。 使用Shadow插件:这是一个常用的创建fat jar的插件。 手动配置Jar任务:将主类信息添加到清单文件,并包含所有依赖。 由于您已经使用了Application插件,但可能没有使用其分发任务,而是直接使用了jar任务,所以生成的JAR缺少清单信息。 解决方案1:使用Shadow插件(推荐) 步骤1:在build.gradle.kts中添加Shadow插件 首先,在plugins块中添加Shadow插件的引用: kotlin plugins { kotlin("jvm") version "1.9.22" application id("com.github.johnrengelman.shadow") version "8.1.1" // 添加Shadow插件 } 步骤2:重新构建 然后使用shadowJar任务构建: bash ./gradlew shadowJar 构建完成后,会在build/libs/目录下生成一个以-all.jar结尾的fat jar,这个jar包含了所有依赖和正确的主类清单。

浙公网安备 33010602011771号