【Java】不允许直接操作数据表中的数据,开发前台界面来实现对多个数据表的增删改查

发布于:2025-08-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

企业的生产环境中,如果不允许直接操作数据表中的数据,则需要开发一个前台界面,在必要时实现对多个数据表中数据的增删改查,如下为实现修改数据的方案……

一、前后端数据交互设计

  1. 修改数据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;
    }
}
  1. 前端传参格式示例
{
  "tableName": "EMPLOYEES",
  "primaryKey": "EMPLOYEE_ID",
  "primaryKeyValue": 100,
  "changes": {
    "FIRST_NAME": "张",
    "LAST_NAME": "三",
    "EMAIL": "zhangsan@example.com",
    "SALARY": 8500
  }
}

二、后端处理实现

  1. 数据更新服务层
@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("未指定修改字段");
        }
    }
}
  1. 控制器层实现
@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()));
        }
    }
}

三、前端实现方案

  1. 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>
  1. 批量编辑实现
// 在父组件中
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);
    }
  }
}

四、安全增强措施

  1. 字段权限控制
// 在服务层添加字段白名单检查
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());
}
  1. 数据验证
// 添加数据验证逻辑
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("薪资不能为负数");
        }
    }
}

五、高级功能扩展

  1. 乐观锁控制
// 在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();
}
  1. 修改历史记录
// 使用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);
        }
    }
}

六、错误处理与用户体验

  1. 后端统一异常处理
@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", "数据完整性校验失败,请检查输入数据"));
    }
}
  1. 前端错误反馈
// 在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;
  }
}