Skip to content

Add a way to differentiate between nullability on the client side vs nullability on the database side #904

Open
@mipo256

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:

  1. Value cannot be null on the database side
  2. 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.

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions