【Java】不允许直接操作数据表中的数据,开发前台界面来实现对多个数据表的增删改查
企业的生产环境中,如果不允许直接操作数据表中的数据,则需要开发一个前台界面,在必要时实现对多个数据表中数据的增删改查,如下为实现修改数据的方案……
一、前后端数据交互设计
- 修改数据DTO设计
@Data
public class TableUpdateDTO {private String tableName; // 表名private String primaryKey; // 主键列名private Object primaryKeyValue; // 主键值private Map<String, Object> changes; // 修改的字段和值@Datapublic static class BatchUpdateDTO {private String tableName;private List<TableUpdateDTO> updates;}
}
- 前端传参格式示例
{"tableName": "EMPLOYEES","primaryKey": "EMPLOYEE_ID","primaryKeyValue": 100,"changes": {"FIRST_NAME": "张","LAST_NAME": "三","EMAIL": "zhangsan@example.com","SALARY": 8500}
}
二、后端处理实现
- 数据更新服务层
@Service
@Transactional
public class OracleDataUpdateService {@Autowiredprivate DataSource dataSource;public int updateTableData(TableUpdateDTO updateDTO) throws SQLException {validateUpdate(updateDTO);// 构建动态SQLString sql = buildUpdateSql(updateDTO);try (Connection conn = dataSource.getConnection();PreparedStatement stmt = conn.prepareStatement(sql)) {// 设置参数int paramIndex = 1;for (Map.Entry<String, Object> entry : updateDTO.getChanges().entrySet()) {setParameter(stmt, paramIndex++, entry.getValue());}setParameter(stmt, paramIndex, updateDTO.getPrimaryKeyValue());return stmt.executeUpdate();}}private String buildUpdateSql(TableUpdateDTO updateDTO) {StringBuilder sql = new StringBuilder("UPDATE ").append(updateDTO.getTableName()).append(" SET ");// 添加SET子句updateDTO.getChanges().keySet().forEach(col -> sql.append(col).append(" = ?, "));// 移除最后一个逗号sql.setLength(sql.length() - 2);// 添加WHERE条件sql.append(" WHERE ").append(updateDTO.getPrimaryKey()).append(" = ?");return sql.toString();}private void setParameter(PreparedStatement stmt, int index, Object value) throws SQLException {// 处理Oracle特定类型if (value instanceof java.util.Date) {stmt.setTimestamp(index, new Timestamp(((java.util.Date)value).getTime()));} else if (value instanceof String && ((String)value).length() > 4000) {// 处理大文本stmt.setCharacterStream(index, new StringReader((String)value), ((String)value).length());} else {stmt.setObject(index, value);}}private void validateUpdate(TableUpdateDTO updateDTO) {// 表名安全校验if (!updateDTO.getTableName().matches("[A-Za-z0-9_]+")) {throw new IllegalArgumentException("非法的表名");}// 检查主键if (updateDTO.getPrimaryKey() == null || updateDTO.getPrimaryKeyValue() == null) {throw new IllegalArgumentException("必须指定主键");}// 检查修改字段if (updateDTO.getChanges() == null || updateDTO.getChanges().isEmpty()) {throw new IllegalArgumentException("未指定修改字段");}}
}
- 控制器层实现
@RestController
@RequestMapping("/api/oracle")
public class OracleDataUpdateController {@Autowiredprivate OracleDataUpdateService updateService;@PutMapping("/update")public ResponseEntity<?> updateData(@RequestBody TableUpdateDTO updateDTO) {try {int affected = updateService.updateTableData(updateDTO);return ResponseEntity.ok(Map.of("success", true, "message", "更新成功", "affectedRows", affected));} catch (Exception e) {return ResponseEntity.badRequest().body(Map.of("success", false, "message", e.getMessage()));}}@PutMapping("/batch-update")public ResponseEntity<?> batchUpdate(@RequestBody TableUpdateDTO.BatchUpdateDTO batchUpdate) {try {List<Integer> results = new ArrayList<>();for (TableUpdateDTO updateDTO : batchUpdate.getUpdates()) {results.add(updateService.updateTableData(updateDTO));}return ResponseEntity.ok(Map.of("success", true, "message", "批量更新成功", "results", results));} catch (Exception e) {return ResponseEntity.badRequest().body(Map.of("success", false, "message", e.getMessage()));}}
}
三、前端实现方案
- Vue.js表格编辑组件
<template><div><table class="editable-table"><thead><tr><th v-for="col in columns" :key="col.columnName">{{ col.columnName }}</th><th>操作</th></tr></thead><tbody><tr v-for="(row, index) in rows" :key="index"><td v-for="col in columns" :key="col.columnName"><template v-if="editingRow === index && col.editable !== false"><input v-model="editingData[col.columnName]" :type="getInputType(col.dataType)"></template><template v-else>{{ formatValue(row[col.columnName]) }}</template></td><td><template v-if="editingRow === index"><button @click="saveEdit(index)">保存</button><button @click="cancelEdit">取消</button></template><button v-else @click="startEdit(index, row)">编辑</button></td></tr></tbody></table></div>
</template><script>
export default {props: {columns: Array, // 列元数据rows: Array // 表数据},data() {return {editingRow: null,editingData: {},primaryKey: 'ID' // 根据实际情况设置主键};},methods: {startEdit(index, row) {this.editingRow = index;this.editingData = {...row};},cancelEdit() {this.editingRow = null;this.editingData = {};},async saveEdit(index) {try {const updateDTO = {tableName: this.tableName,primaryKey: this.primaryKey,primaryKeyValue: this.rows[index][this.primaryKey],changes: this.getChanges(this.rows[index], this.editingData)};const response = await this.$axios.put('/api/oracle/update', updateDTO);if (response.data.success) {// 更新本地数据this.$emit('update-row', index, this.editingData);this.cancelEdit();this.$message.success('更新成功');} else {this.$message.error(response.data.message);}} catch (error) {this.$message.error('更新失败: ' + error.message);}},getChanges(original, edited) {const changes = {};for (const key in edited) {if (!this.deepEqual(original[key], edited[key])) {changes[key] = edited[key];}}return changes;},deepEqual(a, b) {// 实现深度比较return JSON.stringify(a) === JSON.stringify(b);},formatValue(value) {if (value === null || value === undefined) return 'NULL';if (value instanceof Object) return JSON.stringify(value);return value;},getInputType(dataType) {// 根据Oracle数据类型返回合适的input类型const typeMap = {'NUMBER': 'number','DATE': 'datetime-local','VARCHAR2': 'text','CHAR': 'text','CLOB': 'textarea','BLOB': 'file'};return typeMap[dataType] || 'text';}}
};
</script><style scoped>
.editable-table {width: 100%;border-collapse: collapse;
}
.editable-table th, .editable-table td {border: 1px solid #ddd;padding: 8px;
}
.editable-table th {background-color: #f2f2f2;
}
.editable-table input {width: 90%;padding: 5px;
}
</style>
- 批量编辑实现
// 在父组件中
methods: {async handleBatchSave(changedRows) {try {const batchUpdate = {tableName: this.tableName,updates: changedRows.map(row => ({tableName: this.tableName,primaryKey: this.primaryKey,primaryKeyValue: row[this.primaryKey],changes: row.changes}))};const response = await this.$axios.put('/api/oracle/batch-update', batchUpdate);if (response.data.success) {this.$message.success(`成功更新 ${response.data.results.length} 条记录`);this.loadTableData(); // 重新加载数据} else {this.$message.error(response.data.message);}} catch (error) {this.$message.error('批量更新失败: ' + error.message);}}
}
四、安全增强措施
- 字段权限控制
// 在服务层添加字段白名单检查
private void validateUpdateFields(String tableName, Set<String> fields) {Set<String> allowedFields = getAllowedFields(tableName);for (String field : fields) {if (!allowedFields.contains(field.toUpperCase())) {throw new IllegalArgumentException("不允许修改字段: " + field);}}
}private Set<String> getAllowedFields(String tableName) {// 可以从配置文件中加载每个表允许修改的字段Map<String, Set<String>> tableFields = new HashMap<>();tableFields.put("EMPLOYEES", Set.of("FIRST_NAME", "LAST_NAME", "EMAIL", "SALARY"));// 其他表配置...return tableFields.getOrDefault(tableName.toUpperCase(), Collections.emptySet());
}
- 数据验证
// 添加数据验证逻辑
private void validateFieldValue(String tableName, String field, Object value) {// 1. 非空检查if (value == null && isNotNullField(tableName, field)) {throw new IllegalArgumentException(field + " 不能为空");}// 2. 数据类型检查if (value != null) {String expectedType = getFieldType(tableName, field);if (!isTypeMatch(expectedType, value)) {throw new IllegalArgumentException(field + " 类型不匹配");}}// 3. 业务规则检查if ("SALARY".equalsIgnoreCase(field) && value instanceof Number) {double salary = ((Number)value).doubleValue();if (salary < 0) {throw new IllegalArgumentException("薪资不能为负数");}}
}
五、高级功能扩展
- 乐观锁控制
// 在DTO中添加版本号字段
@Data
public class TableUpdateDTO {private String versionColumn; // 版本号列名(如VERSION)private Object versionValue; // 当前版本号值// 其他字段...
}// 在SQL构建中添加版本检查
private String buildUpdateSql(TableUpdateDTO updateDTO) {StringBuilder sql = new StringBuilder("UPDATE ").append(updateDTO.getTableName()).append(" SET ");updateDTO.getChanges().keySet().forEach(col -> sql.append(col).append(" = ?, "));// 添加版本号自增if (updateDTO.getVersionColumn() != null) {sql.append(updateDTO.getVersionColumn()).append(" = ").append(updateDTO.getVersionColumn()).append(" + 1, ");}sql.setLength(sql.length() - 2);sql.append(" WHERE ").append(updateDTO.getPrimaryKey()).append(" = ?");// 添加版本检查条件if (updateDTO.getVersionColumn() != null) {sql.append(" AND ").append(updateDTO.getVersionColumn()).append(" = ?");}return sql.toString();
}
- 修改历史记录
// 使用AOP记录数据修改
@Aspect
@Component
public class DataUpdateAuditAspect {@AfterReturning(pointcut = "execution(* com..OracleDataUpdateService.updateTableData(..)) && args(updateDTO)",returning = "affected")public void auditUpdate(TableUpdateDTO updateDTO, int affected) {if (affected > 0) {DataChangeLog log = new DataChangeLog();log.setTableName(updateDTO.getTableName());log.setRecordId(String.valueOf(updateDTO.getPrimaryKeyValue()));log.setChangeContent(JSON.toJSONString(updateDTO.getChanges()));log.setChangeTime(new Date());log.setChangeUser(getCurrentUser());changeLogRepository.save(log);}}
}
六、错误处理与用户体验
- 后端统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(SQLException.class)public ResponseEntity<?> handleSqlException(SQLException e) {String message = "数据库操作失败";// Oracle特定错误码处理if (e.getErrorCode() == 1) {message = "违反唯一约束: " + e.getMessage();} else if (e.getErrorCode() == 2292) {message = "违反外键约束: " + e.getMessage();}return ResponseEntity.badRequest().body(Map.of("success", false, "message", message));}@ExceptionHandler(DataIntegrityViolationException.class)public ResponseEntity<?> handleDataIntegrityViolation() {return ResponseEntity.badRequest().body(Map.of("success", false, "message", "数据完整性校验失败,请检查输入数据"));}
}
- 前端错误反馈
// 在Vue组件中增强错误处理
async saveEdit(index) {this.$loading = true;try {const response = await this.$axios.put('/api/oracle/update', this.updateDTO);if (response.data.success) {this.$emit('update-row', index, this.editingData);this.cancelEdit();this.$notify({title: '成功',message: '数据更新成功',type: 'success'});} else {this.$notify.error({title: '错误',message: response.data.message});}} catch (error) {let message = error.message;if (error.response && error.response.data) {message = error.response.data.message || message;}this.$notify.error({title: '请求失败',message: `更新失败: ${message}`});} finally {this.$loading = false;}
}