~bug
3.4.0
1.8.0_172
mysql 5.7.25 (Although this problem is not related to the database)
请参阅具有完整回归测试的独立Maven项目
Please see self-contained maven project with the full regression test
Click here to my project in github
当我使用bind标签后,我的其他非空参数会被转换成null值,见下案例
When using thetag, other non-null parameters I pass in will become Null.See below mapping file
<select id="queryBug" resultType="java.util.HashMap">
select
*
from t_blog
<where>
<bind name="params.a" value="params.a.replaceAll('-','')"/>
id = #{params.id}
</where>
</select>
@Test
public void test()
{
BaseParams baseParams = new BaseParams();
Map<String, String> map = new HashMap<String, String>();
map.put("a","2019-12-24");
map.put("id","2");
baseParams.setParams(map);
// Preparing: select * from t_blog WHERE id = ?
// Parameters: 2(String)
// Total: 1
List query = baseDao.query(baseParams);
// Preparing: select * from t_blog WHERE id = ?
// Parameters: null
// Total: 0
List bug = baseDao.queryBug(baseParams);
}
Preparing: select * from t_blog WHERE id = ?
Parameters: null
Total: 0
The bind tag should only modify the values I specified, and it turned my other values into null values
bind标签应该只能对我所指定的值进行修改,而它将我的其他值变成了空值
It might be a little bit confusing, but I don't think this is a bug.
tl;dr
Don't use dot . in name of <bind />.
There is no point because <bind /> does not modify the parameter object passed to the statement. It always declares a new variable.
Just use a simple name. e.g.
<select id="queryBug" resultType="java.util.HashMap">
select * from t_blog
<where>
<bind name="x" value="params.a.replaceAll('-','')"/>
id = #{x}
</where>
</select>
A slightly longer explanation:
If you write <bind name="params.a" value="123" />, it creates a new Map object with one entry {a=123} and put it under the name params in the _context_ (it's basically a map that contains parameters).
And variables declared by <bind /> have higher priority than the parameter object passed to the statement, so when referencing #{params.id} in the statement, MyBatis looks for a parameter with name 'params' and finds the newly created Map object which does not contain id.
My explanation may not be clear enough.
Please read the source code if you really want to know what is going on.
@mnesarco ,
Please comment if I am writing something inaccurate.
Thank you for your answer
After reading the source code, I found that this is really not a bug.
Mainly reflected in the following code in DefaultParameterHandler class
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters")
.object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // Since <bind> has created a new Map, It would enter here to take params <params.a>
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else { // Generally speaking, when no new map is created, the value of the map I pass in will be used.(here parameterObject)
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
Because here we will first determine whether params exists from additionalParameters, and if it exists, it will take the value in additionalParameters, But the Entry {id: 2} does not exist in additionalParameters, resulting in a null value.
Thanks again @harawata for the answer
It might be a little bit confusing, but I don't think this is a bug.
tl;dr
Don't use dot.innameof<bind />.
There is no point because<bind />does not modify the parameter object passed to the statement. It always declares a new variable.
Just use a simple name. e.g.<select id="queryBug" resultType="java.util.HashMap"> select * from t_blog <where> <bind name="x" value="params.a.replaceAll('-','')"/> id = #{x} </where> </select>A slightly longer explanation:
If you write
<bind name="params.a" value="123" />, it creates a newMapobject with one entry{a=123}and put it under the nameparamsin the _context_ (it's basically a map that contains parameters).
And variables declared by<bind />have higher priority than the parameter object passed to the statement, so when referencing#{params.id}in the statement, MyBatis looks for a parameter with name 'params' and finds the newly createdMapobject which does not containid.My explanation may not be clear enough.
Please read the source code if you really want to know what is going on.@mnesarco ,
Please comment if I am writing something inaccurate.
Closing as it's not a bug.
Most helpful comment
Thank you for your answer
After reading the source code, I found that this is really not a bug.
Mainly reflected in the following code in
DefaultParameterHandlerclassBecause here we will first determine whether
paramsexists fromadditionalParameters, and if it exists, it will take the value inadditionalParameters, But theEntry {id: 2} does not exist inadditionalParameters, resulting in a null value.Thanks again @harawata for the answer