MyBatis version
3.4.6
The null pointer was first discovered by the log file. It appears in the createAutomaticMappings
method of DefaultReusltSetHandler
.
We noticed that at line 480, getUnmappedColumnNames
returned a null, which caused NPE.
We continue to debug and find that loadMappedAndUnmappedColumnNames
is very strange.
The simple non-nested resultType we use in SQL and columnPrefix is null. in getMapkey, the generated map key id was wrong cause id contains no null string. We ran it again in the normal environment and found that the key in the map contains a null string.
The key generated in the loadMappedAndUnmappedColumnNames
does not have a null string
. When using mapkey to obtain the corresponding unMappedColumnNames in method getUnmappedColumnNames
.line 170, the key contains null
and we got null....
how does it appears, i cant understand.
Caused by: java.lang.NullPointerException
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createAutomaticMappings(DefaultResultSetHandler.java:481)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:516)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:401)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:355)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:330)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:303)
... at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:196)
... at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:64)
... 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:326)
... at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
... at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
... at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
... at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
Comment From: ghost
@harawata this bug is very hard to reproduce, at least in my local env, i cant reproducer it again
Comment From: harawata
Do you mean that ResultSetWrapper#getMapKey()
converts null
to an empty String ""
?
Which version of Java do you use?
Comment From: ghost
Do you mean that
ResultSetWrapper#getMapKey()
convertsnull
to an empty String""
? Which version of Java do you use?
Yes , you are right! This is an occasional bug that is difficult to reproduce
When a bug occurs, null is indeed converted to "", but under normal circumstances such bugs do not occur. The current jdk version is java8 8u222, if I remember correctly. I can't understand why in special scenarios, null is converted to an empty string. This is really weird.
Comment From: ghost
Do you mean that
ResultSetWrapper#getMapKey()
convertsnull
to an empty String""
? Which version of Java do you use? @harawata do you have any mind, suggestion or solution for this bugs?
Comment From: harawata
Sorry, I have no idea what is going on.
ResultSetWrapper#getMapKey()
is quite simple and when columnPrefix
is null
, the returned String will end with ":null"
not ":"
.
private String getMapKey(ResultMap resultMap, String columnPrefix) {
return resultMap.getId() + ":" + columnPrefix;
}
If the returned String ended with ":"
, columnPrefix
must have been an empty String ""
, but I couldn't find any scenario where an empty String passed as columnPrefix
.
And the low reproducibility of the issue also is weird.
Do you use any plugin? Is there any unusual factor with your MyBatis configuration?
Comment From: ghost
Sorry, I have no idea what is going on.
ResultSetWrapper#getMapKey()
is quite simple and whencolumnPrefix
isnull
, the returned String will end with":null"
not":"
.
java private String getMapKey(ResultMap resultMap, String columnPrefix) { return resultMap.getId() + ":" + columnPrefix; }
If the returned String ended with
":"
,columnPrefix
must have been an empty String""
, but I couldn't find any scenario where an empty String passed ascolumnPrefix
. And the low reproducibility of the issue also is weird.Do you use any plugin? Is there any unusual factor with your MyBatis configuration?
No, we don't actually use weird plugins.
I thought a lot about this, and one possible point caught my attention. Because of our microservices, the select statement is called very frequently, so some code in ResultSetWrapper
will become hot code, so it will cause JVM JIT optimization and method inline optimization, maybe this may cause some unknown problems.
In the last debugging, when I gave a conditional breakpoint at the null pointer and I caught this unusual scenario
I first cleared "unMappedColumnNames". When I execute "loadMappedAndUnmappedColumnNames"
directly in the "Evaluation Expression" window in IDEA and watch the "unMappedColumnNames"
, we can see that the generated keys are contained in "null"
.
But the strange thing happened. When we cleared the map again, and then placed the method body code in loadMappedAndUnmappedColumnNames directly in the "Evaluation Expression" window
and executed , the generated key has no null string.
List<String> mappedColumnNames = new ArrayList<String>();
List<String> unmappedColumnNames = new ArrayList<String>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
yes wenn i direct execute this code and wenn columnPrefix is null object, and we got a key that without null string at the end of string.
What do you think about this, or do you have any jvm friends who have researched this?
Comment From: ghost
@h3adache @hazendaz @pboonphong
Comment From: h3adache
Due to the nature of this a clear reproduction example is probably difficult to do but can you provide a sample project highlighting what you are trying to do?
Comment From: harawata
Without a repro, there may be nothing we can do.