When creating JPA entity model classes, do you gen...
# spring
l
When creating JPA entity model classes, do you generally make your ID field nullable? I’ve tried both ways and I have to do annoying shenanigans either way. (It’s a UUID field that should be populated by the DB for me, so when writing a new record it doesn’t have an ID yet… but making it nullable means that every other time I look at a loaded object, I have to deal with the ID being nullable, even though it’s impossible for that to be the case.)
s
Does the UUID have to be generated by the DB though? You could have it generated on the code level like so:
val id: UUID = UUID.randomUUID()
and not worry about nullability no more. It should be random without any special strategies anyways, so I cannot think of any issues with it. That's also the approach I was successfully using in previous projects. And possibly before you ask, I would use nullable
@Version
field for clear indication if entity was persisted (and also how many times it was updated)
👍 1
l
“nullable @Version”?
I… suppose it technically doesn’t have to be, but that’s how every other entity in this project does it, so I followed suit. (Project is 2-3 years old, I joined a few months ago.)
s
Yes, meaning
Copy code
@Version
val version: Long? = null
field will be null upon creation, but when persisting into the DB it will be automatically populated by Hibernate (and then incremented along each update)
l
We already have a historical versioning mechanism in place. That’s not a concern at the moment.
s
Understand, I was just mentioning it as a possible follow-up to the question "how do you know if the entity was persisted already if ID is not nullable?"
👍 1
That being said, in case you would eventually want to go with non-nullable ID field, then
@Version
field could definitely come in handy considering how Spring Data JPA checks if the entity is new: https://docs.spring.io/spring-data/jpa/reference/jpa/entity-persistence.html#jpa.entity-persistence.saving-entities.strategies
l
Hm. So changing it to a non-nullable ID but not adding a @Version could cause problems, it sounds like.
s
Yes, but if you would like to avoid
@Version
field for some reason, you may use one of other strategies described there (if they're applicable in your case), for example implementing
Persistable
. However, in my opinion adding
@Version
is still worth it in most cases anyways, as it also allows Hibernate to handle optimistic locking mechanism
l
I’m just not sure how that will interact with other parts of our application’s data layer. We have a fair bit of stuff setup in a parent class. (Again, I didn’t set this up. 🙂 )
Hm. Although… this particular entity doesn’t use the parent class….
s
Of course, every code base is different and every change needs to be introduced with some consideration, that's just my take on the matter 🙂
l
But that’s the only realistic way to avoid the null?
j
generating your own ID is basically the only way to avoid the null, yes
(the version stuff is somewhat separate)
l
Hrm. But doing that without also doing @Version would cause issues for saving records, no? JPA wouldn’t know if it’s a new record or existing?
j
Copy code
@GeneratedValue(strategy = GenerationType.UUID)
that tells Hibernate not to use that particular check
l
Here’s what I have now:
Copy code
@Entity
@Table(name = "my_entity")
class MyEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(columnDefinition = "UUID NOT NULL default uuid_generate_v4()")
    var id: UUID? = null,
)
(Though @Column is ignored, since we’re using Flyway with manual SQL files to actually setup the database. No idea why.)
j
right, so you could switch that to
UUID
instead of
AUTO
and remove the null
l
How could I then create a new object? I’d have to pass in a new UUID manually, no? Or… I suppose I could move the ID out of the constructor?
j
you could default it to
UUID.randomValue
although Hibernate might throw that away
this is why I don't use JPA 🙂 (I know you don't have that option)
it's just not designed for Kotlin
l
I so wish I had that option. 😅 This project is all-in on Spring-all-the-things. Company policy, presumably.
j
I love Spring/Kotlin, it's great, but JPA is another matter
l
I’m new to all of it this year, after 25 years doing PHP. 😅 So far, I’m quite liking Kotlin, hating basically everything inherited from Java (including Spring, JPA, and Gradle).
s
In my opinion it's safer to move
id
out of constructor anyways. I would recommend this kind of simplified class structure:
Copy code
@Entity
@Table(name = "table_name")
class EntityName(
 [... class fields]
) {
    @Id
    val id: UUID = UUID.randomUUID()

    @Version
    val version: Int? = null

    [...]
}
Since
id
is assigned on the code level, there are no other annotations as
@GeneratedValue
required (the value is already there, using
@GeneratedValue
would tell Hibernate to possibly generate it again, and reassign it, which is wrong and also redundant), just
@Id
is enough. What's obtained by
UUID.randomUUID()
will then be inserted into the DB as primary key.
id
property being a
val
declared and assigned within class body makes you sure that, firstly it cannot be reassigned after object instantiation, secondly it's always populated consistently (no risk of passing "wrong" value to the constructor), and lastly you save yourself from nullability hassle. When you want to create new instance of an entity (hence new record in the DB), you would just invoke constructor without worrying about ID at all. And when you read the row from the DB, Hibernate will instantiate appropriate object setting
id
field to the value read from DB. Hibernate uses reflection for such mechanisms, so you don't have to make field re-assignable on the code level. With this approach, you pretty much don't have to worry about `id`s, `version`s etc yourself, Hibernate does things for you
👍 1
j
In this case, how does hibernate know if it's insert or update?
s
By using previously mentioned strategies: https://docs.spring.io/spring-data/jpa/reference/jpa/entity-persistence.html#jpa.entity-persistence.saving-entities.strategies (that's what also makes
@Version
useful here)
👍 1
l
Thanks, @Szymon Jeziorski. I’ll give that a try when I get the chance. I’ll probably skip the @Version for now just to avoid messing with the schema, and because I don’t believe we’re using it anywhere else in this app so I don’t want to add it unilaterally.
j
Disclaimer: I didn't read the 31 prior messages on this thread Considering this is the spring channel and spring uses the id field to determine if something is an insert or update when you call repository.save, I'd say if you're on this channel then you want it nullable https://github.com/spring-projects/spring-data-commons/blob/ec3ba10954e7fab4b5f20c[…]ork/data/repository/core/support/AbstractEntityInformation.java
s
@Jacob Id field is not the only source of knowledge for Spring to know whether entity is new or not. What you've provided is just one generic implementation of
EntityInformation
within Spring Data Commons. As a reference, here's one concrete implementation for JPA using
@Version
attribute to resolve whether entity is new: https://github.com/spring-projects/spring-data-jpa/blob/018c833afd2219fb1598bae784[…]/data/jpa/repository/support/JpaMetamodelEntityInformation.java And here's documentation reference listing resolve strategies: https://docs.spring.io/spring-data/jpa/reference/jpa/entity-persistence.html#jpa.entity-persistence.saving-entities.strategies Thanks for the reply, but this was already discussed with here, with references to documentation. Please invest a minute to skim through the messages next time