{"record":{"id":"655add51fe0bbac0","repo":"hibernate/hibernate-orm","slug":"unexpected-row-count-expected-row-count-but-wa-655add","errorCode":null,"errorMessage":"Unexpected row count (expected row count {} but was {})","messagePattern":"Unexpected row count \\(expected row count (.+?) but was (.+?)\\)","errorType":"exception","errorClass":"TooManyRowsAffectedException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/jdbc/Expectations.java","lineNumber":98,"sourceCode":"\t\t\t\telse if ( expectedRowCount < rowCount ) {\n\t\t\t\t\tthrow new BatchedTooManyRowsAffectedException(\n\t\t\t\t\t\t\t\"Batch update returned unexpected row count from update \" + batchPosition\n\t\t\t\t\t\t\t\t\t+ actualVsExpected( expectedRowCount, rowCount ),\n\t\t\t\t\t\t\texpectedRowCount, rowCount, batchPosition );\n\t\t\t\t}\n\t\t}\n\t}\n\n\tstatic void checkNonBatched(int expectedRowCount, int rowCount, String sql) {\n\t\tif ( expectedRowCount > rowCount ) {\n\t\t\tthrow new StaleStateException(\n\t\t\t\t\t\"Unexpected row count\"\n\t\t\t\t\t\t\t+ actualVsExpected( expectedRowCount, rowCount )\n\t\t\t\t\t\t\t+ \" [\" + sql + \"]\"\n\t\t\t);\n\t\t}\n\t\tif ( expectedRowCount < rowCount ) {\n\t\t\tthrow new TooManyRowsAffectedException(\n\t\t\t\t\t\"Unexpected row count\"\n\t\t\t\t\t\t\t+ actualVsExpected( expectedRowCount, rowCount ),\n\t\t\t\t\t1, rowCount\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate static String actualVsExpected(int expectedRowCount, int rowCount) {\n\t\treturn \" (expected row count \" + expectedRowCount + \" but was \" + rowCount + \")\";\n\t}\n\n\tprivate Expectations() {\n\t}\n}\n","sourceCodeStart":80,"sourceCodeEnd":113,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/jdbc/Expectations.java#L80-L113","documentation":"checkNonBatched throws TooManyRowsAffectedException when a non-batched statement affects more rows than the ORM expected (expected 1, actual N). One logical entity operation physically touched several rows - either the SQL predicate is not unique, or the database (trigger, FK cascade, inheritance tables) changed extra rows within the same statement.","triggerScenarios":"Custom @SQLUpdate/@SQLDelete whose predicate matches multiple rows; joined-table inheritance deletes hitting both parent and child tables in one statement; DB triggers or ON DELETE CASCADE duplicating the ORM's own work.","commonSituations":"Custom SQL copied from a bulk-maintenance script without a primary-key predicate; schemas where triggers maintain denormalized copies; migration from single-table to joined inheritance leaving old SQL in place.","solutions":["Make the custom SQL predicate unique (delete/update by primary key and version only)","Remove overlap between ORM cascades and DB-side triggers/cascades so one operation changes exactly one row","If multi-row effects are intended, disable the count check for that operation with @Expectation(none)"],"exampleFix":"// before\n@SQLUpdate(sql = \"update Account set balance = ? where ownerId = ?\") // owner may hold several accounts\n\n// after\n@SQLUpdate(sql = \"update Account set balance = ? where id = ?\") // exactly one row","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    em.flush();\n} catch (org.hibernate.TooManyRowsAffectedException e) {\n    // e.getExpectedRowCount()=1, e.getActualRowCount()=N: the SQL predicate is not unique\n    // or a trigger/cascade changed extra rows - fix mapping or custom SQL\n    throw e;\n}","preventionTips":["Make custom update/delete SQL unique per entity (primary key + version)","Model inheritance deletes so each statement affects its own table row","Smoke-test custom SQL row counts in CI against production-shaped data"],"tags":["custom-sql","row-count","triggers","flush"],"backgroundTag":"unexpected-row-count","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}