Skip to content

spring data jdbc: saveAll on null id's entities calls update statement #2064

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
chrshnv opened this issue May 30, 2025 · 8 comments · May be fixed by #2065
Open

spring data jdbc: saveAll on null id's entities calls update statement #2064

chrshnv opened this issue May 30, 2025 · 8 comments · May be fixed by #2065
Assignees
Labels
type: bug A general bug

Comments

@chrshnv
Copy link

chrshnv commented May 30, 2025

saveAll on null id's entities calls update statement.

List<AppConfiguration> configurationsToSave = List.of(accessTokenConfiguration, refreshTokenConfiguration, expiredAtConfiguration);

configurationsToSave.forEach(it -> log.info(it.getId() != null ? it.getId().toString() : null));

appConfigurationRepository
		.saveAll(List.of(accessTokenConfiguration, refreshTokenConfiguration, expiredAtConfiguration));

logs with saveAll:

2025-05-30T11:30:41.694+05:00  INFO 11461 --- [chats] [nio-8080-exec-1] r.t.o.c.service.impl.BitrixTokenService  : null
2025-05-30T11:30:41.694+05:00  INFO 11461 --- [chats] [nio-8080-exec-1] r.t.o.c.service.impl.BitrixTokenService  : null
2025-05-30T11:30:41.694+05:00  INFO 11461 --- [chats] [nio-8080-exec-1] r.t.o.c.service.impl.BitrixTokenService  : null
2025-05-30T11:30:41.724+05:00 ERROR 11461 --- [chats] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute DbAction.UpdateRoot(entity=ru.trek.ozon.chats.domain.AppConfiguration@658327e7)] with root cause

org.springframework.dao.IncorrectUpdateSemanticsDataAccessException: Failed to update entity [ru.trek.ozon.chats.domain.AppConfiguration@658327e7]; Id [2f0a0c62-1a97-4ca4-a8ba-2c4b501e2459] not found in database```
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 30, 2025
@chrshnv chrshnv changed the title saveAll on null id's entities causing update statement spring data jdbc: saveAll on null id's entities causing update statement Jun 1, 2025
@chrshnv chrshnv changed the title spring data jdbc: saveAll on null id's entities causing update statement spring data jdbc: saveAll on null id's entities calls update statement Jun 1, 2025
@mipo256
Copy link
Contributor

mipo256 commented Jun 1, 2025

Thanks for reaching out @chrshnv

Please, provide minimal reproducible example. It is very hard to deduce the preconditions in your example.

@chrshnv
Copy link
Author

chrshnv commented Jun 1, 2025

@mipo256
Copy link
Contributor

mipo256 commented Jun 1, 2025

Thank you, @chrshnv.

I think the issue is that you're using BeforeConvertCallback and not BeforeSaveCallback for setting the id. The BeforeConvertCallback is invoked too early. Because of that, the id is assigned and the IsNewStrategy makes a decision that your entity is not new, and therefore the SQL UPDATE statement is issued.

Please, change the callbacks, the problem should go away.

@chrshnv
Copy link
Author

chrshnv commented Jun 1, 2025

ok, then why when I use "save" instead of "saveAll" - it inserts instead of update? you can try on 'save' branch in MRE repo

Image

@mipo256
Copy link
Contributor

mipo256 commented Jun 1, 2025

@chrshnv I'm sorry, I have misled you, the BeforeConvertCallback is indeed the correct one, my apologies.

And I think I even know where the problem is, it is right here:

	@Override
	public <T> List<T> saveAll(Iterable<T> instances) {
		return doInBatch(instances, (first) -> (second -> changeCreatorSelectorForSave(first).apply(second)));
	}

We apply the change creator on the first and not the second argument. I'll provide the PR.

Thank you, @chrshnv

@mipo256
Copy link
Contributor

mipo256 commented Jun 1, 2025

After taking a closer look at it.

Indeed, when we have the BeforeConvertCallback with id generation logic within, the in case of:

  1. `save(T) --> INSERT is triggered, as it should and documented in the Jens's article.
  2. saveAll(Iterable<T>) --> UPDATE is triggered, and it is not correct

Now, the reason why it actually happens is that when we do just save(T), we first, determine the RootAggregateChange, and only then call the BeforeConvertCallback, BeforeSaveCallback etc.

In case of saveAll(Iterable<T>), it turns out, that we call the BeforeConvertCallback before we infer the RootAggregateChange, which leads to Update action, and the incorrect update sematics error.

I think the best approach here would be to align the saveAll(Iterable<T>) with save(T), meaning, that the BeforeConvertCallback callback should be invoked after the RootAggregateChange is assembled.

What are your thoughts on this, Jens @schauder, Mark @mp911de?

mipo256 added a commit to mipo256/spring-data-relational that referenced this issue Jun 1, 2025
…the RootAggregateChange creation

Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>
@underbell
Copy link

underbell commented Jun 2, 2025

It seems to be the same issue that I encountered. I have created an MRE for reporting the issue. Please refer to it.
https://github.com/underbell/jdbc-audit-test-boot3.5.0

@mp911de
Copy link
Member

mp911de commented Jun 2, 2025

I consider this a bug and we should align saving for both cases.

@schauder schauder added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
6 participants