Skip to content

Upsert implementation - #3940

Open
msupic wants to merge 61 commits into
5.2.xfrom
upsert-impl
Open

Upsert implementation#3940
msupic wants to merge 61 commits into
5.2.xfrom
upsert-impl

Conversation

@msupic

@msupic msupic commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Closes #3351

Upsert implementation overview

This PR adds native SQL upsert support for Micronaut Data SQL repositories.

Repository API

Upsert is supported only through explicitly declared repository methods. No inherited CrudRepository methods were added.

Supported declaration styles:

ProductReview upsert(ProductReview entity);

List<ProductReview> upsertAll(Iterable<ProductReview> entities);

@Upsert
ProductReview put(ProductReview entity);

@Upsert
List<ProductReview> putAll(Iterable<ProductReview> entities);

The Upsert annotation also supports custom conflict columns through conflictsOn:

@Upsert(conflictsOn = "email")
CustomerProfile upsert(CustomerProfile profile);

@Upsert(conflictsOn = {"sku", "warehouse"})
WarehouseInventory upsert(WarehouseInventory inventory);

Reactive and async return types are also supported:

@Upsert(conflictsOn = "email")
Mono<CustomerProfile> upsertMono(CustomerProfile profile);

@Upsert(conflictsOn = "email")
CompletableFuture<CustomerProfile> upsertFuture(CustomerProfile profile);

@Upsert(conflictsOn = "email")
Flux<CustomerProfile> upsertAllFlux(Iterable<CustomerProfile> profiles);

@Upsert(conflictsOn = "email")
CompletableFuture<List<CustomerProfile>> upsertAllFuture(Iterable<CustomerProfile> profiles);

Dialect support

Native upsert SQL is generated per dialect:

Dialect SQL shape
H2 MERGE INTO ... KEY(...) VALUES (...)
MySQL / MariaDB INSERT ... ON DUPLICATE KEY UPDATE ...
PostgreSQL INSERT ... ON CONFLICT (...) DO UPDATE SET ...
SQLite INSERT ... ON CONFLICT (...) DO UPDATE SET ...
SQL Server MERGE INTO ... WITH (HOLDLOCK) ...
Oracle MERGE INTO ... USING (SELECT ... FROM DUAL) ...
ANSI ANSI-style MERGE

msupic added 30 commits June 11, 2026 10:23
@msupic

msupic commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

This PR introduces OracleJdbcRepositoryOperations and SqlServerJdbcRepositoryOperations as dialect-specific extensions of DefaultJdbcRepositoryOperations.

OracleJdbcRepositoryOperations is needed because Oracle upsert with generated ID returning uses Oracle-specific JDBC APIs, including oracle.jdbc.OraclePreparedStatement. This is especially important for upsertAll, where we want to execute a real JDBC batch and read returned IDs for the batch items, instead of falling back to executing each upsert one by one. Keeping this code in an Oracle-specific operations class also means it is only used when the Oracle JDBC driver is available on the classpath.

I applied the same separation for SQL Server through SqlServerJdbcRepositoryOperations. SQL Server does not require a driver-specific class in the same way Oracle does, but its upsert generated ID handling uses MERGE ... OUTPUT inserted.<id-column>, which is still dialect-specific runtime behavior. Keeping that logic separate helps keep DefaultJdbcRepositoryOperations focused on the generic JDBC execution path.

@dstepanov - If this approach looks good to you, we can continue moving Oracle-specific code out of DefaultJdbcRepositoryOperations into OracleJdbcRepositoryOperations, which should make the default implementation easier to follow.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces native SQL upsert support across Micronaut Data SQL repositories by adding an @Upsert lifecycle annotation, a compile-time method matcher, dialect-specific SQL generation, and runtime execution paths (including generated-id returning for Oracle and SQL Server). It also adds extensive TCK entities/repositories and JDBC/R2DBC test coverage, plus new user-guide documentation.

Changes:

  • Add @Upsert API + processor support (UpsertMethodMatcher) and wire operation types through the model/runtime.
  • Implement SQL upsert SQL generation in SqlQueryBuilder and add specialized JDBC/R2DBC operations for Oracle/SQL Server generated-id returning.
  • Add documentation (“Upserting”) and introduce JDBC/R2DBC/SQLite test suites plus new TCK entities/repositories for upsert scenarios.

Reviewed changes

Copilot reviewed 106 out of 106 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jdbc/sqlite/SQLiteWarehouseInventoryRepository.java Adds SQLite repository bean for upsert TCK + finder used by tests.
test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jdbc/sqlite/SQLiteUpsertTest.java Adds SQLite JDBC tests validating insert/update upsert behavior.
test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jdbc/sqlite/SQLiteProductReviewRepository.java Adds SQLite repository bean for ProductReview upsert tests.
test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jdbc/sqlite/SQLiteCustomerProfileUuidRepository.java Adds SQLite repository bean for UUID upsert coverage (not directly exercised in shown test).
test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jdbc/sqlite/SQLiteCustomerProfileRepository.java Adds SQLite repository bean for CustomerProfile upsert tests + finder used by tests.
src/main/docs/guide/toc.yml Adds “Upserting” to docs TOC under data updates.
src/main/docs/guide/shared/dataUpdates/upserts.adoc New guide section describing upsert API, constraints, and dialect behavior.
data-tck/src/main/java/io/micronaut/data/tck/repositories/upsert/WarehouseInventoryRepository.java Adds TCK repository contract for conflict-column upsert.
data-tck/src/main/java/io/micronaut/data/tck/repositories/upsert/ProductReviewRepository.java Adds TCK repository contract for identity-based upsert + @Upsert alternate method names.
data-tck/src/main/java/io/micronaut/data/tck/repositories/upsert/CustomerProfileUuidRepository.java Adds TCK repository contract for upsert with generated UUID identity and conflict column.
data-tck/src/main/java/io/micronaut/data/tck/repositories/upsert/CustomerProfileRepository.java Adds TCK repository contract for sync/async/reactive upsert return shapes.
data-tck/src/main/java/io/micronaut/data/tck/jdbc/entities/upsert/WarehouseInventory.java New TCK entity with unique index conflict target (sku, warehouse).
data-tck/src/main/java/io/micronaut/data/tck/jdbc/entities/upsert/ProductReview.java New TCK entity with assigned identity for default-conflict upsert tests.
data-tck/src/main/java/io/micronaut/data/tck/jdbc/entities/upsert/CustomerProfileUuid.java New TCK entity with generated UUID identity + unique email conflict target.
data-tck/src/main/java/io/micronaut/data/tck/jdbc/entities/upsert/CustomerProfile.java New TCK entity with generated identity + unique email conflict target.
data-runtime/src/main/java/io/micronaut/data/runtime/intercept/AbstractQueryInterceptor.java Wires UPSERT into stored-query operation-type resolution.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/MSWarehouseInventoryRepository.java SQL Server R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/MSProductReviewRepository.java SQL Server R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/MSCustomerProfileUuidRepository.java SQL Server R2DBC test repository binding for TCK UUID upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/MSCustomerProfileSequenceRepository.java SQL Server R2DBC test repository for sequence-generated identity upsert returning.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/MSCustomerProfileRepository.java SQL Server R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/upsert/CustomerProfileSequence.java SQL Server R2DBC test entity for sequence-generated identity.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/PostgresWarehouseInventoryRepository.java Postgres R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/PostgresProductReviewRepository.java Postgres R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/PostgresCustomerProfileUuidRepository.java Postgres R2DBC test repository binding for TCK UUID upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/PostgresCustomerProfileSequenceRepository.java Postgres R2DBC test repository for sequence-generated identity upsert returning.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/PostgresCustomerProfileRepository.java Postgres R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/upsert/CustomerProfileSequence.java Postgres R2DBC test entity for sequence-generated identity.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/OracleXEWarehouseInventoryRepository.java Oracle XE R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/OracleXEProductReviewRepository.java Oracle XE R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/OracleXECustomerProfileUuidRepository.java Oracle XE R2DBC test repository binding for TCK UUID upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/OracleXECustomerProfileSequenceRepository.java Oracle XE R2DBC test repository for sequence-generated identity upsert returning.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/OracleXECustomerProfileRepository.java Oracle XE R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/upsert/CustomerProfileSequence.java Oracle XE R2DBC test entity for sequence-generated identity.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/upsert/MySqlWarehouseInventoryRepository.java MySQL R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/upsert/MySqlProductReviewRepository.java MySQL R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/upsert/MySqlCustomerProfileUuidRepository.java MySQL R2DBC test repository binding for TCK UUID upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/upsert/MySqlCustomerProfileRepository.java MySQL R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/upsert/H2WarehouseInventoryRepository.java H2 R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/upsert/H2ProductReviewRepository.java H2 R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/upsert/H2CustomerProfileUuidRepository.java H2 R2DBC test repository binding for TCK UUID upsert contract.
data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/upsert/H2CustomerProfileRepository.java H2 R2DBC test repository binding for TCK upsert contract.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/sqlserver/SqlServerUpsertSpec.groovy Adds SQL Server R2DBC upsert spec exercising TCK + sequence-id returning cases.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/vector/PostgresDbInit.java Removes old vector-specific init hook (merged into shared Postgres init).
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresUpsertSpec.groovy Adds Postgres R2DBC upsert spec and wires spec-name properties.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresDbInit.java Enhances Postgres R2DBC init: readiness wait (local only) + extension creation for specific specs.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/oraclexe/OracleXEUpsertSpec.groovy Adds Oracle XE R2DBC upsert spec covering generated-id returning cases.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/operations/R2dbcRepositoryOperationsConditionsSpec.groovy Tests per-dialect R2DBC operations bean selection logic.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mysql/MySqlUpsertSpec.groovy Adds MySQL R2DBC upsert spec and flags unsupported UUID returning.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbUpsertSpec.groovy Adds MariaDB R2DBC upsert spec and flags unsupported UUID returning.
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2UpsertSpec.groovy Adds H2 R2DBC upsert spec.
data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/SqlServerR2dbcRepositoryOperations.java New SQL Server R2DBC operations implementation handling MERGE OUTPUT id reads for upsert.
data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/R2dbcRepositoryOperationsConditions.java New shared R2DBC operations conditions for Oracle/SQL Server/default selection.
data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/OracleR2dbcRepositoryOperations.java New Oracle R2DBC operations implementation handling RETURNING INTO for upsert generated-id.
data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildTableSpec.groovy Adds SQL Server sequence generation DDL test coverage.
data-processor/src/main/resources/META-INF/services/io.micronaut.data.processor.visitors.finders.MethodMatcher Registers UpsertMethodMatcher with the processor.
data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java Includes @Upsert in lifecycle method entity-type inference.
data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/UpsertMethodMatcher.java New processor matcher generating upsert stored queries + validating constraints.
data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/FindersUtils.java Routes UPSERT operation type through update-style interceptor selection.
data-model/src/main/java/io/micronaut/data/model/runtime/StoredQuery.java Adds StoredQuery.OperationType.UPSERT.
data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder.java Adds SQL Server sequence DDL tweaks + upsert query building entry point.
data-model/src/main/java/io/micronaut/data/model/query/builder/QueryBuilder.java Adds buildUpsert API and UpsertQueryDefinition.
data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethodQuery.java Adds UPSERT operation type.
data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethod.java Adds UPSERT operation type.
data-model/src/main/java/io/micronaut/data/annotation/Upsert.java New public @Upsert lifecycle annotation.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/MSWarehouseInventoryRepository.java SQL Server JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/MSProductReviewRepository.java SQL Server JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/MSCustomerProfileUuidRepository.java SQL Server JDBC test repository binding for TCK UUID upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/MSCustomerProfileSequenceRepository.java SQL Server JDBC test repository for sequence-generated identity upsert returning.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/MSCustomerProfileRepository.java SQL Server JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/upsert/CustomerProfileSequence.java SQL Server JDBC test entity for sequence-generated identity.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/PostgresWarehouseInventoryRepository.java Postgres JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/PostgresProductReviewRepository.java Postgres JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/PostgresCustomerProfileUuidRepository.java Postgres JDBC test repository binding for TCK UUID upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/PostgresCustomerProfileSequenceRepository.java Postgres JDBC test repository for sequence-generated identity upsert returning.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/PostgresCustomerProfileRepository.java Postgres JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/upsert/CustomerProfileSequence.java Postgres JDBC test entity for sequence-generated identity.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/OracleXEWarehouseInventoryRepository.java Oracle XE JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/OracleXEProductReviewRepository.java Oracle XE JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/OracleXECustomerProfileUuidRepository.java Oracle XE JDBC test repository binding for TCK UUID upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/OracleXECustomerProfileSequenceRepository.java Oracle XE JDBC test repository for sequence-generated identity upsert returning.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/OracleXECustomerProfileRepository.java Oracle XE JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/upsert/CustomerProfileSequence.java Oracle XE JDBC test entity for sequence-generated identity.
data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/upsert/MySqlWarehouseInventoryRepository.java MySQL JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/upsert/MySqlProductReviewRepository.java MySQL JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/upsert/MySqlCustomerProfileUuidRepository.java MySQL JDBC test repository binding for TCK UUID upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/upsert/MySqlCustomerProfileRepository.java MySQL JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/upsert/H2WarehouseInventoryRepository.java H2 JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/upsert/H2ProductReviewRepository.java H2 JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/upsert/H2CustomerProfileUuidRepository.java H2 JDBC test repository binding for TCK UUID upsert contract.
data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/upsert/H2CustomerProfileRepository.java H2 JDBC test repository binding for TCK upsert contract.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerUpsertSpec.groovy Adds SQL Server JDBC upsert spec + sequence-id returning cases.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresUpsertSpec.groovy Adds Postgres JDBC upsert spec + sequence-id returning cases.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXEUpsertSpec.groovy Adds Oracle XE JDBC upsert spec + sequence-id returning cases.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/operations/JdbcRepositoryOperationsConditionsSpec.groovy Tests per-dialect JDBC operations bean selection logic.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MySqlUpsertSpec.groovy Adds MySQL JDBC upsert spec and flags unsupported UUID returning.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mariadb/MariaUpsertSpec.groovy Adds MariaDB JDBC upsert spec and flags unsupported UUID returning.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2UpsertSpec.groovy Adds H2 JDBC upsert spec.
data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/SqlServerJdbcRepositoryOperations.java New SQL Server JDBC operations implementation handling MERGE OUTPUT id reads for upsert.
data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/JdbcRepositoryOperationsConditions.java New shared JDBC operations conditions for Oracle/SQL Server/default selection.

Comment thread data-model/src/main/java/io/micronaut/data/annotation/Upsert.java
Comment thread data-model/src/main/java/io/micronaut/data/annotation/Upsert.java Outdated
* @param context The condition context
* @return The configured datasource names
*/
private static List<String> resolveConfiguredDataSourceNames(ConditionContext context) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

feels like there should be some utility for this since it is seems like a common requirements to get the configured datasource names. It should also probably cache

@msupic msupic Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I created DataSourceConfigurationUtils and moved resolveDataSourceName and resolveConfiguredDataSourceNames methods to it. The resolveConfiguredDataSourceNames method caches resolved data source names and keeps the cache in BeanContext attributes

@Requires(classes = OraclePreparedStatement.class)
@Requires(condition = OracleJdbcRepositoryOperationsCondition.class)
@Internal
public final class OracleJdbcRepositoryOperations extends DefaultJdbcRepositoryOperations {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does it need to be public?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

DefaultJdbcRepositoryOperations is public too that was probably the reason why I put OracleJdbcRepositoryOperations and SqlServerJdbcRepositoryOperations to be public too, but I think those two don't have to be public so I modify it.

import static io.micronaut.data.annotation.GeneratedValue.Type.AUTO;
import static io.micronaut.data.annotation.GeneratedValue.Type.SEQUENCE;

final class SqlUpsertQueryBuilder {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add maintainer focused javadoc to this class describing its purpose and implementation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added javadoc

The default conflict target is the entity identity.
Use ann:data.annotation.Upsert[] with `conflictsOn` to use another persistent property, or a set of persistent properties, as the conflict target.

[source,java]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we replace these hard coded snippets with the snippet: macro and actual tested source code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@Upsert
Contact put(Contact contact);

@Upsert(conflictsOn = "email")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since we generated JPA metadata at build time would be good to show an example that uses type safe property names instead of strings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@graemerocher If we want to use type safe property names in the Upsert annoation, the entity must be compiled in another module. Currently Micronaut static metamodel generator is used only in data-tck module, so I would need to create a new entity in data-tck module and add data-tck dependency to a doc-examples project. Is that correct?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean it should work if it is in the same module since javac supports multiple rounds of compilation

@msupic msupic Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Javac does support multiple processing rounds, but I tried this pattern

@Upsert(conflictsOn = Passenger_.EMAIL)

and Passenger_ is not resolvable when Micronaut Data processes the repository’s Upsert annotation. The metamodel source is generated in a later round, while UpsertMethodMatcher validates conflictsOn in the earlier round and sees an error type rather than the generated "email" constant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will try to modify the code to use postponedRepositories in RepositoryTypeElementVisitor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yeah this sounds like a bug

Copy link
Copy Markdown
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 do that using AI but without success.

The following is AI response:

Postponing RepositoryTypeElementVisitor when conflictsOn is <error> makes Passenger_.EMAIL resolve in the next javac round, but Micronaut’s bean-definition processor has already emitted the repository proxy in the first round. The resulting repository compiles but fails at runtime because its method has no compiled query metadata.
So this cannot be fixed safely in UpsertMethodMatcher or RepositoryTypeElementVisitor alone. It needs Micronaut core annotation-processing support that postpones both repository processing and bean-definition generation, then reprocesses the original repository in the later round.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmm ok, probably worth an issue with a reproducer for core

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, once this PR gets merged, I will create an issue and reproducer.

@msupic
msupic changed the base branch from 5.1.x to 5.2.x July 23, 2026 10:43
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
9 New Critical Issues (required ≤ 0)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

}
}

private void upsert() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't like this custom repository operations, can we simply create a query that is a function for oracle or is an ordinary query for other cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JDBC repositories should support MERGE (upsert)

5 participants