企业的生产环境中,如果不允许直接操作数据表中的数据,则需要开发一个前台界面,在必要时实现对多个数据表中数据的增删改查,如下为实现修改数据的方案……
一、前后端数据交互设计
- 修改数据DTO设计
@Data
public class TableUpdateDTO {
private String tableName; // 表名
private String primaryKey; // 主键列名
private Object primaryKeyValue; // 主键值
private Map<String, Object> changes; // 修改的字段和值
@Data
public 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 {
@Autowired
private DataSource dataSource;
public int updateTableData(TableUpdateDTO updateDTO) throws SQLException {
validateUpdate(updateDTO);
// 构建动态SQL
String 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 {
@Autowired
private 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;
}
}