Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mipo256 opened this issue Jan 30, 2025 · 5 comments
Labels
enhancement New feature or request

Comments

@mipo256
Copy link

mipo256 commented Jan 30, 2025

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.

@mipo256 mipo256 added the enhancement New feature or request label Jan 30, 2025
@mipo256 mipo256 changed the title [Feature Request] - <title> Add a way to differentiate between nullability on the client side vs nullability on the database side Jan 30, 2025
@storympro
Copy link

I have a large program written with jimmer and I have encountered various situations, but jimmer does not need what you wrote. Everything works there with inserting nested elements. Check the ID generation on the database side.

@storympro
Copy link

You need to understand how databases work, jimmer is a wrapper that syncs with your database settings and allows you to easily interact with them. If you can't design the database correctly, then no ORM will help you here.

@storympro
Copy link

You are saving incorrectly. It is done through Draft.$ ... https://babyfish-ct.github.io/jimmer-doc/docs/mutation/save-command/input-dto/null-handling/#null-related-issues-in-data-input Filling in the ID is optional even if it is "long"

@runnableAir
Copy link
Contributor

Jimmer's Input DTOs can help you.

Input DTOs provide a solution to your problem by allowing properties to be nullable on the client side (Input DTO) while ensuring they are non-null on the database side (Entity). With Input DTOs, you can handle the id property (nullable on the client side) in several ways:

  • Omit the id property:
input BookInput {
    title
}
  • Exclude the id property explicitly:
input BookInput {
    #allScalars(this) 
    -id
}
  • Make the id property nullable using ?:
input BookInput {
    title
    id?
    createdAt?
}

Key Points:

  • If a property is not declared in the Input DTO but exists in the entity, it will be ignored when converting to an entity.
  • If a property is non-null in the entity but has a null value in the Input DTO, it will also be ignored during conversion.

This behavior allows you to have nullable properties on the client side while ensuring they are non-null on the database side.

For more details, please see DTO Language / 7.Nullability and DTO Language / 3.2 input-specific functionalities.

@babyfish-ct
Copy link
Owner

Hi, sorry for late reply because of 8-days Chinese new year.

Yes, @storympro and @runnableAir are right, the InputDTO can help you.

  1. If Id is auto-generated, Input DTO maps the id to nullable Long, you can add ! to let it be non-null long

  2. Otherwise, Input DTO maps the id to non-null property long, you can add ? to let it be nullable Long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants