FINERACT-1971: use KeyHolder instead of LASTVAL/LAST_INSERT_ID

This commit is contained in:
Adam Saghy
2024-04-02 19:34:10 +02:00
parent 5fa2295bd0
commit 749d6511ef
3 changed files with 44 additions and 18 deletions
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.service.database;
import static java.lang.String.format; import static java.lang.String.format;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.math.BigInteger;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -30,6 +31,7 @@ import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeader
import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@@ -157,16 +159,6 @@ public class DatabaseSpecificSQLGenerator {
} }
} }
public String lastInsertId() {
if (databaseTypeResolver.isMySQL()) {
return "LAST_INSERT_ID()";
} else if (databaseTypeResolver.isPostgreSQL()) {
return "LASTVAL()";
} else {
throw new IllegalStateException("Database type is not supported for last insert id " + databaseTypeResolver.databaseType());
}
}
public String castChar(String sql) { public String castChar(String sql) {
if (databaseTypeResolver.isMySQL()) { if (databaseTypeResolver.isMySQL()) {
return format("CAST(%s AS CHAR)", sql); return format("CAST(%s AS CHAR)", sql);
@@ -258,7 +250,7 @@ public class DatabaseSpecificSQLGenerator {
return ""; return "";
} }
return "INSERT INTO " + escape(definition) + '(' + fields.stream().map(this::escape).collect(Collectors.joining(", ")) return "INSERT INTO " + escape(definition) + '(' + fields.stream().map(this::escape).collect(Collectors.joining(", "))
+ ") VALUES (" + fields.stream().map(e -> decoratePlaceHolder(headers, e, "?")).collect(Collectors.joining(", ")) + ')'; + ") VALUES (" + fields.stream().map(e -> decoratePlaceHolder(headers, e, "?")).collect(Collectors.joining(", ")) + ")";
} }
public String buildUpdate(@NotNull String definition, List<String> fields, Map<String, ResultsetColumnHeaderData> headers) { public String buildUpdate(@NotNull String definition, List<String> fields, Map<String, ResultsetColumnHeaderData> headers) {
@@ -282,4 +274,19 @@ public class DatabaseSpecificSQLGenerator {
} }
return placeHolder; return placeHolder;
} }
public Long fetchPK(GeneratedKeyHolder keyHolder) {
return switch (getDialect()) {
case POSTGRESQL -> (Long) keyHolder.getKeys().get("id");
case MYSQL -> {
// Mariadb
BigInteger generatedKey = (BigInteger) keyHolder.getKeys().get("insert_id");
if (generatedKey == null) {
// Mysql
generatedKey = (BigInteger) keyHolder.getKeys().get("GENERATED_KEY");
}
yield generatedKey.longValue();
}
};
}
} }
@@ -60,12 +60,15 @@ import jakarta.persistence.PersistenceException;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -120,6 +123,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -1322,17 +1326,21 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
params.add(scoreValue); params.add(scoreValue);
} }
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
final String sql = sqlGenerator.buildInsert(dataTableName, insertColumns, headersByName); final String sql = sqlGenerator.buildInsert(dataTableName, insertColumns, headersByName);
try { try {
int updated = jdbcTemplate.update(sql, params.toArray(Object[]::new)); int updated = jdbcTemplate.update(con -> {
PreparedStatement ps = con.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
setParameters(params, ps);
return ps;
}, keyHolder);
if (updated != 1) { if (updated != 1) {
throw new PlatformDataIntegrityException("error.msg.invalid.insert", "Expected one inserted row."); throw new PlatformDataIntegrityException("error.msg.invalid.insert", "Expected one inserted row.");
} }
Long resourceId = appTableId; Long resourceId = appTableId;
if (isMultirowDatatable(columnHeaders)) { if (isMultirowDatatable(columnHeaders)) {
resourceId = jdbcTemplate.queryForObject(DatabaseSpecificSQLGenerator.SELECT_CLAUSE.formatted(sqlGenerator.lastInsertId()), resourceId = sqlGenerator.fetchPK(keyHolder);
Long.class);
} }
return CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, resourceId); return CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, resourceId);
} catch (final DataAccessException dve) { } catch (final DataAccessException dve) {
@@ -1344,6 +1352,17 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
} }
private static void setParameters(ArrayList<Object> params, PreparedStatement ps) {
AtomicInteger parameterIndex = new AtomicInteger(1);
params.forEach(param -> {
try {
ps.setObject(parameterIndex.getAndIncrement(), param);
} catch (SQLException e) {
throw new IllegalArgumentException(e);
}
});
}
private static boolean isUserInsertable(@NotNull EntityTables entityTable, @NotNull ResultsetColumnHeaderData columnHeader) { private static boolean isUserInsertable(@NotNull EntityTables entityTable, @NotNull ResultsetColumnHeaderData columnHeader) {
String columnName = columnHeader.getColumnName(); String columnName = columnHeader.getColumnName();
return !columnHeader.getIsColumnPrimaryKey() && !CREATEDAT_FIELD_NAME.equals(columnName) && !UPDATEDAT_FIELD_NAME.equals(columnName) return !columnHeader.getIsColumnPrimaryKey() && !CREATEDAT_FIELD_NAME.equals(columnName) && !UPDATEDAT_FIELD_NAME.equals(columnName)
@@ -33,6 +33,7 @@ import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -50,10 +51,9 @@ public class CustomJobParameterRepositoryImpl implements CustomJobParameterRepos
.formatted(databaseSpecificSQLGenerator.castJson(":jsonString")); .formatted(databaseSpecificSQLGenerator.castJson(":jsonString"));
final String jsonString = gson.toJson(customJobParameters); final String jsonString = gson.toJson(customJobParameters);
SqlParameterSource parameters = new MapSqlParameterSource("jsonString", jsonString); SqlParameterSource parameters = new MapSqlParameterSource("jsonString", jsonString);
namedParameterJdbcTemplate.update(insertSQL, parameters); GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
final Long customParameterId = namedParameterJdbcTemplate.getJdbcTemplate().queryForObject( namedParameterJdbcTemplate.update(insertSQL, parameters, keyHolder);
DatabaseSpecificSQLGenerator.SELECT_CLAUSE.formatted(databaseSpecificSQLGenerator.lastInsertId()), Long.class); return databaseSpecificSQLGenerator.fetchPK(keyHolder);
return customParameterId;
} }
@Override @Override