## 通用mapper insertSelective方法报语法错误

报错信息如下

Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:764)at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:648)at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2958)at com.alibaba.druid.filter.FilterAdapter.statement_execute(FilterAdapter.java:2473)at com.alibaba.druid.filter.FilterEventAdapter.statement_execute(FilterEventAdapter.java:188)at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2956)at com.alibaba.druid.proxy.jdbc.StatementProxyImpl.execute(StatementProxyImpl.java:147)at com.alibaba.druid.pool.DruidPooledStatement.execute(DruidPooledStatement.java:619)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.ibatis.logging.jdbc.StatementLogger.invoke(StatementLogger.java:57)at com.sun.proxy.$Proxy405.execute(Unknown Source)at org.apache.ibatis.executor.statement.SimpleStatementHandler.query(SimpleStatementHandler.java:73)at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)at com.didichuxing.erp.log.interceptor.LogMybatisInterceptor.intercept(LogMybatisInterceptor.java:62)at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)at com.sun.proxy.$Proxy394.query(Unknown Source)at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136)at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)at com.sun.proxy.$Proxy394.query(Unknown Source)at tk.mybatis.mapper.mapperhelper.SelectKeyGenerator.processGeneratedKeys(SelectKeyGenerator.java:77)at tk.mybatis.mapper.mapperhelper.SelectKeyGenerator.processAfter(SelectKeyGenerator.java:63)at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:50)at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)at com.didichuxing.erp.log.interceptor.LogMybatisInterceptor.intercept(LogMybatisInterceptor.java:62)at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)at com.sun.proxy.$Proxy394.update(Unknown Source)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)at com.sun.proxy.$Proxy394.update(Unknown Source)at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)... 123 more

核心报错如下

Error selecting key or setting result to parameter object. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1

报错的大概意思是

为参数对象选择键或设置结果时出错。原因:java.sql.SQLSyntaxErrorException:您的 SQL 语法有错误;检查与您的 MySQL 服务器版本相对应的手册,以了解在“?”附近使用的正确语法在第 1 行

这个问题在我们线上项目偶发,大概三四天出现一次,可以说是非常折磨人。这个报错非常明确,但是找遍全网,也没找到相关的问题,这里记录一下我排查这个问题的思路,以及解决办法。

进入正文

  • 分析日志

    日志打印了三条执行sql的记录,sql看不出来问题,不知道为什么会报错。

  • 分析源码
    找到 mybatis Interceptor 这个类的实现 PageInterceptor
    Interceptor 讲解去这里看

上源码

public Object intercept(Invocation invocation) throws Throwable {Object var22;try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement)args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds)args[2];ResultHandler resultHandler = (ResultHandler)args[3];Executor executor = (Executor)invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;if (args.length == 4) {boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//二级缓存有数据会走到这里cacheKey = (CacheKey)args[4];boundSql = (BoundSql)args[5];}List resultList;//判断是否需要分页if (this.dialect.skip(ms, parameter, rowBounds)) {//不需要分页走这里resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);} else {String msId = ms.getId();Configuration configuration = ms.getConfiguration();Map<String, Object> additionalParameters = (Map)this.additionalParametersField.get(boundSql);//判断是否需要countif (this.dialect.beforeCount(ms, parameter, rowBounds)) {//需要count的上去了会拼上countSuffix String countMsId = msId + this.countSuffix;MappedStatement countMs = this.getExistedMappedStatement(configuration, countMsId);Long count;if (countMs != null) {count = this.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);} else {countMs = (MappedStatement)this.msCountMap.get(countMsId);if (countMs == null) {countMs = MSUtils.newCountMappedStatement(ms, countMsId);this.msCountMap.put(countMsId, countMs);}//执行count的核心方法count = this.executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);}if (!this.dialect.afterCount(count, parameter, rowBounds)) {Object var24 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);return var24;}}if (!this.dialect.beforePage(ms, parameter, rowBounds)) {resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);} else {parameter = this.dialect.processParameterObject(ms, parameter, boundSql, cacheKey);//这个方法在处理sqlString pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);Iterator var17 = additionalParameters.keySet().iterator();while(true) {if (!var17.hasNext()) {resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);break;}String key = (String)var17.next();pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}}}//执行分页的核心方法var22 = this.dialect.afterPage(resultList, parameter, rowBounds);} finally {this.dialect.afterAll();}return var22;}

代码的核心语句我都加上了备注。

  • debug代码
    我把线上报错日志的入参整到本地,想复现报错,用上了jmeter并发请求,发现不会出现任何问题(崩溃)
    跟踪insertSelective 正确执行过程 如下
    执行先执行 install into 然后执行LAST_INSERT_ID() 获取刚刚插入的id 放入到插入的对象中

错误就是下面这段源码抛出来的,所以这里重点看了一下

    private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {try {if (parameter != null && this.keyStatement != null && this.keyStatement.getKeyProperties() != null) {String[] keyProperties = this.keyStatement.getKeyProperties();Configuration configuration = ms.getConfiguration();MetaObject metaParam = configuration.newMetaObject(parameter);if (keyProperties != null) {Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);//获取id值的核心方法List<Object> values = keyExecutor.query(this.keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);if (values.size() == 0) {throw new ExecutorException("SelectKey returned no data.");}if (values.size() > 1) {throw new ExecutorException("SelectKey returned more than one value.");}MetaObject metaResult = configuration.newMetaObject(values.get(0));//有结果的话下面方法把结果set到id中if (keyProperties.length == 1) {if (metaResult.hasGetter(keyProperties[0])) {this.setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));} else {this.setValue(metaParam, keyProperties[0], values.get(0));}} else {this.handleMultipleProperties(keyProperties, metaParam, metaResult);}}}} catch (ExecutorException var10) {throw var10;} catch (Exception var11) {//日志中打印的异常, 抛出的地方throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + var11, var11);}}

源码看到这里完全发现不了问题
我仔细对比了一下执行正确和执行错误的日志,发现出错都会出现这么一行日志,我觉得问题出现在这里。

com.legal.draft.dao.mapper.ContractDraftMapper.insertSelective!selectKey_COUNT

debug许久后,我根据日志中的 _COUNT 找到核心的代码块

     //判断是否分页if (this.dialect.skip(ms, parameter, rowBounds)) {//正常的话会在这个ifresultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);} else {//错误的都会走这个String msId = ms.getId();Configuration configuration = ms.getConfiguration();Map<String, Object> additionalParameters = (Map)this.additionalParametersField.get(boundSql);if (this.dialect.beforeCount(ms, parameter, rowBounds)) {//那个出现错误都会拼上_COUNT ,一行奇怪的日志让我离真相越来越近String countMsId = msId + this.countSuffix;

为什么进else里面呢 一个插入方法为啥会分页,skip(是否分页)的源码如下

public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {if (ms.getId().endsWith("_COUNT")) {throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");} else {//获取pagePage page = this.pageParams.getPage(parameterObject, rowBounds);if (page == null) {return true;} else {if (StringUtil.isEmpty(page.getCountColumn())) {page.setCountColumn(this.pageParams.getCountColumn());}this.autoDialect.initDelegateDialect(ms);return false;}}}

源码很简单,就是判断当前线程有没有page,就是下面这个东西

PageHelper.startPage(pageNum, pageSize);

肯定没有啊,我一个插入接口怎么会去分页,真是离了大普,于是我去查PageHelper插件的原理,发现了这么一句话。

PageHelper.startPage 方法调用后,后面必须有一个Mapper的查询方法,必须被消费掉。否则会由于ThreadLocal的原因,当该线程被其他方法调用时被分页。在文档中非常明确的写了分页插件的使用方法!!另外这不是ThreadLocal引起的问题,是使用不当!

我豁然大悟,于是写了个测试接口。

    /*** 变更* @param* @return*/@PostMapping("modifyDraftTemp")@EPAroundLogpublic BaseResponse<ContractVO> modifyDraft() {//开启分页PageHelper.startPage(1,1);//返回结果return BaseResponse.ok();}

在掉了几次这个测试接口以后,在调用插入insertSelective方法插入,果然问题必复现。

到这里问题已经明了,原因是项目中某个位置调用了PageHelper.startPage,但是没有被消费,因为这个page对象是放在本地线程中,ThreadLocal刚好执行了insertSelective的方法,会走到分页方法中,
处理获取刚刚插入的id时,组装出来的sql长这样。

SELECT LAST_INSERT_ID() LIMIT ?

执行的话就会报上面的sql语法的错误

知道问题原因,就很好解决了。
方法 1 : 找到项目中没有被消费的PageHelper.startPage的地方,优化掉代码。(我看了一下我们项目好几十处,太恐怖)
方法 2 : 在插入语句前加 PageHelper.clearPage() (治标不治本,但是好使,我又懒,我用的2)

        //如果别的线程有未消费的分页,会导致这里插入报错PageHelper.clearPage();if (contractDraftMapper.insertSelective(contractDraft) != 1) {return null;}

搞定。

Error selecting key or setting result to parameter object. Cause: java.sql.SQLSyntaxErrorException相关推荐

  1. Error selecting key or setting result to parameter object.

    Error selecting key or setting result to parameter object. :选择键或将结果设置为参数对象时出错. 'LAST_INSERT_ID' is n ...

  2. Java Mybatis Error selecting key or setting result to parameter object

    Java Mybatis Error selecting key or setting result to parameter object 链接:https://blog.csdn.net/hqbo ...

  3. 在<selectKey></selectKey>标签中使用多个sql语句时报错;Error selecting key or setting result to parameter object. C

    错误记录 今天想使用时间的格式化来作为主键,一开始还很顺利 创建_date表,使用系统时间表作为为另一个表的id -- 插入数据 insert into _date(createTime) value ...

  4. ORACLE 12C 插入数据遇到 Error getting generated key or setting result to parameter object错误

    今天用springboot和oracle的时候遇到以前的老项目保存数据的时候显示报错 Error getting generated key or setting result to paramete ...

  5. Error querying database. Cause: java.sql.SQLSyntaxErrorException: ORA-00911: 无效字符

    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is ...

  6. 毕业设计Spring boot问题记录(后端三):java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;

    原创博文,欢迎转载,转载时请务必附上博文链接,感谢您的尊重 报错情况 intell IDEA调试台报错 Caused by: java.sql.SQLSyntaxErrorException: You ...

  7. Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘experience_openne

    项目场景: 项目使用了mybatis-plus进行数据库操作 问题描述 例如:后端无法插入数据,控制台输出如下信息 org.springframework.jdbc.BadSqlGrammarExce ...

  8. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; mysql的xml文件出现异常解决方案

    今天在写一个查询接口的时候,测试中发现的一个问题,异常全文为: Cause: java.sql.SQLSyntaxErrorException: You have an error in your S ...

  9. 使用mybatis框架分页插件报错### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;

    报错信息如下: ### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the ...

最新文章

  1. swoole单台并发php,php swoole 并发多少?
  2. 获取远程文章内容时,显示图片的两种方式
  3. 手贱随手在Linux敲了 as 命令,出不来了
  4. 食堂点餐小程序,智慧食堂小程序,食堂预约点餐小程序毕设作品
  5. java实现NC数据等值线等值面可视化
  6. 《快速掌握QML》第六章 动画
  7. 文化课很差能学计算机专业吗,文化成绩不好,想要学习计算机不知道能不能学呢?...
  8. 特大喜讯,uni-app支持PC版了!
  9. 联邦学习笔记-《Federated Machine Learning: Concept and Applications》论文翻译个人笔记
  10. 电脑上有么有透明桌面便签?透明记事便利贴小工具下载
  11. mysql命令-创建删除切换数据库登录退出mysql
  12. 金融分析与风险管理——期权类型及到期时的盈亏
  13. php面试时的自我称呼,求职者不知道在面试时该如何称呼hr?
  14. 基于STM32F103,用蜂鸣器播放歌曲
  15. Linux之文件共享
  16. [iOS]一行代码集成空白页面占位图(基于runtime+MJRefresh思想)
  17. 项目管理文化:开展有效的总结会议
  18. 色噪声原理及matlab代码实现,色噪声原理及matlab代码实现
  19. iOS实现截屏并保存到相册
  20. InfoPath + Workflow + MOSS

热门文章

  1. GCN实战深入浅出图神经网络第五章:基于Cora数据集的GCN节点分类 代码分析
  2. 计算机理工英语,计算机专业英语-理工大学读本.pdf
  3. 智力题及答案(逻辑推理)
  4. xodo上的笔记不见了_notability新版本笔记缺失,少页,不见的处理办法
  5. moxa串口服务器显示灯,moxa串口服务器连接设置
  6. Oracle EBS User Guide - URL
  7. ContextCapture User Guide V4.4.11 Home(Smart3D 帮助文档 目录)
  8. vue项目在浏览器嵌入wps进行操作,这里记录两种方式,本地载入这个能匹配内网使用
  9. edui 富文本编辑_富文本编辑器wangEditor添加本地上传视频功能
  10. 2022年全球市场食用香精香料总体规模、主要生产商、主要地区、产品和应用细分研究报告