Kotlin: property declaration in hibernate model

Viewed 1438

We have plenty of java applications and are trying to implement our first one in Kotlin. The Question is: what is the best way to initialize the properties of a simple hibernate model?

Let's take the following example in Java:

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

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

    @Column
    protected Long number;
}

Now let's assume that the id and the number in the database can never be null. So after hibernate is done with everything the model will always have a value in the id field and a value in the number field.

How can I do that in Kotlin? I can't initialize them with null because I have to declare those fields as Nullable which they shouldn't be. I can not use lateinit because both fields are primitive long types.

The only way I see to prevent defining them as nullable is to initialize them with some wrong default value. Something like

var number: Long = -1

But that looks wrong to me.

Is there some kind of best practice to do something like this in Kotlin?

Thank you in advance!

4 Answers

I use 0, false, "" &c for non-nullable fields (and null for nullable ones) — whichever makes the most sense as a ‘default’ value.

(I'm not sure if that's best practice, but it's the best option I've seen so far!)

The value will get overwritten when loading existing entities, of course — but it may be used when creating new ones, for fields you don't set manually.  So that may inform your choice.

Problem 1 is your @Column var number: Long. If this field is not null, should be moved to constructor and initialized there:

class Session(
    @Column
    var number: Long
) 

The same should be done for all other non-null fields, they should be initialized during object creation.

Problem 2 is lazy ID which is always not null, but is not known during object creation, and is filled by Hibernate on insert. This case seems to be not solved in kotlin. As a workaround I use java-base class for entities with ID property introduced there:

@MappedSuperclass
public class BaseEntity {

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

}

If you use javax.annotation.Nonnull or other non-null annotation recognized by kotlin, this field will be seen as declared non-null in kotlin and you'll be able to avoid writing !! everywhere.

Problem 3 is parent-child relation where two entities should be inserted and then set to each other in a relation, and we want to have stored related ID-s in both tables for querying purposes. This is possible in java, but not in kotlin, because it requires to declare a field on one side nullable, and forces to use !! everywhere, while from the business case perspective this field should be not null. In such a scenario I use the following workaround:

@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
        }

}

Another solution I found are kotlins data classes but I'm still not sure if this is a good way.

The Kotlin version of the Java class in my question would look like this:

@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)
}

The id still is nullable because otherwise hibernate may have conflicts with existing entities with the same IDs. The only other option would be 0 as a value but I think for an unpersisted entity null is more expactable than 0.

I added the secondary constructor to prevent passing null for the ID.

What do you think about that?

I had the same problem as you, and I avoided it by define the variable has nullable, then add the annotation @Column(nullable = false), so that you know your entity will always have an id in the database. Here's an example of what you could have :

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

I don't know if it's the best solution, but I hope it helped you.

Related