1. 项目背景
工程内使用shardingsphere支持分库分表,上层使用的MybatisPlus,有一张表的操作总是报NullPointException。
2. 异常堆栈
### SQL: INSERT INTO t_tg_message ( update_id, from_id, from_user_name, chat_id, type, data, text, create_time, deleted ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )
### Cause: java.lang.NullPointerException
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92) ~[mybatis-spring-2.0.5.jar!/:2.0.5]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) ~[mybatis-spring-2.0.5.jar!/:2.0.5]
at com.sun.proxy.$Proxy141.insert(Unknown Source) ~[?:?]
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:271) ~[mybatis-spring-2.0.5.jar!/:2.0.5]
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:60) ~[mybatis-plus-core-3.4.0.jar!/:3.4.0]
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148) ~[mybatis-plus-core-3.4.0.jar!/:3.4.0]
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89) ~[mybatis-plus-core-3.4.0.jar!/:3.4.0]
at com.sun.proxy.$Proxy298.insert(Unknown Source) ~[?:?]
at com.keyniu.user.web.tg.interceptors.impl.RecordInterceptor.appendMessage(RecordInterceptor.java:83) ~[classes!/:?]
at com.keyniu.user.web.tg.interceptors.impl.RecordInterceptor.process(RecordInterceptor.java:40) ~[classes!/:?]
at com.keyniu.user.web.tg.interceptors.Interceptor.intercept(Interceptor.java:13) ~[classes!/:?]
at com.keyniu.user.web.tg.interceptors.InterceptorChain.process(InterceptorChain.java:37) ~[classes!/:?]
at com.keyniu.user.web.tg.tgBot.onUpdateReceived(tgBot.java:70) ~[classes!/:?]
at java.util.ArrayList.forEach(ArrayList.java:1541) ~[?:?]
at org.tg.tgbots.meta.generics.LongPollingBot.onUpdatesReceived(LongPollingBot.java:27) ~[tgbots-meta-6.0.1.jar!/:?]
at org.tg.tgbots.updatesreceivers.DefaultBotSession$HandlerThread.run(DefaultBotSession.java:317) ~[tgbots-6.0.1.jar!/:?]
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: java.lang.NullPointerException
### The error may exist in com/keyniu/out/mapper/TgMessageMapper.java (best guess)
### The error may involve com.keyniu.user.persist.thirdparty.mapper.TgMessageMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO t_tg_message ( update_id, from_id, from_user_name, chat_id, type, data, text, create_time, deleted ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )
### Cause: java.lang.NullPointerException
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) ~[mybatis-3.5.5.jar!/:3.5.5]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199) ~[mybatis-3.5.5.jar!/:3.5.5]
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) ~[mybatis-3.5.5.jar!/:3.5.5]
at jdk.internal.reflect.GeneratedMethodAccessor312.invoke(Unknown Source) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426) ~[mybatis-spring-2.0.5.jar!/:2.0.5]
... 14 more
Caused by: java.lang.NullPointerException
at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) ~[antlr4-runtime-4.7.2.jar!/:4.7.2]
at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.visitInsert(MySQLDMLVisitor.java:147) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.visitInsert(MySQLDMLVisitor.java:127) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser$InsertContext.accept(MySQLStatementParser.java:1090) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) ~[antlr4-runtime-4.7.2.jar!/:4.7.2]
at org.apache.shardingsphere.sql.parser.SQLParserEngine.parse0(SQLParserEngine.java:80) ~[shardingsphere-sql-parser-engine-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.sql.parser.SQLParserEngine.parse(SQLParserEngine.java:61) ~[shardingsphere-sql-parser-engine-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.route.DataNodeRouter.createRouteContext(DataNodeRouter.java:97) ~[shardingsphere-route-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.route.DataNodeRouter.executeRoute(DataNodeRouter.java:89) ~[shardingsphere-route-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.route.DataNodeRouter.route(DataNodeRouter.java:76) ~[shardingsphere-route-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.pluggble.prepare.PreparedQueryPrepareEngine.route(PreparedQueryPrepareEngine.java:54) ~[shardingsphere-pluggable-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.pluggble.prepare.BasePrepareEngine.executeRoute(BasePrepareEngine.java:96) ~[shardingsphere-pluggable-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.underlying.pluggble.prepare.BasePrepareEngine.prepare(BasePrepareEngine.java:83) ~[shardingsphere-pluggable-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.shardingjdbc.jdbc.core.statement.ShardingPreparedStatement.prepare(ShardingPreparedStatement.java:183) ~[sharding-jdbc-core-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.shardingjdbc.jdbc.core.statement.ShardingPreparedStatement.execute(ShardingPreparedStatement.java:143) ~[sharding-jdbc-core-4.1.1.jar!/:4.1.1]
at jdk.internal.reflect.GeneratedMethodAccessor232.invoke(Unknown Source) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
3. 问题定位
一开始是怀疑某个参数字段为空了,反复检查后确定参数都正确设置。表对应的实体类如下:
@Data
@Builder
@TableName("t_tg_message")
public class TgMessageEntity {
@TableId(type = IdType.AUTO)
private Long id;
private Integer updateId;
private Long fromId;
private String fromUserName;
private Long chatId;
private String type;
private String data;
private String text;
private Date createTime;
private Integer deleted;
}
打开MybatisPlus源码抛异常的位置, DEBUG后确定executor、ms、parameter都不是空,才开始关注到root cause,其实是shardingsphere抛出的
看异常堆栈显然是SQL解析报错了,SQL都是生成的,按理应该也没有错,这个时候才关注到字段名可能是数据库关键字,导致解析失败,报了一个莫名其妙的NPE。
Caused by: java.lang.NullPointerException
at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) ~[antlr4-runtime-4.7.2.jar!/:4.7.2]
at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.visitInsert(MySQLDMLVisitor.java:147) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.visitInsert(MySQLDMLVisitor.java:127) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
at org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser$InsertContext.accept(MySQLStatementParser.java:1090) ~[shardingsphere-sql-parser-mysql-4.1.1.jar!/:4.1.1]
4. 经验教训
- 用shardingsphere之类的,要注意是否为关键字,否则有莫名奇妙的问题
- 代码错误可读性和精准,如果一开始报错就是SQL内有关键字,或者MybatisPlus生成SQL的时候转义SQL,体验会好很多