Description
Reason
From the docs:
Jimmer entities are very sensitive to nullability:
For Kotlin, use language's own nullity.
For Java:
Primitives like boolean, char, byte, short, int, long, float, double are non-null.
Boxed types like Boolean, Character, Byte, Short, Integer, Long, Float, Double are nullable.
Other types are non-null by default. Add @TNullable to allow null.
This is leads to the problem. Let's say I have Java an entity like this:
@Entity
public interface Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@TNullable
long id();
// other stuff
}
As you see, It has ID as an identity column in the database. The problem is that this property has the NOT NULL
constraint on the database side, which makes total sense, I guess, for everybody. That means, that if we would use jimmer.database-validation-mode: ERROR
(which by the way is a great feature), we're forced to use a primitive type like long
.
The problem is, that we cannot just leave it as it is, because if we would try to save an entity of the type Book
where we do not provide the ID in the DTO from which the Book
is constructed (because we have an identity column), we would get an error:
java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because "this.id" is null
at io.learnbydoing.dto.BookView.lambda$toEntity$0(BookView.java:138) ~[main/:na]
at org.babyfish.jimmer.runtime.Internal.modifyDraft(Internal.java:173) ~[jimmer-core-0.9.37.jar:na]
at org.babyfish.jimmer.runtime.Internal.lambda$produce$0(Internal.java:28) ~[jimmer-core-0.9.37.jar:na]
at org.babyfish.jimmer.runtime.Internal.usingDraftContext(Internal.java:102) ~[jimmer-core-0.9.37.jar:na]
at org.babyfish.jimmer.runtime.Internal.produce(Internal.java:26) ~[jimmer-core-0.9.37.jar:na]
at io.learnbydoing.models.BookDraft$Producer.produce(BookDraft.java:123) ~[main/:na]
at io.learnbydoing.models.BookDraft$Producer.produce(BookDraft.java:119) ~[main/:na]
at io.learnbydoing.dto.BookView.toEntity(BookView.java:137) ~[main/:na]
at io.learnbydoing.repository.BookRepository.saveBook(BookRepository.java:40) ~[main/:na]
The actual saving code that executes save()
is this (simplified):
public void saveBook() {
BookView bookView = new BookView();
bookView.setCreatedAt(Instant.now());
bookView.setTitle("The Art Of Programming");
var author = new TargetOf_author();
author.setId(1L);
bookView.setAuthor(author);
sqlClient.save(bookView.toEntity()); // notice we did not set the ID for the Book itself
}
And we really cannot do much. We cannot use Long
as a wrapper class, because the column is indeed NOT NULL
, and Jimmer would complain. Adding carious annotations like @TNullable
etc. does not really help at all. So we hit a conundrum.
Description
Already explained above.
Existing solutions
So, my proposal is to do the following: We need to distinguish between 2 things:
- Value cannot be null on the database side
- Value cannot be null on the database side but can on the client side (for any autogenerated column for instance).
For primitives/wrappers case, we can use a wrapper class and also apply an annotation (a newly created one perhaps) that says that this column is possible to be nullable on the client side, but on the database side it is never null. So that the jimmer schema checker can understand that the use of the wrapper here is appropriate.