Skip to content

HHH-19542 embeddable property order #10356

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
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

itsmoonrack
Copy link

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

The change is about using either the secondary table of a component, by looking at the first @column available (to avoid nested component to be picked up), or fall-back on the property holder table if no secondary table is defined.

At first i tried to change the property order, which worked, but when doing the test against java record, it messed up the constructor order, so i had to find another strategy. Since we cannot change the property orders, the idea is to set the table directly on the component so we avoid changing it back in the ComponentPropertyHolder.

See jira for detailed analysis.

I am a bit concerned about naming convention in the lookup method getSecondaryTable if we have prefix, will it still work here ? Also for the lookup of secondary table best I could find was from the Column definition itself line 1011 of the EmbeddableBinder. Maybe there is a more direct route ?

I did not run the full test suit but only my two tests validate, I will let jenkins do the full suit.


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.


…at the first @column available (to avoid nested component to be picked up), or fall-back on the property holder table if no secondary table is defined.
@gavinking gavinking changed the title Hhh 19542 embeddable property order HHH-19542 embeddable property order Jun 17, 2025
@itsmoonrack
Copy link
Author

ok there is lot of tests failing due to this change, i'm working on fixing them

@itsmoonrack
Copy link
Author

all tests are passing on local, would a maintainer accept the process in jenkins so it can run ?

@itsmoonrack
Copy link
Author

What is the process now (this is my first contribution). Should I wait for a design review ? Do we just merge on master ? What if I want to backport this on the 6.x release lifecycle branches ?

Thank you for your time

Copy link
Member

@mbellade mbellade left a comment

Choose a reason for hiding this comment

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

Hello @itsmoonrack, thank you very much for your contribution, left an initial comment.

… which in case of a nested component, was null.
… which in case of a nested component, was null.
@@ -221,23 +221,21 @@ public String getEntityName() {

@Override
public void addProperty(Property property, MemberDetails attributeMemberDetails, AnnotatedColumns columns, ClassDetails declaringClass) {
Copy link
Author

Choose a reason for hiding this comment

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

since we are not using columns at all i wonder if in term of design i should keep the logic in this method at all here ? let me know

Copy link
Member

Choose a reason for hiding this comment

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

Hey @itsmoonrack, this solution looks much better!

There's one last thing that makes me wonder: if the property.getValue().getTable() method returns the expected result (i.e. the @AttributeOverride's table for nested embeddables), why was it not the case in the AnnotatedColumns?

Could you please have a look at the binders logic and check if there might be something missing there? It would be great for consistency to understand why this is happening as well.

Copy link
Author

@itsmoonrack itsmoonrack Jun 20, 2025

Choose a reason for hiding this comment

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

Hey @mbellade, thanks !

That's indeed a good question. From what I have seen earlier the property is already constructed from those columns (higher in the hierarchy call). So in effect the property contains the same informations as the AnnotatedColumns.

What I found however, is that in the special event of an embeddable in an embeddable (our nested use case), that the columns were missing (columns = null), effectively skipping the whole logic of replacing a table. I have ran the full test suit to ensure non regression and it passed. So conceptually I am a bit bugged about this AnnotatedColumns versus Property, for me, after analysing, the later contains the former;

I thought about doing the following,

columns != null ? columns.getTable() : property.getValue().getTable()

But eventually rolled back as I did not find any value of doing so.

In both cases the full test suit passed.

Please correct me if I'm wrong or if I missed something obvious. We can put back the proposed line in this comment if we are more confident with it.

Copy link
Author

Choose a reason for hiding this comment

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

Oh, you mean why the columns were empty in the first place ?

That's a good question, I will investigate this evening.

Copy link
Author

@itsmoonrack itsmoonrack Jun 20, 2025

Choose a reason for hiding this comment

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

@mbellade I tried to pass the columns to the EmbeddableBinder.createEmbeddedProperty to see what happen. Now the AnnotatedColumns are correctly passed, however the getTable() resolve to the component.getTable(), at this stage, the Person component getTable() returns UserEntity, while the property.getValue().getTable() returns Person. The later being correct.

So we cannot blatantly passes the columns to the PropertyBinder.

I am also reading that when we have an underlying value we use the makePropertyAndBind(), instead of the makePropertyValueAndBind() //used when the value is provided and the binding is done elsewhere

Copy link
Author

@itsmoonrack itsmoonrack Jun 28, 2025

Choose a reason for hiding this comment

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

the full test suit passes, and i also make this work:

@Embeddable
public record Person(
	FullName fullName,
	@Column(table = "Person")
	Integer age) {

}

@Embeddable
public record FullName(
	@Column(table = "Person")
	String firstName,
	@Column(table = "Person")
	String lastName) {

}

Copy link
Author

@itsmoonrack itsmoonrack Jun 28, 2025

Choose a reason for hiding this comment

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

while this work, i feel i should find a way to pass columns directly, the solution proposed here is actually messy.

My idea is - just like the AnnotatedJoinColumns works - create a "virtual column parent AnnotatedColumns for the component. That way the checkPropertyConsistency will do its work

Copy link
Author

@itsmoonrack itsmoonrack Jun 28, 2025

Choose a reason for hiding this comment

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

I have changed the approach completely using my idea but improve to check on the fly and i am pretty happy with the results now.

	@Entity
	@Table(name = "UserEntity")
	@SecondaryTable(name = "Person")
	public static class UserEntity {
		@Id
		@GeneratedValue
		private Integer id;
		@AttributeOverride(name = "fullName.firstName", column = @Column(table = "Person"))
		@AttributeOverride(name = "fullName.lastName", column = @Column(table = "Person"))
		@AttributeOverride(name = "age", column = @Column(table = "Person"))
		private Person person;

		public UserEntity(
				final Person person) {
			this.person = person;
		}

		protected UserEntity() {

		}
	}

	@Embeddable
	public record Person(
			FullName fullName,
			Integer age) {

	}

	@Embeddable
	public record FullName(
			String firstName,
			String lastName) {

	}

Checked that every combination of mapping works, and when remove one override or one mapping for 1 or 2 fields, it fails as excepted.

Copy link
Author

Choose a reason for hiding this comment

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

@gavinking, @mbellade, its in your hands now :)

Copy link
Author

Choose a reason for hiding this comment

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

@beikov you were right, I found out that the last version did not threw an exception when the columns did not matched. its now resolved

… which in case of a nested component, war null.
…oes but the table is different it will be catch earlier, if it does not it mean the mapping is not correct
@itsmoonrack itsmoonrack requested a review from gavinking June 28, 2025 15:11
…oes but the table is different it will be catch earlier, if it does not it mean the mapping is not correct
…oes but the table is different it will be catch earlier, if it does not it mean the mapping is not correct
…oes but the table is different it will be catch earlier, if it does not it mean the mapping is not correct
Copy link
Member

@beikov beikov left a comment

Choose a reason for hiding this comment

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

Can you please add a negative test similar to org.hibernate.orm.test.mapping.collections.SortAndOrderTests that verifies some mapping variants that use multiple tables in an embeddable really fail?

@itsmoonrack itsmoonrack requested a review from beikov June 30, 2025 19:27
@itsmoonrack itsmoonrack requested a review from gavinking June 30, 2025 22:22
Comment on lines -237 to +269
+ "' has properties mapped to two different tables"
+ " (all properties of the embeddable class must map to the same table)"
+ "' has properties mapped to two different tables"
+ " (all properties of the embeddable class must map to the same table)"
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind reverting this whitespace change (yes, I know IntelliJ makes this change since a recent release, but I'm not keen on it, and I wish there was a way to revert to the old behavior to stay consistent with the existing code.

Copy link
Author

Choose a reason for hiding this comment

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

@gavinking which whitespace ? in the EmbeddableBinder ?

those ?

binder.setEmbedded( isComponentEmbedded );
		binder.setHolder( propertyHolder );
		binder.setId( isId );
		binder.setEntityBinder( entityBinder );
		binder.setInheritanceStatePerClass( inheritanceStatePerClass );
		binder.setBuildingContext( context );

Copy link
Member

Choose a reason for hiding this comment

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

No, look at the quoted lines, which removed two tabs.

Copy link
Author

Choose a reason for hiding this comment

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

@gavinking its actually an optical illusion, the AnnotatedException is threw twice here (i could not really find a better way of doing this) and git says there is a change in whitespace but if you pull the PR there is actually none.

For the squash, I create a new PR based on main correct ? And close this one ? Or there is a way to squash inside this branch ?

Copy link
Author

Choose a reason for hiding this comment

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

I created the new PR here: #10447

Copy link
Member

@gavinking gavinking left a comment

Choose a reason for hiding this comment

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

This looks OK now, based on a superficial review.

@itsmoonrack Would you please squash the commits and maybe fix up that minor whitespace change at the same time.

}

public void checkPropertyConsistency() {
if ( annotatedColumns.size() > 1 ) {
Copy link
Member

Choose a reason for hiding this comment

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

Can't you just use component.getColumns() instead?

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.

4 participants