Skip to content

Add support for composite ids #1957

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

Closed
wants to merge 8 commits into from
Closed

Add support for composite ids #1957

wants to merge 8 commits into from

Conversation

schauder
Copy link
Contributor

Entities may be annotated with @Id and @Embedded, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom BeforeConvertCallback to set the id for new aggregate.

For an entity with @Embedded id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of + _ + .
E.g. the back reference to a Person entity, with a composite id with the properties firstName and lastName will consist of the two columns PERSON_FIRST_NAME and PERSON_LAST_NAME.
This holds for directly referenced entities as well as List, Set and Map.

Closes #574

@schauder schauder requested a review from mp911de December 12, 2024 13:20
@schauder schauder force-pushed the issue/574-composite-id branch from d2967c0 to b931dcf Compare December 23, 2024 10:50
@mp911de mp911de self-assigned this Jan 30, 2025
schauder added a commit that referenced this pull request Mar 13, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
schauder added a commit that referenced this pull request Mar 13, 2025
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Mar 13, 2025
Adds support for id generation by sequence as part of a composite id.
Added a proper test for sorting by composite id element.
Added a stand in test for projection by composite id element.
The latter does not test the actual intended behaviour since projection don't work as intended yet.

See #1821
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Mar 13, 2025
Removed unused assignments.

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from b931dcf to 5527960 Compare March 13, 2025 11:55
Copy link
Member

@mp911de mp911de left a comment

Choose a reason for hiding this comment

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

This is quite a PR with a lot of changes. I did a review pass on it capturing most impressions as comments. This PR introduces a lot of accidental complexity making the actual changes much more difficult to understand. I think rewriting some parts to simpler forms that make concepts more explicit (instead of implicit ifs or for-loops that do something) helps to understand the code.

Assertions that assert more than three items in a row exhaust my capability to understand these, also asserting tuples might technically work but that leads to test code that is much more difficult to understand than what is the actual functionality tested. If we continue that way, you'll be the sole person that is able to maintain the project.

I tried to make suggestions as concise as possible for improvements to reduce conceptual (and code) duplications.

@@ -403,6 +388,29 @@ public <T> T getPropertyValue(RelationalPersistentProperty property) {
return (T) delegate.getValue(aggregatePath);
}

private Identifier constructIdentifier(AggregatePath aggregatePath) {

Identifier identifierToUse = this.identifier;
Copy link
Member

Choose a reason for hiding this comment

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

This looks pretty similar to what JdbcIdentifierBuilder is doing. There's a high likelihood of duplicating code. Please check what parts could be refactored to avoid duplication of concepts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -449,7 +457,7 @@ public boolean hasNonEmptyValue(RelationalPersistentProperty property) {
return delegate.hasValue(toUse);
}

return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfos().any().alias());
Copy link
Member

Choose a reason for hiding this comment

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

Related: ColumnInfos methods should be documented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


/**
* Constructs a function for constructing a where condition. The where condition will be of the form
* {@literal <columns> IN :bind-marker}
Copy link
Member

Choose a reason for hiding this comment

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

lt/gt signs conflict with Javadoc. These should be escaped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is escaped by the @literal if I'm not mistaken.

From the What's new announcement, when it was introduced:

New tags {@literal} and {@code} The {@literal} tag denotes literal text. The enclosed text is interpreted as not containing HTML markup or nested javadoc tags. For example, the doc comment text: {@literal ac} displays in the generated HTML page unchanged: ac -- that is, the is not interpreted as bold.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IntelliJs Javadoc renderer seems to agree with me.

Note that the comment above does not get rendered correctly. See the source for what it is trying to say.


/**
* Constructs a function for constructing a where. The where condition will be of the form
* {@literal <column-a> = :bind-marker-a AND <column-b> = :bind-marker-b ...}
Copy link
Member

Choose a reason for hiding this comment

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

See above for Javadoc lt/gt chars.

this.columns = new Columns(entity, mappingContext, converter);
this.queryMapper = new QueryMapper(converter);
this.dialect = dialect;

inCondition = inCondition();
Copy link
Member

Choose a reason for hiding this comment

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

Why are these modelled as functions? Generally, functions are neat, however these introduce another indirection to already hard-to-read code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to methods returning conditions, and method references

record CompositeId(String one, String two) {
}

record DummyEntityWithCompositeId(@Id @Embedded.Nullable CompositeId id, String name) {
Copy link
Member

Choose a reason for hiding this comment

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

Dummy 🙈

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


assertSoftly(softly -> {

softly.assertThat(path("second").append(path()).toDotPath()).isEqualTo("second");
Copy link
Member

Choose a reason for hiding this comment

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

There's so much to read in these test style that the actual test fixture and test code becomes invisible.

Please use much simpler assertions, otherwise I don't understand what is happening here.

Applies also for many other places in this project.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to improve the readability of the tests with a custom assertion.


import org.junit.jupiter.api.Test;

class TupleExpressionUnitTests {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

* Tests for rendering joins.
*/
@Nested
class JoinsTests {
Copy link
Member

Choose a reason for hiding this comment

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

Are these tests actually invoked during mvn verify?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Verified by adding a failing test temporarily.

[[entity-persistence.embedded-ids]]
=== Embedded Ids

Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether we could simplify our detection. If a simple type is annotated with @Id, we consider it a simple identifier. Any non-simple type annotated with @Id is automatically considered an embedded/composite Id without the need to use @Embedded. @Embedded raises the question of Embedded.onEmpty(). Being able to provide more detail via @Embedded is a nice detail.

It would be also neat to have Mapping Annotation Overview explaining that @Id can be simple or embedded/composite identifiers along with embedded Id examples. So far, @Id is only used with simple types in code samples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree on the documentation part and will tackle that.

I don't like the idea of automatically turning an entity into an embedded when an @Id annotation is placed on it. If users want this kind of behavior they could create their own meta annotation.

Also the question of Embedded.onEmpty() gets risen by an implicit conversion to an embedded also.

schauder added a commit that referenced this pull request Apr 2, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
schauder added a commit that referenced this pull request Apr 2, 2025
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Apr 2, 2025
Adds support for id generation by sequence as part of a composite id.
Added a proper test for sorting by composite id element.
Added a stand in test for projection by composite id element.
The latter does not test the actual intended behaviour since projection don't work as intended yet.

See #1821
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Apr 2, 2025
Removed unused assignments.

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from 099c890 to db06331 Compare April 2, 2025 11:09
@mp911de mp911de force-pushed the main branch 2 times, most recently from 571fd96 to 1f2e694 Compare April 9, 2025 13:29
schauder added a commit that referenced this pull request Apr 16, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
schauder added a commit that referenced this pull request Apr 16, 2025
Adds support for id generation by sequence as part of a composite id.
Added a proper test for sorting by composite id element.
Added a stand in test for projection by composite id element.
The latter does not test the actual intended behaviour since projection don't work as intended yet.

See #1821
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Apr 16, 2025
Removed unused assignments.

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from 8228915 to 404730f Compare April 16, 2025 11:23
schauder added a commit that referenced this pull request Apr 25, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
schauder added a commit that referenced this pull request Apr 25, 2025
Adds support for id generation by sequence as part of a composite id.
Added a proper test for sorting by composite id element.
Added a stand in test for projection by composite id element.
The latter does not test the actual intended behaviour since projection don't work as intended yet.

See #1821
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Apr 25, 2025
Removed unused assignments.

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from 22b23d3 to f4cbeca Compare April 25, 2025 12:34
schauder added a commit that referenced this pull request May 6, 2025
The major ones are:

* directly construct joins
* remove multiple places of duplication
* naming
* documentation

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from 8d6a98a to ffab841 Compare May 6, 2025 10:04
@schauder schauder changed the base branch from main to 4.0.x May 6, 2025 10:05
@schauder
Copy link
Contributor Author

schauder commented May 6, 2025

Ok, finally ready for another round of review.

mp911de pushed a commit that referenced this pull request May 26, 2025
The major ones are:

* directly construct joins
* remove multiple places of duplication
* naming
* documentation

Original pull request #1957
See #574
@mp911de mp911de force-pushed the issue/574-composite-id branch from 0f81a51 to be4718e Compare May 26, 2025 14:36
@mp911de mp911de added this to the 4.0 M4 (2025.1.0) milestone May 27, 2025
schauder added a commit that referenced this pull request Jun 10, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
schauder added a commit that referenced this pull request Jun 10, 2025
Adds support for id generation by sequence as part of a composite id.
Added a proper test for sorting by composite id element.
Added a stand in test for projection by composite id element.
The latter does not test the actual intended behaviour since projection don't work as intended yet.

See #1821
Original pull request #1957
See #574
schauder added a commit that referenced this pull request Jun 10, 2025
Removed unused assignments.

Original pull request #1957
See #574
schauder added a commit that referenced this pull request Jun 10, 2025
The major ones are:

* directly construct joins
* remove multiple places of duplication
* naming
* documentation

Original pull request #1957
See #574
@schauder schauder force-pushed the issue/574-composite-id branch from 6d695f6 to 67aa64f Compare June 10, 2025 14:14
schauder added a commit that referenced this pull request Jun 10, 2025
Turns out the concept of base path becomes superfluous.

Original pull request #1957
See #574
mp911de and others added 4 commits June 16, 2025 14:21
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
Introduce ColumnInfos.reduce(…) operation.
The major ones are:

* directly construct joins
* remove multiple places of duplication
* naming
* documentation

Original pull request #1957
See #574
mp911de pushed a commit that referenced this pull request Jun 16, 2025
Turns out the concept of base path becomes superfluous.

Original pull request #1957
See #574
@mp911de mp911de force-pushed the issue/574-composite-id branch from 67aa64f to c085319 Compare June 16, 2025 14:30
mp911de and others added 4 commits June 16, 2025 16:31
Add benchmarks. Performance refinements.
Turns out the concept of base path becomes superfluous.

Original pull request #1957
See #574
Refactor duplications, introduce QueryAssert.
@mp911de mp911de force-pushed the issue/574-composite-id branch from c085319 to 45d2ee1 Compare June 16, 2025 14:31
mp911de pushed a commit that referenced this pull request Jun 17, 2025
Entities may be annotated with `@Id` and `@Embedded`, resulting in a composite id on the database side.
The full embedded entity is considered the id, and therefore the check for determining if an aggregate is considered a new aggregate requiring an insert or an existing one, asking for an update is based on that entity, not its elements.
Most use cases will require a custom `BeforeConvertCallback` to set the id for new aggregate.

For an entity with `@Embedded` id, the back reference used in tables for referenced entities consists of multiple columns, each named by a concatenation of <table-name> + `_` + <column-name>.
E.g. the back reference to a `Person` entity, with a composite id with the properties `firstName` and `lastName` will consist of the two columns `PERSON_FIRST_NAME` and `PERSON_LAST_NAME`.
This holds for directly referenced entities as well as `List`, `Set` and `Map`.

Closes #574
Original pull request #1957
mp911de added a commit that referenced this pull request Jun 17, 2025
Introduce ColumnInfos.reduce(…) operation.

See #574
Original pull request #1957
mp911de pushed a commit that referenced this pull request Jun 17, 2025
The major ones are:

* directly construct joins
* remove multiple places of duplication
* naming
* documentation

See #574
Original pull request #1957
mp911de added a commit that referenced this pull request Jun 17, 2025
Add benchmarks. Performance refinements.

See #574
Original pull request #1957
mp911de pushed a commit that referenced this pull request Jun 17, 2025
Turns out the concept of base path becomes superfluous.

Original pull request #1957
See #574
mp911de added a commit that referenced this pull request Jun 17, 2025
Also, refactor duplications, introduce QueryAssert.

See #574
Original pull request #1957
@mp911de mp911de added the type: enhancement A general enhancement label Jun 17, 2025
@mp911de
Copy link
Member

mp911de commented Jun 17, 2025

That's merged and polished now.

@mp911de mp911de closed this Jun 17, 2025
@mp911de mp911de deleted the issue/574-composite-id branch June 17, 2025 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Composite Keys using @Embeded and @Id
4 participants