I have a method that builds the add query into a postgres db. The problem is that these queries seem to be case sensitive in addition to having to do a lot of formatting to build this query, and I was wondering if there was a simpler way to write this code.
@Transactional
@NonNull
public ImportFileActivityOutput processAdd(ImportFileActivityInput input) {
try {
importFileConverter.convertImportFile(input.file(), input.requiredColumns(), input.delimiter())
.subscribe(
batch -> {
final var columnNames = new ArrayList<>(batch.get(0).keySet());
final var formattedColumnNames = columnNames.stream().map(col -> "\"" + col + "\"").collect(Collectors.joining(", "));
final var queryPlaceholders = columnNames.stream().map(col -> "?").collect(Collectors.joining(", "));
final var conflictColumns = input.primaryKeys().stream().map(pk -> "\"" + pk + "\"").collect(Collectors.joining(", ", "(", ")"));
final var insertQuery = String.format(
"INSERT INTO \"%s\".\"%s\" (%s) VALUES (%s) ON CONFLICT %s DO NOTHING",
SCHEMA, input.tableName(), formattedColumnNames, queryPlaceholders, conflictColumns
);
List<Object[]> batchArgs = batch.stream().map(row -> columnNames.stream().map(row::get).toArray(Object[]::new)).toList();
jdbcWrapper.executeBatchUpdate(insertQuery, batchArgs);
},
error -> {
log.error("Error during batch insertion for table {}: {}", input.tableName(), error.getMessage(), error);
throw new RuntimeException("Failed to process batch insert", error);
},
() -> log.info("Completed batch insertion for table: {}", input.tableName())
);
return new ImportFileActivityOutput(input.automationId(), ActivityStatus.SUCCESS, "add complete for table: " + input.tableName());
} catch (Exception e) {
log.error("Error during add operation for table {}: {}", input.tableName(), e.getMessage(), e);
return new ImportFileActivityOutput(input.automationId(), ActivityStatus.FAILURE, "Add failed: " + e.getMessage());
}
}
public void executeBatchUpdate(String query, List<Object[]> batchArgs) {
try {
jdbcTemplate.batchUpdate(query, batchArgs);
} catch (Exception e) {
log.error("Error executing batch update: {}", e.getMessage(), e);
throw new RuntimeException("Database operation failed", e);
}
}
convertImportFile returns something like [{id: id1, name: name1, job: job1},{id: id2, name: name2, job: job2}]
It just seems like it takes a lot of work to write out this query, with the black slashes, adding the ? and etc. are there some built in methods or something to simplify this process?
Btw is there anything to improve upon in this code in general? Does it make sense to create a separate function for the jdbc operations rather than stuffing it all inside processAdd()?
I have a method that builds the add query into a postgres db. The problem is that these queries seem to be case sensitive in addition to having to do a lot of formatting to build this query, and I was wondering if there was a simpler way to write this code.
@Transactional
@NonNull
public ImportFileActivityOutput processAdd(ImportFileActivityInput input) {
try {
importFileConverter.convertImportFile(input.file(), input.requiredColumns(), input.delimiter())
.subscribe(
batch -> {
final var columnNames = new ArrayList<>(batch.get(0).keySet());
final var formattedColumnNames = columnNames.stream().map(col -> "\"" + col + "\"").collect(Collectors.joining(", "));
final var queryPlaceholders = columnNames.stream().map(col -> "?").collect(Collectors.joining(", "));
final var conflictColumns = input.primaryKeys().stream().map(pk -> "\"" + pk + "\"").collect(Collectors.joining(", ", "(", ")"));
final var insertQuery = String.format(
"INSERT INTO \"%s\".\"%s\" (%s) VALUES (%s) ON CONFLICT %s DO NOTHING",
SCHEMA, input.tableName(), formattedColumnNames, queryPlaceholders, conflictColumns
);
List<Object[]> batchArgs = batch.stream().map(row -> columnNames.stream().map(row::get).toArray(Object[]::new)).toList();
jdbcWrapper.executeBatchUpdate(insertQuery, batchArgs);
},
error -> {
log.error("Error during batch insertion for table {}: {}", input.tableName(), error.getMessage(), error);
throw new RuntimeException("Failed to process batch insert", error);
},
() -> log.info("Completed batch insertion for table: {}", input.tableName())
);
return new ImportFileActivityOutput(input.automationId(), ActivityStatus.SUCCESS, "add complete for table: " + input.tableName());
} catch (Exception e) {
log.error("Error during add operation for table {}: {}", input.tableName(), e.getMessage(), e);
return new ImportFileActivityOutput(input.automationId(), ActivityStatus.FAILURE, "Add failed: " + e.getMessage());
}
}
public void executeBatchUpdate(String query, List<Object[]> batchArgs) {
try {
jdbcTemplate.batchUpdate(query, batchArgs);
} catch (Exception e) {
log.error("Error executing batch update: {}", e.getMessage(), e);
throw new RuntimeException("Database operation failed", e);
}
}
convertImportFile returns something like [{id: id1, name: name1, job: job1},{id: id2, name: name2, job: job2}]
It just seems like it takes a lot of work to write out this query, with the black slashes, adding the ? and etc. are there some built in methods or something to simplify this process?
Btw is there anything to improve upon in this code in general? Does it make sense to create a separate function for the jdbc operations rather than stuffing it all inside processAdd()?
Share Improve this question asked Mar 3 at 14:39 geegee 571 bronze badge 6 | Show 1 more comment2 Answers
Reset to default 2Your implementation leaves you vulnerable to sql injection attacks
You are "meta-crafting". So, no, there isn't 'a better way', and it looks weird because this isn't really what jdbc-template is for.
JDBC template is for making it easier (relative to raw JDBC, at least) to do DB queries, when the data is variable.
Data are things like:
gee
29231999
2025-03-03 14:39:02Z
'How to simplify writing postgres querys with jdbctemplate?'
And you might want to insert these things into a table, where the code that does this can 'hardcode' the column names and table names.
But that isn't what you're doing here. You don't have data. Instead, you have definitions, or meta-data. You have table names and column names. Your inputs are things like:
- table
Users
- table
Questions
- column
so_alias
- column
so_userid
- column
question_timestamp
- column
title
and you want to craft SQL that is based on these inputs.
jdbc-template isn't for this. For example, most DB JDBC drivers don't let you use the ?
mechanism to 'fill these in', and in that sense if any of your column or table names are sourced from potentially untrusted user input, you need to be careful because your code is extremely susceptible to SQL attacks, and the code you wrote in the post would be a security leak. I'm guessing the source of e.g. the columnNames
collection is 'trusted' and this isn't an issue, but the javadoc of this method should really scream that out to the caller, that they MUST ensure it is trusted or this code means any hacker will own the database, in its entirety.
What you're doing is essentially writing a JDBC-template esque library, specifically one that can arrange for table dumps / backups / recovery of those backups given as inputs 'table names and column names'. The code of jdbc-template, JDBI, JOOQ, etc look... exactly like your code looks.
If you want it to look 'less bad', you'd.. look around, check if you aren't reinventing a wheel here. The concept of 'just dump all tables with all columns values' surely has come up before. Or, find alternate ways to do what you want. For example, instead of running table dumps, maybe set up your DB to duplicate its WAL to another server so that you have a backup server that is as up to date as the main one, continuously, and can even 'hot-replace' the main server if the main server goes down. Less data, and far better uptime if failover is needed / far better 'recency' if backups are needed (any new data is backed up within a second, instead of having to wait for the nightly dump). Once you have that, it obviates the need for doing this in code.
FWIW, jdbc-template buys you next to nothing here, you might as well write it straight on top of JDBC and eliminate a middle-man dependency - a dependency that doesn't really address the thing you are doing in the first place.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745089172a4610584.html
where
clauses. – mr mcwolf Commented Mar 3 at 14:52SQLiteDatabase
. What are you actually trying to achieve? – mr mcwolf Commented Mar 3 at 15:16