Skip to content

HHH-19328 introduce @NaturalIdClass #9982

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

gavinking
Copy link
Member

@gavinking gavinking commented Apr 8, 2025

A VERY rough implementation of @NaturalIdClass.

This is turning out significantly harder and messier than expected.

[Please describe here what your change is about]


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


https://hibernate.atlassian.net/browse/HHH-19328

@gavinking gavinking requested a review from sebersole April 8, 2025 14:42
@gavinking gavinking marked this pull request as draft April 8, 2025 14:50
@gavinking
Copy link
Member Author

Note that it's going to take some work to finish this off, so it's not for 7.0.


public void setNaturalId(Component naturalId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the pattern of id-class, this natural-id-class should only be considered in the "non-aggregated composite" case (aka, not in the case of simple, "single attribute" natural-ids). Is that what happens here? Hard to tell.

Based just on the naming here, I'd assume you wrap even "single attribute" (aka, @Basic and @Embedded) as a component? That seems wrong. This could be SimpleValue and cover all 3 cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assume you wrap even "single attribute" (aka, @basic and @Embedded) as a component?

Well, wrap the @Basic case. The @Embedded case is already a component ofc.

Copy link
Member Author

@gavinking gavinking May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea there (and it's definitely not set in stone) was that this wrapper object could be passed to find() and used to distinguish the natural id, even in the non-composite case, for example:

record Isbn(String isbn) {}

@Entity
@NaturalIdClass(Isbn.class)
class Book {
    @GeneratedValue @Id String uuid;
    @NaturalId Isbn isbn

    ...
}

So that you could write:

Book bookWithUuid = session.find(Book.class, uuid);
Book bookWithIsbn = session.find(Book.class, new Isnb(isbn));

Copy link
Member

@sebersole sebersole May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I get that. That's more or less how the @NaturalId @Embedded case works now - Isbn being your embeddable. More I am talking about, e.g.:

class Book {
    @GeneratedValue @Id String uuid;
    @NaturalId String isbn;
    ...
}

But it seems like you are trying to solve a slightly different problem and essentially overload find. Not quite sure how I feel about that. I worry specifically about confusion:

interface EntityManager {
    /**
     * Find by primary key.
     * ...
     */
    <T> T find(Class<T> entityClass, Object primaryKey);
}

Not to mention, imo, its terribly unclear what happens in the case I mentioned earlier where both the id and natural-id are the same class. But I think you are thinking you can only use this "find override" when there is explicitly a @NaturalIdClass.

In your example, it seems strange to tell Hibernate about Isbn being the natural-id class twice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah shit, sorry, you're right, my bad, that's not what I mean to write.

What I mean to write was this:

record Isbn(String isbn) {}

@Entity
@NaturalIdClass(Isbn.class)
class Book {
    @GeneratedValue @Id String uuid;
    @NaturalId String isbn

    ...
}

I didn't mean to use Isbn is the field type.

That said, I guess I think it the following should also work:

@Embeddable
record Isbn(String isbn) {}

@Entity
class Book {
    @GeneratedValue @Id String uuid;
    @NaturalId Isbn isbn

    ...
}

And in both cases you could use the Isbn class to "overload" find(), as you say.

@sebersole
Copy link
Member

Really like the idea. 👍

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

Successfully merging this pull request may close these issues.

2 participants