关于在mp静态代码块中使用条件构造器造成的错误

错误代码

1
2
3
4
5
boolean flag = seckillVoucherService.update(new LambdaUpdateWrapper<SeckillVoucher>()
.eq(SeckillVoucher::getVoucherId, voucherId)
.gt(SeckillVoucher::getStock, 0)
.setSql("stock = stock - 1")
);

错误信息

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression ‘ew.sqlSegment != null and ew.sqlSegment != ‘’ and ew.nonEmptyOfWhere’. Cause: org.apache.ibatis.ognl.OgnlException: sqlSegment [java.lang.ExceptionInInitializerError]

文心一言解释

这个错误信息涉及到MyBatis框架及其与Spring的集成,以及在处理OGNL表达式时遇到的问题。让我们逐步分析这个错误:

错误信息解析

  1. org.mybatis.spring.MyBatisSystemException

    • 这是一个由MyBatis-Spring集成库抛出的系统异常,表明在MyBatis的操作中发生了错误。
  2. nested exception is org.apache.ibatis.builder.BuilderException

    • 这表明在MyBatis的SQL构建过程中发生了异常。
  3. Error evaluating expression 'ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere'

    • 这是一个OGNL表达式评估错误。OGNL(Object-Graph Navigation Language)是MyBatis用于解析动态SQL表达式的语言。这个表达式检查ew.sqlSegment是否不为null且不为空字符串,并且ew.nonEmptyOfWhere也应该是某种“非空”或“真”的状态。
  4. Cause: org.apache.ibatis.ognl.OgnlException: sqlSegment [java.lang.ExceptionInInitializerError]

    • 这表明在评估OGNL表达式时,sqlSegment属性或相关代码引发了ExceptionInInitializerError。这通常意味着在初始化sqlSegment或其依赖的某个静态字段时,静态初始化块中抛出了异常。
  5. [java.lang.ExceptionInInitializerError]

    • 如前所述,ExceptionInInitializerError是一个包装器异常,表明在静态初始化过程中发生了异常。这个异常的具体原因需要查看它的“caused by”部分来确定。

可能的原因和解决方案

  • 静态初始化失败

    • 检查与ew(可能是某个Wrapper类或实例)相关的类的静态初始化代码。确保静态字段和静态初始化块中的代码不会抛出异常。
  • 不兼容的库版本

    • 确保你使用的MyBatis和MyBatis-Spring版本相互兼容,并且与你的Spring版本兼容。
  • 检查MyBatis配置

    • 仔细检查你的MyBatis映射文件和配置,确保OGNL表达式正确无误,并且引用的属性和方法存在且可访问。
  • 增加日志记录

    • 在你的应用中增加更详细的日志记录,特别是关于MyBatis和Spring的日志,以便能够捕获和分析初始化过程中的异常。
  • 查看完整的异常堆栈

    • ExceptionInInitializerError通常会包含一个“caused by”异常,这个异常才是导致初始化失败的根本原因。查看完整的异常堆栈跟踪,找到并解决这个问题。
  • JVM参数和类加载器问题

    • 在某些情况下,JVM参数(如-XX:+UseG1GC等)或类加载器问题(如多个类加载器加载同一个类)也可能导致此类错误。检查你的JVM配置和类加载器设置。

解决这个问题的关键在于找到导致ExceptionInInitializerError的根本原因。这通常涉及到分析静态初始化代码、检查库版本兼容性、增加日志记录以及查看完整的异常堆栈跟踪。

改正后代码

1
2
3
4
5
boolean flag = seckillVoucherService.update()
.eq("voucher_id", voucherId)
.gt("stock", 0)
.setSql("stock = stock - 1")
.update();

两者间区别

代码区别

  1. 第一段代码
1
2
3
4
5
boolean flag = seckillVoucherService.update()
.eq("voucher_id", voucherId)
.gt("stock", 0)
.setSql("stock = stock - 1")
.update();
  • 这段代码看起来是在链式调用中直接使用了update()方法的返回值(可能是一个UpdateWrapper或类似的对象),然后链式地添加了条件(eqgt)和执行SQL语句(setSql),最后调用了.update()方法来执行更新操作。
  • 注意:这段代码没有显式地创建一个LambdaUpdateWrapper实例,而是可能依赖于update()方法的返回值已经是一个合适的包装器对象。
  1. 第二段代码
1
2
3
4
5
boolean flag = seckillVoucherService.update(new LambdaUpdateWrapper<SeckillVoucher>()
.eq(SeckillVoucher::getVoucherId, voucherId)
.gt(SeckillVoucher::getStock, 0)
.setSql("stock = stok - 1")
);
  • 这段代码创建了一个LambdaUpdateWrapper<SeckillVoucher>的实例,并在创建时就链式地添加了条件和执行SQL语句。
  • 然后,这个包装器实例被传递给seckillVoucherService.update()方法。

错误原因

  • 方法调用可能不正确:第一段代码中的update().eq(...).gt(...).setSql(...).update()链式调用看起来有些不寻常,因为通常update()方法是在包装器配置完成后调用的,而不是在链式调用的开始。这可能是因为你使用的MyBatis-Plus或类似库的特定版本或封装方式有所不同。然而,这种用法不是标准的MyBatis-Plus用法,因此可能存在兼容性问题或误解。
  • 类型安全问题:第二段代码使用了LambdaUpdateWrapper<SeckillVoucher>,它提供了类型安全的方法(如eq(SeckillVoucher::getVoucherId, voucherId)),这有助于避免字段名拼写错误和类型不匹配的问题。然而,由于setSql方法接受的是原生SQL字符串,因此它不受类型安全的保护,仍然可能引入SQL错误(如本例中的拼写错误)。