Skip to content

Conversation

@belljun3395
Copy link

@belljun3395 belljun3395 commented Sep 4, 2025

Optimize parameter matching in TableMetaDataContext

Problem

The TableMetaDataContext.matchInParameterValuesWithInsertColumns() method uses a nested loop for case-insensitive parameter matching, resulting in O(n×m) complexity where n = columns and m = parameters.

For each column, when exact matching fails, it iterates through all parameter entries to find case-insensitive matches:

for (String column : this.tableColumns) {                    // O(n)
    Object value = inParameters.get(column);
    if (value == null) {
        // ... lowercase lookup
        if (value == null) {
            for (Map.Entry<String, ?> entry : inParameters.entrySet()) {  // O(m)
                if (column.equalsIgnoreCase(entry.getKey())) {
                    value = entry.getValue();
                    break;
                }
            }
        }
    }
    values.add(value);
}

This becomes expensive with many columns and parameters (e.g., 100 columns × 1000 parameters = 100K iterations).

Solution

Pre-compute a case-insensitive lookup map, reducing complexity from O(n×m) to O(n+m):

// Build lookup map once - O(m)
Map<String, Object> caseInsensitiveLookup = new HashMap<>(inParameters.size());
for (Map.Entry<String, ?> entry : inParameters.entrySet()) {
    caseInsensitiveLookup.put(entry.getKey().toLowerCase(Locale.ROOT), entry.getValue());
}

for (String column : this.tableColumns) {  // O(n)
    Object value = inParameters.get(column);
    if (value == null) {
        value = inParameters.get(column.toLowerCase(Locale.ROOT));
        if (value == null) {
            value = caseInsensitiveLookup.get(column.toLowerCase(Locale.ROOT)); // O(1)
        }
    }
    values.add(value);
}

Performance Impact

  • Before: O(n×m) - quadratic growth
  • After: O(n+m) - linear growth
  • Applications affected: SimpleJdbcInsert users with many columns/parameters

Testing

  • ✅ All existing tests pass (no behavior change)
  • ✅ Verified case-sensitive/insensitive matching logic preserved

Replace nested loop with pre-computed case-insensitive lookup map,
reducing time complexity from O(n×m) to O(n+m) where n = number
of columns and m = number of parameters.

Before: Iterates through all parameter entries for each column
when case-insensitive matching is needed.

After: Build HashMap once for O(1) case-insensitive lookups.

Signed-off-by: belljun3395 <195850@jnu.ac.kr>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 4, 2025
@sbrannen sbrannen added the in: data Issues in data modules (jdbc, orm, oxm, tx) label Sep 6, 2025
List<Object> values = new ArrayList<>(inParameters.size());
List<Object> values = new ArrayList<>(this.tableColumns.size());

Map<String, Object> caseInsensitiveLookup = new HashMap<>(inParameters.size());
Copy link
Contributor

Choose a reason for hiding this comment

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

Spring itself provides a LinkedCaseInsensitiveMap which provides this functionality already. Wouldn't it be easier to use that, it seems to be used in other places in the JDBC access already.

Copy link
Author

Choose a reason for hiding this comment

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

I've updated the implementation to use LinkedCaseInsensitiveMap instead of manually creating a case-insensitive lookup map. The changes are available in commit cdb149b

break;
}
}
value = caseInsensitiveLookup.get(column.toLowerCase(Locale.ROOT));
Copy link
Contributor

Choose a reason for hiding this comment

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

When using the LinkedCaseInsensitiveMap you can just do a regular get without the need to convert to lowercase (the map will take care of this). Which would simplify this code.

…ved efficiency

Signed-off-by: belljun3395 <195850@jnu.ac.kr>
Copy link
Author

@belljun3395 belljun3395 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 highlighting the existing implementation examples I overlooked, and I appreciate the review!

List<Object> values = new ArrayList<>(inParameters.size());
List<Object> values = new ArrayList<>(this.tableColumns.size());

Map<String, Object> caseInsensitiveLookup = new HashMap<>(inParameters.size());
Copy link
Author

Choose a reason for hiding this comment

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

I've updated the implementation to use LinkedCaseInsensitiveMap instead of manually creating a case-insensitive lookup map. The changes are available in commit cdb149b

@belljun3395 belljun3395 requested a review from mdeinum September 16, 2025 13:50
@bclozel bclozel assigned bclozel and unassigned bclozel Nov 17, 2025
@bclozel
Copy link
Member

bclozel commented Nov 17, 2025

This looks good overall but I'm wondering if this isn't a typical cpu vs. memory tradeoff.
The first lookup is done against inParameters.get(column.toLowerCase(Locale.ROOT)); and we only fall back to a case insensitive lookup if nothing was found.

Depending on the most common situation here, one could consider that creating a new Map instance with thousands of entries for a fallback operation that is barely used is a performance regression.

When is this particular fallback being used @belljun3395 , maybe you encountered a performance issue in your application and can share more about what happened?

Thanks!

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Nov 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: data Issues in data modules (jdbc, orm, oxm, tx) status: waiting-for-feedback We need additional information before we can continue status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants