【问题标题】:Kotlin: property declaration in hibernate modelKotlin:休眠模型中的属性声明
【发布时间】:2026-02-11 14:50:01
【问题描述】:

我们有大量的 Java 应用程序,并且正在尝试在 Kotlin 中实现我们的第一个应用程序。问题是:初始化简单休眠模型属性的最佳方法是什么?

让我们以 Java 为例:

@Entity
@Table(name = "Session_Ids")
public class SessionId() {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    @Column
    protected Long number;
}

现在让我们假设数据库中的 id 和数字永远不能为空。因此,在休眠完成所有操作后,模型将始终在 id 字段中具有一个值,在 number 字段中具有一个值。

如何在 Kotlin 中做到这一点? 我不能用 null 来初始化它们,因为我必须将这些字段声明为它们不应该的 Nullable。我不能使用lateinit,因为这两个字段都是原始长类型。

我认为防止将它们定义为可为空的唯一方法是用一些错误的默认值初始化它们。类似的东西

var number: Long = -1

但这在我看来是错误的。

在 Kotlin 中是否有某种最佳实践来做这样的事情?

提前谢谢你!

【问题讨论】:

    标签: hibernate kotlin properties model notnull


    【解决方案1】:

    问题 1 是您的 @Column var number: Long。如果此字段不为空,则应移至构造函数并在那里初始化:

    class Session(
        @Column
        var number: Long
    ) 
    

    所有其他非空字段也应该这样做,它们应该在对象创建期间被初始化。

    问题 2 是惰性 ID,它始终不为空,但在对象创建期间不知道,并在插入时由 Hibernate 填充。这种情况在kotlin中似乎没有解决。作为一种解决方法,我将 java-base 类用于具有 ID 属性的实体:

    @MappedSuperclass
    public class BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @NonNull
        protected Long id;
    
    }
    
    

    如果您使用javax.annotation.Nonnull 或其他 kotlin 识别的非空注释,该字段将被视为在 kotlin 中声明为非空,您将能够避免在任何地方写 !!

    问题 3 是父子关系,其中应该插入两个实体,然后在关系中相互设置,我们希望在两个表中都存储相关的 ID 以进行查询。这在 java 中是可能的,但在 kotlin 中是不可能的,因为它需要在一侧声明一个可为空的字段,并强制在任何地方使用!!,而从业务案例的角度来看,该字段不应为空。在这种情况下,我使用以下解决方法:

    @Entity class UserAccount(
    
        // user can have multiple accounts
        @field:[
            NotNull
            ManyToOne
        ]
        val user: User,
    
    )
    
    @Entity class User {
    
        // but has always one main account
        @field:[
            OneToOne
            JoinColumn(table = TABLE, name = "main_account_id")
        ]
        private var _mainAccount: UserAccount? = null
    
        override var mainAccount: UserAccount
            get() = _mainAccount!!
            set(value) {
                _mainAccount = value
            }
    
    }
    

    【讨论】:

      【解决方案2】:

      我和你有同样的问题,我通过定义变量可以为空来避免它,然后添加注释@Column(nullable = false),这样你就知道你的实体在数据库中总是有一个id。这是您可以拥有的示例:

      class BaseEntity {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          @Column(unique = true, nullable = false)
          val id: Long? = null
      }
      

      我不知道这是否是最好的解决方案,但我希望它对你有所帮助。

      【讨论】:

      • 我想过,但我想阻止整个?和 !!当我访问这些属性时在代码中。将 hibernate 的 nullable 定义为 false 对此无济于事:/
      • 嗯。尝试保存时还存在字段仍然为空的风险;并且实体类的用户不清楚是否允许 null。
      • 如果尝试保存时该字段仍然为null,那是因为hibernate未能将值放入该变量中,所以问题将出在其他地方。我的意思是,如果 hibernate 正常工作,就不可能有一个 null id,对吧?
      • 技术上是的。但是你仍然需要做所有的空检查操作。例如,你不能做类似 var test: Long = myBaseEntity.id 的事情,即使 id 在技术上不能为空。我想要实现的是你可以做到这一点。我上面可能的解决方案应该解决除 ID 之外的所有属性的问题。我仍在为 id 问题寻找更好的解决方案:D
      • @Materno,当然,我明白你的问题是什么,我刚刚回答了 gidds。 ;) 我想你唯一的解决方案是用默认值初始化你的变量,但是如果你找到一个更好的方法来避免这个问题,请发布一个新的答案,它肯定会帮助我!
      【解决方案3】:

      我发现的另一个解决方案是 kotlins 数据类,但我仍然不确定这是否是一个好方法。

      我的问题中的 Java 类的 Kotlin 版本如下所示:

      @Entity
      @Table(name = "Session_Ids")
      data class SessionId(
      
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          var id: Long?,
      
          @Column
          var number: Long,
      ){
          constructor(userId: Long): this(null, userId)
      }
      

      id 仍然可以为空,否则 hibernate 可能会与具有相同 ID 的现有实体发生冲突。唯一的其他选项是将 0 作为值,但我认为对于未持久化的实体 null 比 0 更可扩展。

      我添加了辅助构造函数以防止为 ID 传递 null。

      你怎么看?

      【讨论】:

        【解决方案4】:

        我将0false"" &c 用于不可为空的字段(null 用于可空的字段)——以最有意义的“默认”值为准。

        (我不确定这是否是最佳做法,但这是迄今为止我见过的最佳选择!)

        当然,在加载现有实体时该值会被覆盖 - 但它可能会在创建新实体时使用,用于您未手动设置的字段。这可能会影响您的选择。

        【讨论】:

        • 我考虑过相同的默认值,但我认为这是某种隐含的知识。我喜欢它更明确,但我仍然不确定这是否可能。