用StringBuilder连接MYSQL字段时被清空的问题

本文介绍了一种在使用StringBuilder连接MySQL数据库中读取的字段值时遇到的问题及解决办法。当特定字段被读取并追加时,StringBuilder的内容会异常清空。通过trim()方法处理字段值后,问题得到解决。

问题描述

读取MySQL的数据,然后使用StringBuilder进行连接,但在运行时发现每次append到一个字段的时候StringBuilder就会被清空,最后只剩后边的几个字段。
假如读出了四个字段:

字段名称
id10150145806225128
姓名张三
消息謝謝大家支持。
时间2016-06-13 19:09:47

正确运行的话,应该最终输出:

10150145806225128;张三;謝謝大家支持。;2016-06-13 19:09:47;0

但实际的输出过程是:

10150145806225128
10150145806225128;张三
10150145806225128;张三;謝謝大家支持。
2016-06-13 19:09:47
最终:2016-06-13 19:09:47

可以看到在append完消息字段后,再append时间的时候StringBuilder被清空了。
如果append的时候跳过消息字段,也没有任何问题

10150145806225128
10150145806225128;张三
10150145806225128;张三;2016-06-13 19:09:47
最终:10150145806225128;张三;2016-06-13 19:09:47

可能的解决方案

(1)首先考虑了StringBuilder长度限制的问题,但根据以往经验应该不是,因为没有限制length参数,而且所有字段数据加起来也不长。
(2)考虑了编码的影响,发现也没什么问题,从数据库到程序都使用了utf8,在程序里加了强制转换也依然没什么用处。
(3)怀疑是字段内容的问题,该字段为消息字段,内容为中文,如果把内容直接复制出来,在程序里直接append没有任何问题,但是如果从数据库里读再append就会出现清空的现象。
感觉应该是读取数据库的时候在字段内容末尾加了什么奇怪的东西,加了个trim()试了下,竟然成功了……

10150145806225128
10150145806225128;张三
10150145806225128;张三;謝謝大家支持。
10150145806225128;张三;謝謝大家支持。;2016-06-13 19:09:47
最终:10150145806225128;张三;謝謝大家支持。;2016-06-13 19:09:47

总结

So,就这么解决了,下次读取数据库里文本的时候,记得trim()一下……

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()) { // 跳过标签列和间戳字段、id if (tagColumn.equals(columnName) || tdengineTimestampField.equals(columnName)||columnName.equals("id")||columnName.equals("upload_time")) { 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 ("id".equals(columnName)||"upload_time".equals(columnName)) { continue; // 跳过id字段 } // 跳过标签列和间戳字段 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","topic"}; for (String keyword : keywords) { if (keyword.equalsIgnoreCase(columnName)) { return "`" + columnName + "`"; } } return columnName; } }代码是这样的,ts是不是区的问题还是格式的问题
最新发布
11-15
从你提供的代码来看,`ts` 字段的处理逻辑确实可能存在 **间格式或区转换错误**,从而导致 TDengine 报错: ``` desc: Timestamp data out of range ``` --- ## ✅ 问题分析 ### ❓ 为什么会出现 `Timestamp data out of range` 错误? TDengine 的间戳字段默认支持范围为: ``` 1970-01-01 00:00:00.000 ~ 2149-06-04 15:32:51.000(毫秒级) ``` 但如果你传入的是: - **非法格式**(如 `2024/09/23 08:58:06`) - **错误的间戳数值**(如 `0`、`1970-01-01 00:00:00`) - **未考虑问题**(如 `LocalDateTime` 缺少区信息) 就可能导致 TDengine 误判为非法间。 --- ## 🔍 你的代码中间处理的关键点 ### 1. 间戳字段提取逻辑(`timestampValue`): ```java Object timestampValue = record.get(timestampField); ``` 你从 MySQL 中取出的 `timestampValue` 类型可能是: - `java.sql.Timestamp` - `java.time.LocalDateTime` - `String` ### 2. 间格式化逻辑: 你使用了自定义的 `TimeUtils.formatWithMillis(...)` 方法: ```java TimeUtils.formatWithMillis(TimeUtils.from(ts)) ``` 但未展示 `TimeUtils` 的具体实现,**这可能是问题的根源**。 --- ## ✅ 推荐解决方案 ### ✅ 1. 确保间戳字段为 **毫秒级间戳** 或 **ISO8601 格式字符串** #### ✅ 正确的格式示例: ```sql '2024-09-23 08:58:06.000' -- ISO8601 字符串格式 1727063886000 -- 毫秒级间戳 ``` #### ❌ 错误的格式示例: ```sql '2024/09/23 08:58:06' -- 使用斜杠格式错误 '2024-09-23T08:58:06' -- ISO8601 但未处理区 ``` --- ### ✅ 2. 增加区处理(关键!) 如果你使用的是 `LocalDateTime` 或 `Timestamp`,建议使用 `ZonedDateTime` 来显式指定区: ```java import java.time.ZoneId; import java.time.ZonedDateTime; private String formatTimestampWithZone(Object value) { if (value == null) { return "NULL"; } ZonedDateTime zdt; if (value instanceof Timestamp) { zdt = ((Timestamp) value).toLocalDateTime().atZone(ZoneId.of("Asia/Shanghai")); } else if (value instanceof LocalDateTime) { zdt = ((LocalDateTime) value).atZone(ZoneId.of("Asia/Shanghai")); } else { try { // 尝试解析字符串格式 LocalDateTime ldt = LocalDateTime.parse(value.toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS]")); zdt = ldt.atZone(ZoneId.of("Asia/Shanghai")); } catch (Exception e) { throw new IllegalArgumentException("无法解析字符串: " + value); } } // 输出 ISO8601 格式 return "'" + zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + "'"; } ``` 然后替换你原来的字段处理逻辑: ```java // 获取间戳字段的值 Object timestampValue = record.get(timestampField); if (timestampValue == null) { System.err.println("警告: 间戳字段为空,跳过该记录"); return; } // 使用带区的间格式化方法 String formattedTimestamp = formatTimestampWithZone(timestampValue); sql.append(") VALUES (").append(formattedTimestamp).append(", "); ``` --- ### ✅ 3. 验证间戳是否合法(可选) 你可以在插入前验证间是否在 TDengine 支持范围内: ```java private boolean isValidTimestamp(ZonedDateTime zdt) { long millis = zdt.toInstant().toEpochMilli(); return millis >= 0 && millis <= 253402300800000L; // 2149-06-04 15:32:51.000 } ``` --- ## ✅ 总结修复步骤 | 步骤 | 内容 | |------|------| | 1 | 显式指定区(如 `Asia/Shanghai`) | | 2 | 使用 `ZonedDateTime` 统一间处理 | | 3 | 输出标准格式 `'yyyy-MM-dd HH:mm:ss.SSS'` | | 4 | 可选:验证间是否在 TDengine 支持范围内 | | 5 | 替换原有 `TimeUtils.formatWithMillis()` 逻辑 | --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值