问题描述

对于 oracle 数据库,我们的表通常会创建唯一索引。

不过有时候因为并发等问题,重复插入失败是很正常的,我们希望捕获掉对应的异常,输出 warn 级别的日志即可。

实际使用的 mybatis-plus 作为数据库操作框架,记录一下一些小问题。

单个插入的重复

这个比较符合预期,实现如下:

try {
    userService.insert(user);
} catch (DuplicateKeyException exception) {
    logger.warn("插入重复,插入失败。", exception);
}

直接捕获 DuplicateKeyException,然后处理即可。

批量插入的重复

这里主要是为了记录批量插入的问题。

此处使用的 MP 的 insertBatch 方法,发现主键重复,使用 DuplicateKeyException 却捕获不到对应的异常。

看日志是 BatchExecutorException,发现还是无法捕获。

无奈最后 debug,发现实际抛出的是 MybatisPlusException 异常。

但是这个异常实际上不是很明确,比如数据库非 NULL 的,如果我们插入 NULL,也会报这个错误。

解决方案

把主键冲突的异常获取如下:

org.apache.ibatis.exceptions.PersistenceException: 
### Error flushing statements.  Cause: org.apache.ibatis.executor.BatchExecutorException: xxx.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: ORA-00001: 违反唯一约束条件 (xxx)

这是一个本地化的异常提示,我们可以选取唯一的错误码 ORA-00001

try {
    xxxService.insertBatch(list);
} catch (MybatisPlusException exception) {
    String message = exception.getCause().getLocalizedMessage();
    // oracle 的重复异常的唯一码
    if(message.contains("ORA-00001:")) {
        logger.warn("信息插入重复,插入失败。", exception);
    } else {
        throw exception;
    }
}

工具方法

当然,可以写成一个工具方法,便于复用。

/**
 * oracle 重复键异常处理
 *
 * 作用:mybatis insertBatch 会对异常封装,无法只管获取是否为重复。
 * @param exception 异常
 * @param logInfo 提示
 */
public static void oracleDuplicateKeyException(MybatisPlusException exception, String logInfo) {
    String message = exception.getCause().getLocalizedMessage();
    // oracle 的重复异常的唯一码
    if(message.contains("ORA-00001:")) {
        logger.warn(logInfo, exception);
    } else {
        throw exception;
    }
}