4

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!

Materno
  • 343
  • 8
  • 20

4 Answers4

0

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.

gidds
  • 16,558
  • 2
  • 19
  • 26
  • I thought about the same default values but I think this is some kind of implicit knowledge. I like it more explicit but I'm still not sure if that is possible. – Materno Jan 08 '19 at 15:55
0

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
        }

}
Lukasz Frankowski
  • 2,955
  • 1
  • 31
  • 32
-1

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?

Materno
  • 343
  • 8
  • 20
-2

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.

Ctorres
  • 468
  • 6
  • 19
  • I thought about that but I want to prevent the whole ? and !! in the code when I access those properties. Defining nullable as false for hibernate would not help for that : / – Materno Jan 08 '19 at 15:53
  • Mmm. There's also the risk of fields still being null when trying to save; and it's not clear to the user of the entity class whether null is allowed or not. – gidds Jan 08 '19 at 16:08
  • If the field is still null when trying to save, it's because hibernate failed to put a value in this variable, so the problem will be somewhere else. I mean, if hibernate works properly, there's no way to have a null id, right? – Ctorres Jan 08 '19 at 16:19
  • Technically yes. But you still have to do all the null check operations. For example you can not do something like var test: Long = myBaseEntity.id even if the id technically can not be null. What I want to achieve is that you can do exactly that. My possible solution above should fix that for all properties except the ID. I'm still looking for a better solution for the id problem :D – Materno Jan 08 '19 at 16:29
  • @Materno, sure, I understood what your problem is, I just answered to gidds. ;) I guess your only solution is to initialize your variables with default values, but if you find a better way to avoid the problem, please post an new answered, it will help me for sure! – Ctorres Jan 08 '19 at 16:39
  • I already posted an answer, that one with the data class below :) I'm still not sure if this is good or good enough though :D Feel free to post what you think about that :) – Materno Jan 08 '19 at 16:53
  • I chose to use traditional classes for my project, but I don't remember why, but I think I read somewhere they were sometimes buggy with hibernate. I don't know if it's true, and maybe it was, but JetBrain corrected it, I don't know. But I think if you can use data classes you should do it, because they're designed to represent data, and that's exactly what we want to do here. – Ctorres Jan 09 '19 at 08:42
  • Why does your data class solution resolve the issue? Your primary contructor is still accessible, and you still have to check if id is null or not when you want to use it. I mean, id is still a nullable attribute. So your problem is still here, right? Or maybe I missed something? – Ctorres Jan 09 '19 at 08:48
  • The ID somehow still has the problem, right. But I think (correct me if I'm wrong) that this is not a big deal because every entity I create just does not have an ID before I persist it with hibernate. So technically and logically the ID can be nullable. The big improvement for me seems to be that all the other fields don't have to be nullable like the "number field" in my example entity. They just have to be assigned via the constructor. I don't have to declare it as nullable and I don't have to declare it with some kind of "default" value like in the other answer. – Materno Jan 09 '19 at 09:29
  • You will have the same result as if you used my solution, I guess it's a matter of taste. But after reading all these solutions, yours seems to have the cleaner, because there's no "conflict" between what is written in the hibernate annotation and how the variable is declared, like in mine, and you don't have to assign fake value to the id attribute, as gidds suggested. If you encounter any difficulties due to the use of data classes, please let me know in comments section of your answer, I'll be curious to see if there's something wrong with Hibernate and data classes. Thank you ;) – Ctorres Jan 09 '19 at 13:09