Skip to content

GH-1137 avoid populating version second time during update and insert #1150

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 7 commits into from
Closed

GH-1137 avoid populating version second time during update and insert #1150

wants to merge 7 commits into from

Conversation

mipo256
Copy link
Contributor

@mipo256 mipo256 commented Jan 30, 2022

Addressing #1137 issue. Jens @schauder, can you, please, review the changes.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 30, 2022
Copy link
Contributor

@schauder schauder left a comment

Choose a reason for hiding this comment

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

Thanks for the PR.
Most of it looks really good. There is only one major concern: We can't remove public classes, methods, or constructors without deprecating them first, except for major versions. So we should make those changes that the original signatures stay available but are deprecated.

@mipo256
Copy link
Contributor Author

mipo256 commented Feb 1, 2022

Thanks for the PR. Most of it looks really good. There is only one major concern: We can't remove public classes, methods, or constructors without deprecating them first, except for major versions. So we should make those changes that the original signatures stay available but are deprecated.

Yes, you are right, I have forgotten about it :) I will fix this shortly, thank you very much for reviewing PR, appretiate it)

@mipo256 mipo256 requested a review from schauder February 1, 2022 21:16
@mipo256
Copy link
Contributor Author

mipo256 commented Feb 1, 2022

As requested, I have deprecated the api that will not be used anymore if we apply this change :) Jens @schauder, can you, please, take a look?)

@schauder schauder changed the title DATAJDBC-1137 avoid populating version second time during update and insert GH-1137 avoid populating version second time during update and insert Feb 15, 2022
@schauder schauder linked an issue Feb 15, 2022 that may be closed by this pull request
@@ -99,16 +99,15 @@ public void callbackOnSave() {

SampleEntity first = new SampleEntity(null, "Alfred");
SampleEntity second = new SampleEntity(23L, "Alfred E.");
SampleEntity third = new SampleEntity(23L, "Neumann");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you think the behaviour tested by this class should change?

Copy link
Contributor Author

@mipo256 mipo256 Mar 3, 2022

Choose a reason for hiding this comment

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

That might take some time to explain. Let me try :)

Previously we utilized JdbcAggregateChangeExecutionContext#populateIdsIfNecessary to set Ids to aggregates and other stuff. This method used to return null, if the entity after execution of DML have not been changed (simple example of how entity can be changed - there could be an auto generated identified provided by DB). And then after that we assigned the version to the entity. The essense of the problem were the follisng - both populateIdsIfNecessary method, or AggregateChange#getEntity method return entity without any version, even if we assign it previously.

Now, JdbcAggregateChangeExecutionContext can store in results map InsertExecutionResult object, which contains an entity with version - that is necessary, because we want to avoid redundant version population, but here is the problem - method JdbcAggregateChangeExecutionContext#populateIdsIfNecessary (I have given in my PR the more human readable, at least from my perspective, name to the method - getRootWithPopulatedFieldsFromExecutionResult) should not return null anymore, even if entity did not changed, becuase only JdbcAggregateChangeExecutionContext knows the version that was assigned to an entity. If we would return null, then we will loose this version information - just to remind, AggregateChange#getEntity does not know anything about version assigned to given entity, so if we will allow to return null, we will fallback to initaill issue.

This test case relied on fact, that AggregateChangeExecutor#execute will return an entity that is stored inside aggregateChange - wich is an entity from mock , the third one, but now it is no longer the truth. Now, the inserted entity will always be taken from InsertExecutionResult, which is the second one. Thus this test needs to be a little bit changed

I really did my best to explain it :) @schauder Jens, in case of any further questions - do reach me out, I will clarify everything :)

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem with this is the test is a very valid test for the working of callbacks.
We need to continue to work with the third value returned by the mock in the original version of the test, because it is a value a user might return from a callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Jens @schauder, quite, I got it. So lets segregate 2 things - the user would expect, that the return value of the tamplate#save() method will be the same as the entity, returned from AfterSaveCallback, right? I agree,and I have fixed this, thanks! But becuase of the reasons I have explained upwards, AfterSaveCallback will be populated with the second, not third entity. That thing will remain, right @schauder? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

AfterSaveCallback will be populated with the second, not third entity.

No, that is not acceptable. The callback have to behave like a pipeline. What comes out of one needs to go into the next. The version update should be a part of that pipeline, so it must get its initial value from one callback and its result must go into the next callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I got it) So I have fixed this, Jens @schauder, can you please re-review the changes?)

@mipo256 mipo256 requested a review from schauder March 9, 2022 09:13
@schauder
Copy link
Contributor

Please rebase this on the current main branch and squash it into a single commit.

ctailor2 added a commit that referenced this pull request Mar 17, 2022
Copy link
Contributor

@ctailor2 ctailor2 left a comment

Choose a reason for hiding this comment

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

The proposed changes to JdbcAggregateChangeExecutionContext break the method contract and will require updates ensure that this is not the case. Please see the line comments for further details.

*
* @link https://github.com/spring-projects/spring-data-jdbc/issues/1137
*/
@Deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

As this method is not public I believe it could be removed rather than deprecated. Is my thinking correct @schauder ? If so, that would apply to the private methods related to the version field as well.

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 here, that make sence, let's await for the @schauder assesment

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct, we need to maintain backward compatibility for code created by our users.

The most obvious is: we can't change/remove public methods. We also can't add methods to an interface without default implementation, or change protected methods which might be overwritten or called by classes created by users.

Everything that isn't directly reachable by the users is up for grabs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, got it, thanks, so I will remove the non-public api, thanks guys!


// the id property was immutable so we have to propagate changes up the tree
if (newEntity != withEntity.getEntity()) {
rootWithPopulatedFields = (T) newEntity;
Copy link
Contributor

Choose a reason for hiding this comment

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

Setting this variable for all InsertExecutionResult instead of only those whose action is instanceof DbAction.InsertRoot will cause some unexpected behavior for inserts on aggregates whose root is mutable but contains immutable references. In this scenario the return value of this method call would be a non-root entity rather than the aggregate root, which would break the method contract.

Add the following test to JdbcAggregateChangeExecutorContextImmutableUnitTests to see an example of this behavior.

@Test
public void idGenerationOfChild_withMutableRoot() {

	Content content = new Content();

	when(accessStrategy.insert(any(DummyEntityMutableNoVersion.class), eq(DummyEntityMutableNoVersion.class), eq(Identifier.empty()))).thenReturn(23L);
	when(accessStrategy.insert(any(Content.class), eq(Content.class), eq(createBackRef()))).thenReturn(24L);

	DbAction.InsertRoot<DummyEntityMutableNoVersion> rootInsert = new DbAction.InsertRoot<>(new DummyEntityMutableNoVersion());
	executionContext.executeInsertRoot(rootInsert);
	executionContext.executeInsert(createInsert(rootInsert, "content", content, null));

        // This call results in a ClassCastException as it returns an instance of Content rather than DummyEntityMutableNoVersion
	DummyEntityMutableNoVersion newRoot = executionContext.getRootWithPopulatedFieldsFromExecutionResult();

	assertThat(newRoot).isNotNull();
	assertThat(newRoot.id).isEqualTo(23L);

	assertThat(newRoot.content.id).isEqualTo(24L);
}

@Data
@AllArgsConstructor
@NoArgsConstructor
private static class DummyEntityMutableNoVersion {
	@Id private Long id;

	private Content content;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, let me double check it, thank you very much for your suggestion.

@mipo256 mipo256 marked this pull request as draft March 17, 2022 20:39
@ctailor2
Copy link
Contributor

Over the course of exploring a possible design for GH-537 where AggregateChange represents the change plan via DbAction for multiple entities and JdbcAggregateChangeExecutionContext is capable of being used for multiple roots, I happened to explore a refactor that deals with this same concern but uses a different approach.

I wasn't previously aware of the issue that this PR closes, but since my changes also seem to address the extraneous constructor calls, I figured I would cross reference it here for possible consideration.

@schauder
Copy link
Contributor

I'd like to go forward with the PR by @ctailor2
The result is easier to understand at least for me.

@mikhail2048 I'm sorry that you put so much work into this before I turn around and go with a different PR. Your work on this, on previous and hopefully future PRs is still very much appreciated.

I leave this open for now until I can actually merge the other PR.

schauder pushed a commit that referenced this pull request Mar 22, 2022
…DbActions to simplify execution context.

This change incorporates one test from #1150

Original pull request #1196
Closes #1137
@schauder schauder closed this Mar 22, 2022
@schauder schauder added status: superseded An issue that has been superseded by another type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 22, 2022
@mipo256
Copy link
Contributor Author

mipo256 commented Mar 23, 2022

A pity, but OK, at the end of the day the goal is to make framework better :) Thank you fellows @schauder @ctailor2 !)

lseeker pushed a commit to lseeker/spring-data-jdbc that referenced this pull request Apr 6, 2022
…DbActions to simplify execution context.

This change incorporates one test from spring-projects#1150

Original pull request spring-projects#1196
Closes spring-projects#1137
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: superseded An issue that has been superseded by another type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Constructor is accessed twice when persisting Java 16 records
4 participants