【Java 填坑日记】Excel里的“1.00“存入数据库解密后,Integer说它不认识:一次 NumberFormatException 翻车实录

作者:@Neoest

时间:2025-12-10

关键词:NumberFormatException、Excel导入、POI、数据加密、类型转换、DecimalFormat


1. 问题现场

业务需求:从Excel导入用户数据,其中数量列是数字类型 ,比如 1.00。入库前需要加密 ,读取时需要解密 并转成 Integer

java 复制代码
// 导入时:Excel单元格值 1.00
Double cellValue = 1.00;  // POI读取后
String encryptStr = encrypt(cellValue.toString());  // 加密成"MS4uMDA="存库

// 读取时:解密后转换
String decryptStr = decrypt(encryptStr);  // 解密得到"1.00"
Integer count = Integer.valueOf(decryptStr);  // 当场翻车!

控制台炸裂:

复制代码
java.lang.NumberFormatException: For input string: "1.00"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.valueOf(Integer.java:740)
	at com.example.service.UserService.getUserCount(UserService.java:123)

2. 先定位根因

环节 期望 实际 问题
Excel单元格 整数 1 数字 1.00 POI读取为Double
转字符串 "1" "1.00" toString()保留小数
加密存储 密文
解密后 "1" "1.00" 带小数点
Integer.parseInt() 合法整数 .非法 崩溃

核心原因Integer 只认纯整数格式,"1.00" 对它来说和 "abc" 没区别。Excel的数字格式和Java的整数类型之间存在语义鸿沟


3. 四种修复姿势(针对Excel场景)

✅ 方案 A:导入时强制转整数(源头根治)
java 复制代码
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;

public class ExcelImporter {
    
    public static String parseCellToIntegerString(Cell cell) {
        if (cell == null) return "0";
        
        if (cell.getCellType() == CellType.NUMERIC) {
            // 关键:直接取整,丢弃小数部分
            return String.valueOf((int) cell.getNumericCellValue());
        } else if (cell.getCellType() == CellType.STRING) {
            return cell.getStringCellValue().trim();
        }
        return "0";
    }
}

// 使用
String cleanStr = parseCellToIntegerString(cell);  // "1"
String encryptStr = encrypt(cleanStr);  // 存库
  • 优点:源头干净,入库的就是纯整数
  • 缺点 :会丢失精度,不适合需要保留小数的场景

✅ 方案 B:用 DecimalFormat 格式化(推荐)
java 复制代码
import java.text.DecimalFormat;

public class NumberEncryptor {
    
    private static final DecimalFormat INTEGER_FORMAT = new DecimalFormat("#");
    
    public static String formatBeforeEncrypt(Double num) {
        if (num == null) return "0";
        // 1.00 → "1", 1.50 → "2"(四舍五入)
        return INTEGER_FORMAT.format(num);
    }
}

// 导入流程
Double cellValue = cell.getNumericCellValue();  // 1.00
String formatted = NumberEncryptor.formatBeforeEncrypt(cellValue);  // "1"
String encryptStr = encrypt(formatted);  // 存库
Excel值 格式化后 说明
1.00 "1" 去小数
1.50 "2" 四舍五入
0.00 "0" 正常处理
  • 优点:控制精确,支持四舍五入
  • 缺点:需要引入格式化类

✅ 方案 C:解密后做兼容性转换(后置容错)
java 复制代码
public class SafeNumberParser {
    
    /**
     * 兼容带小数的数字字符串
     */
    public static Integer parseLenient(String str, Integer defaultValue) {
        if (StringUtils.isBlank(str)) return defaultValue;
        try {
            return Integer.valueOf(str.trim());
        } catch (NumberFormatException e) {
            // 尝试按浮点数解析
            try {
                return Double.valueOf(str.trim()).intValue();
            } catch (NumberFormatException e2) {
                return defaultValue;
            }
        }
    }
}

// 读取流程
String decryptStr = decrypt(encryptStr);  // "1.00"
Integer count = SafeNumberParser.parseLenient(decryptStr, 0);  // 成功返回 1
  • 优点:兼容历史脏数据,不需要改入库逻辑
  • 缺点: 性能略差,存在隐式转换风险

✅ 方案 D:全链路使用 BigDecimal(企业级方案)
java 复制代码
import java.math.BigDecimal;

// 导入时
BigDecimal cellDecimal = BigDecimal.valueOf(cell.getNumericCellValue());
String encryptStr = encrypt(cellDecimal.toPlainString());  // "1.00"

// 读取时
String decryptStr = decrypt(encryptStr);  // "1.00"
Integer count = new BigDecimal(decryptStr).intValue();  // 安全转换

流程图

复制代码
Excel(1.00) → POI(Double) → BigDecimal → toPlainString() → 加密 → 数据库
数据库 → 解密 → "1.00" → BigDecimal → intValue() → Integer(1)
  • 优点:精度无损,金融级安全,避免浮点误差
  • 缺点 :代码稍重,需要理解 BigDecimal API

4. 工程级最佳实践

建议一:导入时统一清洗
java 复制代码
@UtilityClass
public class ExcelDataCleaner {
    
    public String cleanInteger(Cell cell) {
        if (cell == null) return "0";
        switch (cell.getCellType()) {
            case NUMERIC:
                // Excel数字统一转整数字符串
                return String.valueOf((long) cell.getNumericCellValue());
            case STRING:
                String str = cell.getStringCellValue().trim();
                // 字符串也清洗掉小数点
                return str.contains(".") ? str.substring(0, str.indexOf(".")) : str;
            default:
                return "0";
        }
    }
}
建议二:封装加密解密工具类
java 复制代码
public class SecurityNumberHandler {
    
    public static String encryptNumber(Double value) {
        // 先格式化再加密,确保格式统一
        String plain = new DecimalFormat("#").format(value);
        return AESUtil.encrypt(plain);
    }
    
    public static Integer decryptToInteger(String cipher) {
        String plain = AESUtil.decrypt(cipher);
        return new BigDecimal(plain).intValue();  // 安全转换
    }
}
建议三:数据库字段设计反思

如果业务允许,建议:

  • 不加密纯数字字段,用明文整数存储
  • 如需加密,考虑使用数据库层加密(如MySQL的AES_ENCRYPT)
  • 避免应用层加密导致类型信息丢失

5. 小结

方案 适用场景 侵入性 精度保留 推荐度
导入时强制转整 纯整数业务 ❌ 否 ⭐⭐⭐
DecimalFormat 需要四舍五入 ⚠️ 部分 ⭐⭐⭐⭐
后置容错转换 历史数据兼容 ✅ 是 ⭐⭐⭐
BigDecimal全链路 企业级/金融级 ✅ 是 ⭐⭐⭐⭐⭐

6. 一键复制版补丁(推荐方案 D)

java 复制代码
// 导入依赖:poi-ooxml 5.x
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import java.math.BigDecimal;

public final class ExcelNumberHandler {
    
    private ExcelNumberHandler() {}
    
    /**
     * Excel单元格转加密字符串(整数)
     */
    public static String cellToEncryptedInteger(Cell cell) {
        if (cell == null || cell.getCellType() == CellType.BLANK) {
            return encrypt("0");
        }
        
        BigDecimal decimal;
        if (cell.getCellType() == CellType.NUMERIC) {
            decimal = BigDecimal.valueOf(cell.getNumericCellValue());
        } else {
            String str = cell.getStringCellValue().trim();
            decimal = new BigDecimal(str);
        }
        // 去除小数部分
        return encrypt(decimal.toPlainString());
    }
    
    /**
     * 解密字符串转Integer
     */
    public static Integer decryptToInteger(String cipher) {
        String plain = decrypt(cipher);
        return new BigDecimal(plain).intValue();
    }
    
    // 模拟加密方法
    private static String encrypt(String plain) {
        return Base64.getEncoder().encodeToString(plain.getBytes());
    }
    
    // 模拟解密方法
    private static String decrypt(String cipher) {
        return new String(Base64.getDecoder().decode(cipher));
    }
}

7. 踩坑复盘

教训 :数据在跨系统/跨格式 流转时,类型信息极易丢失 。Excel的1.00 ≠ Java的"1" ≠ 数据库的1

** checklist **:

  • Excel导入时明确数据类型(数字/文本/日期)
  • 转换字符串前做格式化 ,而非直接toString()
  • 加密字段在解密后必须进行合法性校验
  • 生产环境务必打印原始值和转换值便于排查

8. 参考资料

如果本文帮到你,记得点赞收藏!评论区一起交流更多Excel导入的坑位~

相关推荐
小明说Java2 小时前
MySQL慢查询优化:从2秒到2毫秒的蜕变
数据库·mysql
小坏讲微服务2 小时前
Spring Boot 4.0 新特性整合 MyBatis-Plus 完整教程
java·spring boot·后端·spring cloud·微服务·mybatis·mybatis plus
尚墨11112 小时前
Java RestTemplate报错Invalid mime type “charset=utf-8“: does not contain ‘/‘
java·开发语言
摇滚侠2 小时前
Redis 零基础到进阶,教程简介,Redis 是什么,Redis 能干嘛,Redis 去哪下,Redis 怎么玩,Redis7 新特性,笔记一到八
数据库·redis·笔记
我命由我123452 小时前
Java 开发使用 MyBatis PostgreSQL 问题:传入的参数为 null,CONCAT 函数无法推断参数的数据类型
java·开发语言·数据库·学习·postgresql·mybatis·学习方法
爱装代码的小瓶子2 小时前
【c++知识铺子】map和set的底层-红黑树
java·开发语言·c++
小蒜学长2 小时前
基于Spring Boot家政服务系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
lzq6032 小时前
Python自动化办公:5分钟批量处理Excel数据
python·自动化·excel
洛阳泰山2 小时前
Java实现周易六爻自动排盘:根据卜卦的时间推算出天干地支
java·开发语言·周易·六爻