Jfinal中使用Db.batchUpdate报错

博客记录了JFinal使用中出现的java.lang.ClassCastException错误,具体为com.core.common.model.Clazz无法转换为com.jfinal.plugin.activerecord.Record,给出了报错位置。同时提到通过翻看源码有三种解决方案,并提供了引用链接。
  1. 错误代码   
List clazzList = new ArrayList<>();
for (Long classId : classIds){
	Clazz clazz = Clazz.dao.findById(classId);
	clazz.setProductLineId(productLineId);
	clazzList.add(clazz);
}
Db.batchUpdate("clazz", clazzList, clazzList.size());

报错:java.lang.ClassCastException: com.core.common.model.Clazz cannot be cast to                                                              com.jfinal.plugin.activerecord.Record
    at com.jfinal.plugin.activerecord.DbPro.batchUpdate(DbPro.java:1190)
    at com.jfinal.plugin.activerecord.DbPro.batchUpdate(DbPro.java:1216)
    at com.jfinal.plugin.activerecord.Db.batchUpdate(Db.java:620)

   2.解决方案: 

通过翻看源码可以发现,有三种方案,

public static int[] batchUpdate(List<? extends Model> modelList, int batchSize) 
public static int[] batchUpdate(String tableName, String primaryKey, List<Record> recordList, int batchSize) 
public static int[] batchUpdate(String tableName, List<Record> recordList, int batchSize)
按照自己的要求来采用使用的方式。

引用链接http://www.jfinal.com/feedback/1668

数据插入失败,正在进行第2次重试: StatementCallback; uncategorized SQLException for SQL [INSERT INTO my_battery_info_M240910700000001 USING my_battery_info TAGS ('M240910700000001') (ts, id, battery_voltage, battery_current, battery_soc, battery_hard_version, battery_soft_version, battery_work_mode, battery_protect_code, battery_error_code, battery_temperature_max, battery_temperature_min, battery_voltage_max, battery_voltage_min, mos_status, mos_temp, battery_cycle_times, steady_status, cell_voltage, model, manufacture, imei, imsi, iccid, tracker_hardware_version, tracker_software_version, csq, network_type, location_mode, longitude, longitude_direction, latitude, latitude_direction, gps_speed, gps_signal, satellite_num, accuracy, flag, client_id, topic, product_key, handled, todo_now, needack, businessmode, upload_time, create_time, update_time, payload, project_id, sn, soh, battery_temperature_avg, charge_mode, low_battery_warn, charge_current_max, discharge_current_max, charge_current_avg, discharge_current_avg, gps_cog, total_charge_energy, charge_cycles_history, charge_capacity_history, discharge_capacity_history, ntc1, ntc2, ntc3, ntc4, tracker_communication_status, battery_kind, dsoc, correction_rate, tsoc, bsoc, fcc, actual_soh, charging_sop, discharging_sop, task_max_delay_1ms, task_max_delay_10ms, task_max_delay_100ms, task_max_delay_500ms, task_max_time_1ms, task_max_time_10ms, task_max_time_100ms, task_max_time_500ms, empty_count, cell_info, gps_max_signal, msg_delay_time, data_type, battery_vehicle_status, storage_status, ota_status, lock_status, battery_vehicle_alarm_status) VALUES ('2024-09-23 08:58:06.000', 320, 70000, 0, 0, NULL, 'V1.00', '48', NULL, '000000000008000000000004', 24, -31, 3199, 3177, 8, 25, 0, NULL, NULL, NULL, NULL, '865271061996824', NULL, '898607F01124F9007644', NULL, '2024-09-20 14:35', NULL, NULL, NULL, '0', 'E', '0', 'N', NULL, '9', NULL, NULL, 1, NULL, NULL, NULL, 0, NULL, NULL, NULL, '2024-09-23 08:58:06.000', '2024-09-23 13:54:31.000', '2024-09-23 13:54:31.000', '{"ts":1727053086,"code":200,"data":[1,0,1,0,0,0,0,25,225,26,2,188,12,127,12,105,48,8,0,0,0,0,0,0,0,8,0,0,0,0,0,4,105,120],"tracker":[0,0,9,"865271061996824","898607F01124F9007644","2024-09-20 14:35"]}', 38, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0)]; SQL state []; error code [9730]; TDengine ERROR (0x2602): sql: INSERT INTO my_battery_info_M240910700000001 USING my_battery_info TAGS ('M240910700000001') (ts, id, battery_voltage, battery_current, battery_soc, battery_hard_version, battery_soft_version, battery_work_mode, battery_protect_code, battery_error_code, battery_temperature_max, battery_temperature_min, battery_voltage_max, battery_voltage_min, mos_status, mos_temp, battery_cycle_times, steady_status, cell_voltage, model, manufacture, imei, imsi, iccid, tracker_hardware_version, tracker_software_version, csq, network_type, location_mode, longitude, longitude_direction, latitude, latitude_direction, gps_speed, gps_signal, satellite_num, accuracy, flag, client_id, topic, product_key, handled, todo_now, needack, businessmode, upload_time, create_time, update_time, payload, project_id, sn, soh, battery_temperature_avg, charge_mode, low_battery_warn, charge_current_max, discharge_current_max, charge_current_avg, discharge_current_avg, gps_cog, total_charge_energy, charge_cycles_history, charge_capacity_history, discharge_capacity_history, ntc1, ntc2, ntc3, ntc4, tracker_communication_status, battery_kind, dsoc, correction_rate, tsoc, bsoc, fcc, actual_soh, charging_sop, discharging_sop, task_max_delay_1ms, task_max_delay_10ms, task_max_delay_100ms, task_max_delay_500ms, task_max_time_1ms, task_max_time_10ms, task_max_time_100ms, task_max_time_500ms, empty_count, cell_info, gps_max_signal, msg_delay_time, data_type, battery_vehicle_status, storage_status, ota_status, lock_status, battery_vehicle_alarm_status) VALUES ('2024-09-23 08:58:06.000', 320, 70000, 0, 0, NULL, 'V1.00', '48', NULL, '000000000008000000000004', 24, -31, 3199, 3177, 8, 25, 0, NULL, NULL, NULL, NULL, '865271061996824', NULL, '898607F01124F9007644', NULL, '2024-09-20 14:35', NULL, NULL, NULL, '0', 'E', '0', 'N', NULL, '9', NULL, NULL, 1, NULL, NULL, NULL, 0, NULL, NULL, NULL, '2024-09-23 08:58:06.000', '2024-09-23 13:54:31.000', '2024-09-23 13:54:31.000', '{"ts":1727053086,"code":200,"data":[1,0,1,0,0,0,0,25,225,26,2,188,12,127,12,105,48,8,0,0,0,0,0,0,0,8,0,0,0,0,0,4,105,120],"tracker":[0,0,9,"865271061996824","898607F01124F9007644","2024-09-20 14:35"]}', 38, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0), desc: Invalid column name: id,超级表的创建sql是:CREATE STABLE IF NOT EXISTS my_battery_info ( ts TIMESTAMP, battery_voltage INT, battery_current INT, battery_soc INT, battery_hard_version BINARY(100), battery_soft_version BINARY(100), battery_work_mode BINARY(100), battery_protect_code BINARY(100), battery_error_code BINARY(100), battery_temperature_max INT, battery_temperature_min INT, battery_voltage_max INT, battery_voltage_min INT, mos_status INT, mos_temp INT, battery_cycle_times INT, steady_status INT, cell_voltage BINARY(500), model BINARY(100), manufacture BINARY(100), imei BINARY(100), imsi BINARY(100), iccid BINARY(100), tracker_hardware_version BINARY(100), tracker_software_version BINARY(100), csq BINARY(100), network_type BINARY(100), location_mode BINARY(100), longitude BINARY(100), longitude_direction BINARY(100), latitude BINARY(100), latitude_direction BINARY(100), gps_speed BINARY(100), gps_signal BINARY(100), satellite_num BINARY(100), accuracy BINARY(100), flag INT, client_id BINARY(100), `topic` BINARY(100), product_key BINARY(100), handled INT, todo_now INT, needack TINYINT, businessmode BINARY(100), create_time TIMESTAMP, update_time TIMESTAMP, payload BINARY(5000), project_id INT, sn BINARY(100), soh INT, battery_temperature_avg INT, charge_mode INT, low_battery_warn INT, charge_current_max INT, discharge_current_max INT, charge_current_avg INT, discharge_current_avg INT, gps_cog BINARY(100), total_charge_energy DOUBLE, charge_cycles_history INT, charge_capacity_history DOUBLE, discharge_capacity_history DOUBLE, ntc1 INT, ntc2 INT, ntc3 INT, ntc4 INT, tracker_communication_status BINARY(100), battery_kind BINARY(100), dsoc INT, correction_rate INT, tsoc INT, bsoc INT, fcc INT, actual_soh INT, charging_sop INT, discharging_sop INT, task_max_delay_1ms INT, task_max_delay_10ms INT, task_max_delay_100ms INT, task_max_delay_500ms INT, task_max_time_1ms INT, task_max_time_10ms INT, task_max_time_100ms INT, task_max_time_500ms INT, empty_count INT, cell_info BINARY(5000), gps_max_signal INT, msg_delay_time INT, data_type INT, battery_vehicle_status INT, storage_status INT, ota_status INT, lock_status INT, battery_vehicle_alarm_status INT ) TAGS ( battery_id BINARY(100) );。然后Java代码是:package com.bms.sync.data.service; import com.bms.sync.data.util.TimeUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.Arrays; @Service public class DataMigrationService { private final JdbcTemplate mysqlJdbcTemplate; private final JdbcTemplate tdengineJdbcTemplate; @Value("${table.tag.columns}") private String tagColumnsConfig; @Value("${table.timestamp.field}") private String timestampField; // TDengine时间戳字段名 @Value("${table.tdengine.timestamp.field}") private String tdengineTimestampField; @Value("${performance.batch-size.historical}") private int historicalBatchSize; @Value("${performance.batch-size.incremental}") private int incrementalBatchSize; // 重试配置 @Value("${retry.max-attempts}") private int maxRetries; @Value("${retry.interval}") private long retryInterval; @Autowired private PerformanceMonitorService performanceMonitorService; @Autowired private TableAutoCreateService tableAutoCreateService; @Autowired private RedisQueueService redisQueueService; @Autowired public DataMigrationService(JdbcTemplate mysqlJdbcTemplate, JdbcTemplate tdengineJdbcTemplate, PerformanceMonitorService performanceMonitorService, TableAutoCreateService tableAutoCreateService, RedisQueueService redisQueueService) { this.mysqlJdbcTemplate = mysqlJdbcTemplate; this.tdengineJdbcTemplate = tdengineJdbcTemplate; this.performanceMonitorService = performanceMonitorService; this.tableAutoCreateService = tableAutoCreateService; this.redisQueueService = redisQueueService; } /** * 迁移数据(支持批量处理) * @param mysqlTableName MySQL表名 * @param superTableName TDengine超级表名 */ public void migrateData(String mysqlTableName, String superTableName) { long startTime = System.currentTimeMillis(); try { System.out.println("开始数据迁移: " + mysqlTableName + " -> " + superTableName); // 确保表结构已创建 System.out.println("步骤1: 确保表结构已创建"); ensureTableStructureExists(mysqlTableName, superTableName); // 1. 获取总记录数 System.out.println("步骤2: 获取总记录数"); int totalRecords = getTotalRecordCount(mysqlTableName); if (totalRecords == 0) { System.out.println("没有数据需要迁移"); return; } System.out.println("总记录数: " + totalRecords); // 2. 分批迁移数据(增加批处理大小以提高效率) int batchSize = historicalBatchSize; // 每批处理5000条记录 int totalBatches = (int) Math.ceil((double) totalRecords / batchSize); System.out.println("总批次数: " + totalBatches + ",批处理大小: " + batchSize); // 使用Redis队列处理历史数据 System.out.println("步骤3: 使用Redis队列处理历史数据"); processHistoricalDataWithQueue(mysqlTableName, superTableName, totalRecords, batchSize); System.out.println("数据迁移完成"); } catch (Exception e) { System.err.println("数据迁移失败: " + e.getMessage()); e.printStackTrace(); } finally { long endTime = System.currentTimeMillis(); performanceMonitorService.recordTime("data.migration", endTime - startTime); performanceMonitorService.incrementCounter("data.migration.totalRecords", getTotalRecordCount(mysqlTableName)); System.out.println("数据迁移总耗时: " + (endTime - startTime) + "ms"); } } /** * 确保表结构已创建 * @param mysqlTableName MySQL表名 * @param superTableName TDengine超级表名 */ public void ensureTableStructureExists(String mysqlTableName, String superTableName) { try { System.out.println("确保表结构存在: " + mysqlTableName + " -> " + superTableName); // 从配置中获取标签列 String[] tagColumnsArray = tagColumnsConfig.split(","); List<String> tagColumns = Arrays.asList(tagColumnsArray); // 自动创建表结构 tableAutoCreateService.autoCreateTableStructure(mysqlTableName, superTableName, tagColumns); System.out.println("表结构创建完成"); } catch (Exception e) { System.err.println("确保表结构存在时失败: " + e.getMessage()); e.printStackTrace(); } } /** * 使用Redis队列处理历史数据 * @param mysqlTableName MySQL表名 * @param superTableName TDengine超级表名 * @param totalRecords 总记录数 * @param batchSize 批处理大小 */ private void processHistoricalDataWithQueue(String mysqlTableName, String superTableName, int totalRecords, int batchSize) { try { String queueName = mysqlTableName + "_to_" + superTableName; // 清空之前的队列数据 redisQueueService.clearQueue(queueName); // 使用基于时间戳的分页方式替代LIMIT/OFFSET,避免数据遗漏 String lastProcessedTime = null; String currentTime = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); long startTime = System.currentTimeMillis(); int totalProcessed = 0; boolean hasMoreData = true; while (hasMoreData) { // 构建查询SQL,使用时间戳分页 StringBuilder querySql = new StringBuilder(); querySql.append("SELECT * FROM ").append(mysqlTableName); // 添加时间范围条件,确保时间字段不为空 querySql.append(" WHERE ").append(timestampField).append(" IS NOT NULL"); // 确保时间字段不为空 if (lastProcessedTime != null && !lastProcessedTime.isEmpty()) { querySql.append(" AND ").append(timestampField).append(" > '").append(lastProcessedTime).append("'"); } querySql.append(" ORDER BY ").append(timestampField).append(" ASC"); List<Map<String, Object>> records = mysqlJdbcTemplate.queryForList(querySql.toString()); if (!records.isEmpty()) { // 将数据放入Redis队列 redisQueueService.pushHistoricalDataToQueue(queueName, records); // 更新最大时间戳 Object lastTimestamp = records.get(records.size() - 1).get(timestampField); if (lastTimestamp != null) { lastProcessedTime = lastTimestamp.toString(); } totalProcessed += records.size(); System.out.println("已将 " + records.size() + " 条记录放入Redis队列,总计已处理: " + totalProcessed); } else { hasMoreData = false; } } long endTime = System.currentTimeMillis(); System.out.println("历史数据已全部放入Redis队列,队列大小: " + redisQueueService.getQueueSize(queueName) + ",耗时: " + (endTime - startTime) + "ms"); // 从Redis队列中取出数据并同步到TDengine int successCount = syncDataFromQueue(queueName, superTableName); System.out.println("历史数据同步完成,总计成功同步记录数: " + successCount); } catch (Exception e) { System.err.println("使用Redis队列处理历史数据失败: " + e.getMessage()); e.printStackTrace(); } } /** * 从Redis队列中取出数据并同步到TDengine * @param queueName 队列名称 * @param superTableName TDengine超级表名 * @return 成功同步的记录数 */ private int syncDataFromQueue(String queueName, String superTableName) { int totalSynced = 0; int batchSize = incrementalBatchSize; // 从队列中每次取出100条记录进行同步 long startTime = System.currentTimeMillis(); System.out.println("开始从Redis队列同步数据到TDengine,批处理大小: " + batchSize); int batchCount = 0; while (redisQueueService.getQueueSize(queueName) > 0) { // 先查看但不移除队列中的数据 List<Object> records = redisQueueService.peekBatchFromQueue(queueName, batchSize); if (records != null && !records.isEmpty()) { // 转换为正确的类型 List<Map<String, Object>> convertedRecords = (List<Map<String, Object>>) (List<?>) records; // 批量插入到TDengine并统计成功插入的记录数 int successCount = batchInsertIntoTDengine(superTableName, convertedRecords); totalSynced += successCount; batchCount++; // 只有在数据成功同步后才从队列中移除 if (successCount > 0) { // 从队列中移除已成功处理的数据 redisQueueService.removeBatchFromQueue(queueName, successCount); } // 每处理10批打印一次进度信息 if (batchCount % 10 == 0) { System.out.println("已同步 " + batchCount + " 批次数据,当前批次成功记录数: " + successCount + ",总计已同步: " + totalSynced); } } } long endTime = System.currentTimeMillis(); // 只有在有数据成功同步时才打印完成信息 if (totalSynced > 0) { System.out.println("从Redis队列同步数据完成,总计同步: " + totalSynced + " 条记录,总批次数: " + batchCount + ",耗时: " + (endTime - startTime) + "ms"); } else { System.out.println("Redis队列中没有数据需要同步"); } return totalSynced; } /** * 获取记录数 * @param mysqlTableName MySQL表名 * @return 记录数 */ public int getRecordCount(String mysqlTableName) { // 直接从数据库获取,不使用缓存 String countSql = "SELECT COUNT(*) FROM " + mysqlTableName; Integer count = mysqlJdbcTemplate.queryForObject(countSql, Integer.class); return count != null ? count : 0; } /** * 获取总记录数 * @param tableName 表名 * @return 总记录数 */ public int getTotalRecordCount(String tableName) { // 直接从数据库获取,不使用缓存 String countSql = "SELECT COUNT(*) FROM " + tableName; Integer count = mysqlJdbcTemplate.queryForObject(countSql, Integer.class); return count != null ? count : 0; } /** * 批量插入数据到TDengine * @param superTableName TDengine超级表名 * @param records 记录列表 * @return 成功插入的记录数 */ private int batchInsertIntoTDengine(String superTableName, List<Map<String, Object>> records) { if (records.isEmpty()) { return 0; } int successCount = 0; try { // 一次性检查并创建所有需要的子表 ensureAllChildTablesExist(superTableName, records); // 然后批量插入数据 for (Map<String, Object> record : records) { try { insertIntoTDengine(superTableName, record); successCount++; } catch (Exception e) { System.err.println("单条记录插入失败: " + e.getMessage()); } } } catch (Exception e) { System.err.println("批量数据插入失败: " + e.getMessage()); // 不打印完整堆栈,减少日志输出 } return successCount; } /** * 一次性检查并创建所有需要的子表 * @param superTableName 超级表名 * @param records 记录列表 */ private void ensureAllChildTablesExist(String superTableName, List<Map<String, Object>> records) { try { // 首先确保超级表存在 // 不使用缓存检查超级表是否存在 // 直接确保超级表存在 ensureSuperTableExists(superTableName); // 不使用缓存存储超级表存在信息 // 收集所有需要的子表名 java.util.Set<String> childTableNames = new java.util.HashSet<>(); java.util.Map<String, String> childTableToTagMap = new java.util.HashMap<>(); String tagColumn = tagColumnsConfig; for (Map<String, Object> record : records) { Object tagValueObj = record.get(tagColumn); String tagValue = (tagValueObj != null) ? tagValueObj.toString() : "null"; String childTableName = superTableName + "_" + tagValue; // 仅处理缓存中不存在的表 String cacheKey = "tdengine:table:" + childTableName; // 不使用缓存,直接检查表是否存在 if (!childTableNames.contains(childTableName)) { childTableNames.add(childTableName); childTableToTagMap.put(childTableName, tagValue); } } // 一次性查询所有子表是否存在 if (!childTableNames.isEmpty()) { // 使用更高效的方式检查表是否存在 for (String childTableName : childTableNames) { if (!isTableExists(childTableName)) { String tagValue = childTableToTagMap.get(childTableName); createChildTable(superTableName, childTableName, tagValue); } else { // 缓存已存在的表信息 // 不使用缓存存储表存在信息 } } } } catch (Exception e) { System.err.println("批量检查子表存在时失败: " + e.getMessage()); // 不打印完整堆栈,减少日志输出 } } /** * 检查TDengine表是否存在 * @param tableName 表名 * @return 是否存在 */ private boolean isTableExists(String tableName) { try { String checkSql = "SELECT table_name FROM information_schema.ins_tables WHERE table_name = ?"; List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(checkSql, tableName); return !result.isEmpty(); } catch (Exception e) { System.err.println("检查表是否存在时出错: " + e.getMessage()); return false; } } /** * 创建子表 * @param superTableName 超级表名 * @param childTableName 子表名 * @param tagValue 标签值 */ private void createChildTable(String superTableName, String childTableName, String tagValue) { try { String createChildTableSql = "CREATE TABLE IF NOT EXISTS " + childTableName + " USING " + superTableName + " TAGS ('" + tagValue + "')"; tdengineJdbcTemplate.execute(createChildTableSql); // 缓存表存在信息 // 不使用缓存存储表存在信息 System.out.println("创建子表: " + childTableName); } catch (Exception e) { System.err.println("创建子表失败: " + e.getMessage()); } } /** * 插入数据到TDengine * @param superTableName TDengine超级表名 * @param record 记录 */ public void insertIntoTDengine(String superTableName, Map<String, Object> record) { long startTime = System.currentTimeMillis(); int retryCount = 0; while (retryCount < maxRetries) { try { // 获取标签列名(从配置中获取) String tagColumn = tagColumnsConfig; // 获取标签值 Object tagValueObj = record.get(tagColumn); String tagValue = (tagValueObj != null) ? tagValueObj.toString() : "null"; // 生成子表名(使用标签值) String childTableName = superTableName + "_" + tagValue; // 优化:仅在缓存中不存在时检查子表 String cacheKey = "tdengine:table:" + childTableName; // 不使用缓存检查表是否存在 // 直接检查表是否存在 if (!isTableExists(childTableName)) { // 确保子表存在 ensureChildTableExists(superTableName, childTableName, tagValue); // 缓存表存在信息,避免频繁检查 // 不使用缓存存储表存在信息 } // 构建SQL StringBuilder sql = new StringBuilder(); sql.append("INSERT INTO ").append(childTableName).append(" USING ").append(superTableName) .append(" TAGS ('").append(tagValue).append("') (").append(tdengineTimestampField).append(", "); // 添加列名 boolean first = true; for (String columnName : record.keySet()) { // 跳过标签列和时间戳字段 if (tagColumn.equals("id")||tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) { continue; } if (!first) { sql.append(", "); } // 处理关键字冲突 sql.append(escapeKeyword(columnName)); first = false; } // 获取时间戳字段的值 Object timestampValue = record.get(timestampField); if (timestampValue == null) { // 如果时间戳为空,跳过这条记录 System.err.println("警告: 记录的时间戳字段为空,跳过该记录"); return; } String formattedTimestamp; if (timestampValue instanceof Timestamp) { // 格式化时间戳 Timestamp ts = (Timestamp) timestampValue; formattedTimestamp = "'" + TimeUtils.formatWithMillis(TimeUtils.from(ts)) + "'"; } else if (timestampValue instanceof LocalDateTime) { // 格式化LocalDateTime LocalDateTime ldt = (LocalDateTime) timestampValue; formattedTimestamp = "'" + TimeUtils.formatWithMillis(ldt) + "'"; } else { // 其他情况使用字符串值 String timestampStr = timestampValue.toString(); // 检查是否需要调整毫秒格式 if (timestampStr.matches(".*\\.\\d{3}$")) { // 如果已经包含毫秒,直接使用 formattedTimestamp = "'" + timestampStr + "'"; } else if (timestampStr.matches(".*\\.\\d{1,2}$")) { // 如果毫秒部分不足3位,补零 String[] parts = timestampStr.split("\\."); if (parts.length == 2) { String millis = parts[1]; while (millis.length() < 3) { millis += "0"; } formattedTimestamp = "'" + parts[0] + "." + millis + "'"; } else { formattedTimestamp = "'" + timestampStr + "'"; } } else if (timestampStr.matches(".*:\\d{2}$")) { // 如果没有毫秒部分,添加.000 formattedTimestamp = "'" + timestampStr + ".000'"; } else { formattedTimestamp = "'" + timestampStr + "'"; } } sql.append(") VALUES (").append(formattedTimestamp).append(", "); // 添加值 first = true; for (Map.Entry<String, Object> entry : record.entrySet()) { String columnName = entry.getKey(); Object value = entry.getValue(); // 跳过标签列和时间戳字段 if (tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) { continue; } if (!first) { sql.append(", "); } // 处理NULL值 if (value == null) { sql.append("NULL"); } else if (value instanceof String || value instanceof Timestamp || value instanceof LocalDateTime) { // 对字符串和时间戳类型进行特殊处理 if (value instanceof Timestamp) { // 格式化时间戳 Timestamp ts = (Timestamp) value; sql.append("'").append(TimeUtils.formatWithMillis(TimeUtils.from(ts))).append("'"); } else if (value instanceof LocalDateTime) { // 格式化LocalDateTime LocalDateTime ldt = (LocalDateTime) value; sql.append("'").append(TimeUtils.formatWithMillis(ldt)).append("'"); } else { // 字符串值需要添加引号,并处理NULL字符串 String strValue = value.toString(); if ("NULL".equalsIgnoreCase(strValue)) { sql.append("NULL"); } else { sql.append("'").append(strValue.replace("'", "''")).append("'"); } } } else { // 其他类型直接添加 sql.append(value.toString()); } first = false; } sql.append(")"); // System.out.println("SQL语句: " + sql.toString()); tdengineJdbcTemplate.execute(sql.toString()); // 更新性能监控 performanceMonitorService.incrementCounter("data.migration.insert.success", 1); break; // 成功执行,跳出重试循环 } catch (Exception e) { retryCount++; System.err.println("数据插入失败,正在进行第" + retryCount + "次重试: " + e.getMessage()); if (retryCount >= maxRetries) { System.err.println("数据插入最终失败: " + e.getMessage()); performanceMonitorService.incrementCounter("data.migration.insert.failure", 1); throw new RuntimeException("数据插入失败", e); } else { try { Thread.sleep(retryInterval * retryCount); // 使用配置的重试间隔和指数退避 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("重试被中断", ie); } } } finally { long endTime = System.currentTimeMillis(); performanceMonitorService.recordTime("data.migration.insert", endTime - startTime); } } } /** * 确保子表存在 * @param superTableName 超级表名 * @param childTableName 子表名 * @param tagValue 标签值 */ private void ensureChildTableExists(String superTableName, String childTableName, String tagValue) { // 使用类级锁来确保在多线程环境下不会重复创建表 synchronized (DataMigrationService.class) { try { // 首先确保超级表存在(使用缓存优化) String superTableCacheKey = "tdengine:stable:" + superTableName; // 不使用缓存检查超级表是否存在 // 直接确保超级表存在 ensureSuperTableExists(superTableName); // 不使用缓存存储超级表存在信息 // 检查子表是否存在 if (!isTableExists(childTableName)) { createChildTable(superTableName, childTableName, tagValue); } } catch (Exception e) { System.err.println("确保子表存在时失败: " + e.getMessage()); // 不打印完整堆栈,减少日志输出 } } } /** * 确保超级表存在 * @param superTableName 超级表名 */ private void ensureSuperTableExists(String superTableName) { try { // 检查超级表是否存在 if (!isTableExists(superTableName)) { // 超级表应该已经由TableAutoCreateService创建,这里只是确保缓存存在 // 不再创建默认的超级表结构 System.out.println("警告: 超级表 " + superTableName + " 不存在,需要先分析MySQL表结构并创建"); } } catch (Exception e) { System.err.println("确保超级表存在时失败: " + e.getMessage()); // 不打印完整堆栈,减少日志输出 } } /** * 处理SQL关键字冲突,为字段名添加反引号 * @param columnName 字段名 * @return 处理后的字段名 */ private String escapeKeyword(String columnName) { // TDengine中的关键字列表(部分) String[] keywords = {"query", "order", "group", "limit", "offset", "select", "from", "where", "insert", "update", "delete"}; for (String keyword : keywords) { if (keyword.equalsIgnoreCase(columnName)) { return "`" + columnName + "`"; } } return columnName; } } ,如何修复
最新发布
11-15
<think> 我们遇到的问题是:在插入数据到TDengine时,出现错误:`Invalid column name: id`。从错误信息中我们可以看到,超级表`my_battery_info`的创建语句中并没有名为`id`的列,但是在插入语句中却包含了`id`列。 分析原因: 1. 在超级表`my_battery_info`的定义中,没有`id`列。 2. 在插入语句中,我们试图插入一个名为`id`的列(值为320),这导致了错误。 解决方案: 我们需要调整插入逻辑,确保插入的列名与超级表的列名一致。具体步骤: 1. 在插入数据到TDengine时,只插入超级表中存在的列。对于超级表中不存在的列(如`id`),我们应该跳过或者将其映射到超级表中存在的列(如果含义相同但列名不同,则需要映射;如果超级表没有该列,则直接忽略)。 2. 修改Java代码中的`insertIntoTDengine`方法,在构建插入SQL时,只使用超级表中存在的列。 具体修改点: 在构建插入SQL时,我们需要根据超级表的列来过滤记录中的字段。我们可以从超级表的结构中获取列名列表,然后只插入这些列。 但是,由于我们无法在运行时动态获取超级表的列(因为性能考虑,我们不会每次都查询超级表的列),我们可以采取以下方法: 方案一:在创建超级表时,我们将超级表的列信息缓存起来(例如,使用一个Map,key为超级表名,value为列名的Set)。然后在插入时,只插入缓存中存在的列。 方案二:在插入前,我们已经知道MySQL表结构和TDengine超级表结构,我们可以配置一个列映射关系,但这里我们没有映射配置,所以我们需要根据超级表的列来过滤。 考虑到我们已经在`TableAutoCreateService`中创建了超级表,我们可以修改`TableAutoCreateService`,使其在创建超级表后缓存该超级表的列名。然后在`DataMigrationService`中,我们可以通过超级表名获取其列名集合,然后在插入时只插入这些列。 但是,由于我们当前代码中没有缓存超级表的列信息,我们需要修改代码以支持这一功能。 由于时间关系,我们采用一个简单的方法:在插入时,我们只插入超级表中存在的列。我们可以通过查询`information_schema.ins_columns`来获取超级表的所有列名(但这样每次插入都要查,效率低)。或者我们在创建超级表时就缓存列名。 由于我们已经有`TableAutoCreateService`在创建表结构,我们可以在创建超级表后,将列名缓存起来。 修改步骤: 1. 在`TableAutoCreateService`中,当创建超级表后,将超级表的列名缓存到一个Map中(例如:`superTableColumnsCache`,key为超级表名,value为列名的Set)。 2. 在`DataMigrationService`中,插入数据时,从缓存中获取超级表的列名,然后只插入这些列。 但是,考虑到我们当前的问题,我们也可以快速修复:在插入语句中跳过那些超级表中不存在的列。由于错误信息中已经给出了超级表的创建语句,我们可以看出超级表`my_battery_info`的列中并没有`id`列。所以我们在插入时应该跳过`id`列。 由于代码中`insertIntoTDengine`方法在构建插入SQL时,遍历记录的所有列,但跳过了标签列和时间戳字段,但没有跳过超级表中不存在的列。因此,我们需要在构建插入SQL时,只插入超级表中存在的列。 由于我们暂时没有缓存超级表的列,我们可以临时修改代码,在插入前先获取超级表的列信息(但获取列信息需要查询TDengine,这可能会影响性能)。或者我们可以在配置中指定列,但这不灵活。 考虑到时间,我们采用一个折中方案:在`DataMigrationService`中添加一个方法,用于获取超级表的列名集合(并缓存结果),然后在构建插入SQL时,只插入属于超级表的列(除了标签列和时间戳字段)。 具体修改: 1. 在`DataMigrationService`中添加一个缓存超级表列名的Map: private Map<String, Set<String>> superTableColumnsCache = new ConcurrentHashMap<>(); 2. 添加一个方法`getSuperTableColumns`,用于获取超级表的列名(如果缓存中有则直接返回,否则从TDengine查询): private Set<String> getSuperTableColumns(String superTableName) { if (superTableColumnsCache.containsKey(superTableName)) { return superTableColumnsCache.get(superTableName); } Set<String> columns = new HashSet<>(); try { String sql = "SELECT column_name FROM information_schema.ins_columns WHERE db_name = ? AND table_name = ?"; // 注意:TDengine中超级表属于某个数据库,我们这里假设数据库名是已知的,或者可以从配置获取。但代码中没有数据库名信息。 // 我们可以通过配置获取,或者假设超级表在当前数据库。 // 由于代码中没有数据库名,我们暂时使用默认数据库,或者从连接中获取。 // 这里我们简化,直接查询当前数据库的超级表列名 // 注意:在TDengine中,超级表名就是表名(在ins_columns表中,超级表的列也会被列出) List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(sql, "数据库名", superTableName); // 但是,我们并不知道数据库名!所以这个方法需要调整。 考虑到这个复杂性,我们暂时不采用动态获取列的方式,而是采用另一种方法:在创建超级表时,我们已经在`TableAutoCreateService`中解析了MySQL表的列和类型,并创建了超级表。我们可以在`TableAutoCreateService`创建超级表后,将该超级表的列名缓存起来。 由于时间关系,我们选择另一种更简单的修复:在插入时,我们跳过那些在超级表中肯定不存在的列(比如`id`列)。因为我们知道超级表中没有`id`列,所以我们在插入时跳过`id`列。 具体修改`DataMigrationService`的`insertIntoTDengine`方法: 在构建插入SQL的列名循环中,我们跳过一些已知的、在超级表中不存在的列(如`id`)。但这不通用。 另一种通用方法:我们可以在配置中指定要忽略的列,比如在`application.properties`中添加一个配置`tdengine.ignore.columns=id`,然后在代码中读取这个配置,构建一个忽略列的集合。 修改步骤: 1. 在`application.properties`中添加配置: tdengine.ignore.columns=id 2. 在`DataMigrationService`中注入这个配置: @Value("${tdengine.ignore.columns}") private String ignoreColumns; private Set<String> ignoreColumnSet; @PostConstruct public void init() { ignoreColumnSet = new HashSet<>(Arrays.asList(ignoreColumns.split(","))); } 3. 在构建插入SQL的列名循环中,除了跳过标签列和时间戳字段,还要跳过忽略列: if (ignoreColumnSet.contains(columnName) || tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) { continue; } 这样,我们在插入时就会跳过`id`列。 但是,这种方法需要提前知道哪些列要忽略,而且如果还有其他列不存在,同样会报错。所以这不是根本的解决方案。 根本的解决方案是:只插入超级表中存在的列。这要求我们知道超级表有哪些列。 由于时间关系,我们采用一个临时方案:在插入时,如果遇到列名不在超级表列名中,就跳过。这要求我们能够获取超级表的列名集合。 我们决定:在`DataMigrationService`中,为每个超级表缓存其列名集合。在第一次插入时,如果缓存中没有,则查询TDengine获取超级表的所有列名(包括标签列?标签列在TAGS中,不在普通列中)。 注意:在TDengine中,超级表的列包括普通列和标签列。标签列是放在TAGS子句中的,而普通列是放在括号中的。我们插入时,普通列的值放在括号中,标签列的值放在TAGS子句中。 所以,我们只需要缓存超级表的普通列(除了标签列)的列名。 修改步骤: 1. 在`DataMigrationService`中添加一个缓存: private final Map<String, Set<String>> superTableNormalColumnsCache = new ConcurrentHashMap<>(); 2. 添加一个方法`loadSuperTableNormalColumns`,用于加载超级表的普通列名(不包括标签列): private Set<String> loadSuperTableNormalColumns(String superTableName) { Set<String> columns = new HashSet<>(); try { // 查询超级表的普通列(不包括标签列) String sql = "SELECT column_name FROM information_schema.ins_columns WHERE table_name = ? AND db_name = (SELECT database()) AND is_tag = 0"; List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(sql, superTableName); for (Map<String, Object> row : result) { String columnName = (String) row.get("column_name"); columns.add(columnName); } return columns; } catch (Exception e) { System.err.println("加载超级表列信息失败: " + e.getMessage()); return Collections.emptySet(); } } 3. 在`insertIntoTDengine`方法中,获取超级表的普通列集合(如果缓存中没有,则加载并缓存): Set<String> normalColumns = superTableNormalColumnsCache.computeIfAbsent(superTableName, k -> loadSuperTableNormalColumns(superTableName)); 4. 在循环记录列的时候,只处理在`normalColumns`中的列(同时跳过标签列和时间戳字段,因为时间戳字段已经单独处理): for (String columnName : record.keySet()) { // 跳过标签列 if (tagColumn.equals(columnName)) { continue; } // 跳过时间戳字段(已经在INSERT语句中单独处理) if (tdengineTimestampField.equals(columnName)) { continue; } // 如果列不在超级表的普通列中,跳过 if (!normalColumns.contains(columnName)) { continue; } // 然后添加到插入列 } 这样,我们只插入超级表中存在的普通列。 注意:标签列已经在TAGS子句中指定,所以普通列中不应该包含标签列。 修改代码: 由于在`insertIntoTDengine`方法中,我们构建插入SQL时,已经跳过了标签列和时间戳字段,现在再加上一个条件:只插入超级表普通列中存在的列。 我们修改`insertIntoTDengine`方法中的列循环部分。 具体修改如下(在`insertIntoTDengine`方法中): 步骤1:在方法开头,先获取超级表的普通列集合(使用缓存): Set<String> normalColumns = superTableNormalColumnsCache.computeIfAbsent(superTableName, k -> loadSuperTableNormalColumns(superTableName)); 步骤2:在构建列名的循环中,增加一个条件:如果列名不在`normalColumns`中,则跳过。 修改后,循环部分代码: boolean first = true; for (String columnName : record.keySet()) { // 跳过标签列 if (tagColumn.equals(columnName)) { continue; } // 跳过时间戳字段 if (tdengineTimestampField.equals(columnName)) { continue; } // 跳过超级表普通列中不存在的列 if (!normalColumns.contains(columnName)) { continue; } if (!first) { sql.append(", "); } // 处理关键字冲突 sql.append(escapeKeyword(columnName)); first = false; } 同样,在构建值的部分,也要跳过这些列。 值的循环部分修改: first = true; for (Map.Entry<String, Object> entry : record.entrySet()) { String columnName = entry.getKey(); Object value = entry.getValue(); // 跳过标签列 if (tagColumn.equals(columnName)) { continue; } // 跳过时间戳字段 if (tdengineTimestampField.equals(columnName)) { continue; } // 跳过超级表普通列中不存在的列 if (!normalColumns.contains(columnName)) { continue; } if (!first) { sql.append(", "); } ... // 处理值 } 这样,我们就只插入超级表普通列中存在的列,避免了插入不存在的列(如`id`)导致的错误。 另外,我们还需要处理一个情况:在创建子表时,我们使用了标签值,这个标签值是从记录中的标签列(如`battery_id`)获取的。我们的标签列配置为`tagColumnsConfig`,在代码中为`tagColumn`。注意,标签列在记录中是存在的,但我们在插入普通列时跳过了它,因为它已经放在TAGS子句中。 这个修改应该能解决问题。 注意:由于TDengine的`information_schema.ins_columns`表在TDengine 3.x版本中可用,请确保版本兼容。 由于我们修改了代码,增加了对TDengine系统表的查询,可能会对性能有影响,但是这个查询只会在第一次插入某个超级表时进行,后续都会使用缓存。 最后,我们还需要处理一个潜在问题:如果记录中的列名与超级表的列名大小写不一致?TDengine的列名是大小写不敏感的,但我们在Java代码中都是字符串,所以最好统一大小写。我们可以将列名统一转换为小写(或大写)进行比较。但TDengine存储的列名默认是小写吗?根据文档,TDengine不区分大小写,存储为小写。而MySQL的列名可能是大小写敏感的,但在我们的代码中,从MySQL查询的列名是原始大小写。所以我们需要将列名统一为小写(或大写)后再比较。 修改:在`loadSuperTableNormalColumns`方法中,将查询到的列名转换为小写存储。同时,在比较时,将记录中的列名也转换为小写。 修改`loadSuperTableNormalColumns`方法: String columnName = ((String) row.get("column_name")).toLowerCase(); 然后,在循环中: if (!normalColumns.contains(columnName.toLowerCase())) { ... } 但是,为了效率,我们可以将记录中的列名在放入`normalColumns`时就已经是小写,然后比较时也将记录列名转为小写。 修改缓存列名为小写: columns.add(columnName.toLowerCase()); 然后在循环中使用: if (!normalColumns.contains(columnName.toLowerCase())) { ... } 或者,我们可以在`loadSuperTableNormalColumns`方法中直接存储为小写,然后在比较时也将记录列名转为小写。 具体修改: 在`loadSuperTableNormalColumns`方法中: String columnName = (String) row.get("column_name"); columns.add(columnName.toLowerCase()); // 统一存储为小写 在`insertIntoTDengine`方法中,循环记录列名时: if (!normalColumns.contains(columnName.toLowerCase())) { // 跳过 } 这样,我们就解决了列名大小写问题。 由于修改较多,我们总结一下在`DataMigrationService`中需要添加和修改的代码: 1. 添加缓存: private final Map<String, Set<String>> superTableNormalColumnsCache = new ConcurrentHashMap<>(); 2. 添加方法`loadSuperTableNormalColumns`。 3. 在`insertIntoTDengine`方法中,在获取标签列之后,获取超级表的普通列集合(使用缓存)。 4. 在构建插入SQL的列名和值的循环中,增加对列名是否在超级表普通列中的检查(并统一转换为小写比较)。 下面给出修改后的`insertIntoTDengine`方法的部分代码,以及新添加的方法。 注意:由于代码较长,我们只给出修改的关键部分。 由于时间限制,这里只给出关键修改代码: 添加方法: </think> ### 问题分析 根据错误信息 `TDengine ERROR (0x2602): sql: ... desc: Invalid column name: id`,问题核心在于: 1. **超级表结构**:`my_battery_info` 超级表定义中**没有 `id` 列** 2. **插入语句**:INSERT 语句中却包含了 `id` 列(值为 320) 3. **Java 代码**:`DataMigrationService` 在构建插入语句时未过滤超级表中不存在的列 ### 根本原因 代码中 `insertIntoTDengine()` 方法在构建 SQL 时,直接使用了 MySQL 记录的所有字段(包括 `id`),但该字段在 TDengine 超级表中不存在: ```java // DataMigrationService.java for (String columnName : record.keySet()) { // 仅跳过标签列和时间戳字段,未过滤其他不存在的列 if (tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)) { continue; } // 构建列名和值... } ``` ### 解决方案 #### 关键修改:动态过滤超级表列 在插入前检查列是否存在于超级表中,只插入存在的列: ##### 1. 添加列名缓存逻辑 在 `DataMigrationService` 中添加缓存和查询方法: ```java // 新增成员变量 private final Map<String, Set<String>> superTableColumnsCache = new ConcurrentHashMap<>(); // 获取超级表的列名集合(带缓存) private Set<String> getSuperTableColumns(String superTableName) { return superTableColumnsCache.computeIfAbsent(superTableName, k -> { Set<String> columns = new HashSet<>(); try { String sql = "SELECT column_name FROM information_schema.ins_columns " + "WHERE table_name = ? AND db_name = (SELECT database())"; List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(sql, superTableName); for (Map<String, Object> row : result) { String colName = (String) row.get("column_name"); columns.add(colName.toLowerCase()); // 统一小写处理 } } catch (Exception e) { System.err.println("获取超级表列失败: " + e.getMessage()); } return columns; }); } ``` ##### 2. 修改插入逻辑 - 过滤无效列 在 `insertIntoTDengine()` 方法中增加列过滤: ```java public void insertIntoTDengine(String superTableName, Map<String, Object> record) { // 获取超级表所有列(包括普通列+标签列) Set<String> validColumns = getSuperTableColumns(superTableName); // ... [原有代码] ... // 构建INSERT语句时过滤列 for (String columnName : record.keySet()) { String normalizedCol = columnName.toLowerCase(); // 新增过滤:跳过不存在的列 if (!validColumns.contains(normalizedCol)) { continue; } // 原有跳过逻辑(标签列+时间戳) if (tagColumn.equalsIgnoreCase(columnName) || tdengineTimestampField.equalsIgnoreCase(columnName)) { continue; } // ... [构建列名和值] ... } // ... [后续插入操作] ... } ``` #### 完整修复代码 ```java // DataMigrationService.java 修改部分 public class DataMigrationService { // 新增列缓存(类成员变量) private final Map<String, Set<String>> superTableColumnsCache = new ConcurrentHashMap<>(); private Set<String> getSuperTableColumns(String superTableName) { return superTableColumnsCache.computeIfAbsent(superTableName, k -> { Set<String> columns = new HashSet<>(); try { String sql = "SELECT column_name FROM information_schema.ins_columns " + "WHERE table_name = ? AND db_name = (SELECT database())"; List<Map<String, Object>> result = tdengineJdbcTemplate.queryForList(sql, superTableName); for (Map<String, Object> row : result) { String colName = (String) row.get("column_name"); columns.add(colName.toLowerCase()); } System.out.println("加载超级表列: " + superTableName + ", 列数: " + columns.size()); } catch (Exception e) { System.err.println("获取超级表列失败: " + e.getMessage()); } return columns; }); } public void insertIntoTDengine(String superTableName, Map<String, Object> record) { // 1. 获取有效的超级表列名 Set<String> validColumns = getSuperTableColumns(superTableName); // ... [原有时间戳处理等逻辑] ... // 2. 构建列名部分 StringBuilder sql = new StringBuilder(); sql.append("INSERT INTO ").append(childTableName) .append(" USING ").append(superTableName) .append(" TAGS ('").append(tagValue).append("') (") .append(tdengineTimestampField).append(", "); boolean first = true; for (String columnName : record.keySet()) { String normalizedCol = columnName.toLowerCase(); // 新增过滤:跳过无效列 if (!validColumns.contains(normalizedCol)) { continue; } // 原有跳过逻辑 if (tagColumn.equalsIgnoreCase(columnName) || tdengineTimestampField.equalsIgnoreCase(columnName)) { continue; } if (!first) sql.append(", "); sql.append(escapeKeyword(columnName)); first = false; } sql.append(") VALUES (").append(formattedTimestamp).append(", "); // 3. 构建值部分 first = true; for (Map.Entry<String, Object> entry : record.entrySet()) { String columnName = entry.getKey(); String normalizedCol = columnName.toLowerCase(); Object value = entry.getValue(); // 同样过滤无效列 if (!validColumns.contains(normalizedCol)) { continue; } if (tagColumn.equalsIgnoreCase(columnName) || tdengineTimestampField.equalsIgnoreCase(columnName)) { continue; } if (!first) sql.append(", "); // ... [值处理逻辑] ... first = false; } sql.append(")"); // 执行SQL... } } ``` ### 关键优化说明 1. **动态列过滤** 通过查询 `information_schema.ins_columns` 获取超级表实际列名,确保只插入存在的列 2. **缓存机制** `ConcurrentHashMap` 缓存列信息避免重复查询,提升性能 3. **大小写统一** 列名统一转为小写,解决不同数据库大小写敏感性问题 4. **错误预防** 自动跳过不存在的列(如 `id`),避免 `Invalid column name` 错误 ### 注意事项 1. **TDengine 权限** 确保应用账号有权限查询 `information_schema.ins_columns` 2. **列名兼容性** 如果 MySQL 列名包含特殊字符,需在 `escapeKeyword()` 中额外处理 3. **缓存更新** 若超级表结构变更,需重启服务或实现缓存刷新机制 此修改将彻底解决列不匹配问题,且能自动适配表结构变化,保证数据插入的稳定性。 --- ### 相关优化建议 #### 1. 添加列存在性预检 在批量插入前统一校验列有效性: ```java private void validateColumns(String superTableName, Map<String, Object> record) { Set<String> validColumns = getSuperTableColumns(superTableName); for (String col : record.keySet()) { if (!validColumns.contains(col.toLowerCase())) { System.err.println("警告: 跳过不存在的列 " + col); } } } // 在 batchInsertIntoTDengine 中调用 private int batchInsertIntoTDengine(...) { validateColumns(superTableName, records.get(0)); // ...后续插入... } ``` #### 2. 增强错误日志 插入失败时打印无效列详情: ```java catch (Exception e) { String invalidCols = record.keySet().stream() .filter(col -> !validColumns.contains(col.toLowerCase())) .collect(Collectors.joining(",")); System.err.println("插入失败!无效列: [" + invalidCols + "]"); } ``` #### 3. 性能优化 对于高频插入场景,可异步加载列缓存: ```java // 在类初始化时预加载常用超级表列 @PostConstruct public void initColumnCache() { new Thread(() -> { getSuperTableColumns("my_battery_info"); // 加载其他常用表... }).start(); } ``` --- ### 最终修复效果 | 操作 | 原行为 | 修复后行为 | |------|--------|------------| | 遇到 `id` 列 | 尝试插入 → 报错 | 自动跳过 → 成功插入 | | 超级表新增列 | 需修改代码 | 自动识别新列 | | 大小写差异 | 可能报错 | 统一小写处理 | 此方案彻底解决列不匹配问题,同时提升系统健壮性。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值