在回顾@Param注解时,发现自己并不理解mepper.xml里sql语句的参数设置。之前认为的是,如果参数类型为Bean,那么语句内填写的参数名应与Bean中get方法名对应。例如:
UserMapper.xml
insert into user values(null,#{name},#{addr},#{age})
那么按我之前的想法,User类中必须存在getName()、getAddr()、getAge()三个方法,User.java
public class User {
private Integer id;
private String name;
private String addr;
private Integer age;
// setters and getters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age= age;
}
}
但是在我后面的测试中,发现如果将get方法名或属性名中任意一个改成与xml中需要的参数名不对应,也能完成插入操作。也就是说,只有在get方法名与属性名都与xml中参数名不对应时,如:方法名为getAgg()、属性名为ag、xml中#{age},才会抛异常,异常信息为:
There is no getter for property named 'age' in 'class com.demo.pojo.User'
在发现实际运行结果与理解出了偏差后,第一时间去Mybatis官网查看了下xml中对参数的要求,其中写到:
insert into users (id, username, password) values (#{id}, #{username}, #{password}) If a parameter object of type User was passed into that statement, the id, username and password property would be looked up and their values passed to a PreparedStatement parameter.
大概意思是,id、username和password三个属性将被寻找。但是没说怎么寻找,在哪寻找,也没说如果参数名不匹配会发生什么。在尝试了百度、询问老师、debug跟踪后,最后将目光锁定在DefaultParameterHandler类中的setParameters()方法上:
// DefaultParameterHandler类
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List 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;
// 获取需要的参数名,如 #{name},propertyName = "name"
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 如果xml中paramType为简单Bean类,将执行此句,通过传入User的对象构建元对象
MetaObject metaObject = configuration.newMetaObject(parameterObject);
// 通过反射,调用get方法,获得存放在对象中的参数值
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null)
jdbcType = configuration.getJdbcTypeForNull();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
当我们在xml中写到#{age}、User类中方法为getAgg()时,上述方法中的propertyName="age"。MetaObject(元对象)又是如何根据“age”执行User类中getAgg()方法呢?继续跟踪,发现MetaObject对象通过调用了Reflector对象的getGetInvoker()方法拿到get方法:
// Reflector类
public Invoker getGetInvoker(String propertyName) {
Invoker method = getMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
}
目标一下明朗起来,只要知道这个叫getMethods的map里到底存了什么东西,什么时候存了东西,问题就好解决了。
原来,在准备连接前,Mybatis会新建多个Reflector类的对象,其中一个就是用来保存User中属性的get方法的。类中getMethods属性为HashMap
// Reflector类
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
getMethods.put(name, new MethodInvoker(method));
getTypes.put(name, method.getReturnType());
}
}
private void addGetField(Field field) {
if (isValidPropertyName(field.getName())) {
getMethods.put(field.getName(), new GetFieldInvoker(field));
getTypes.put(field.getName(), field.getType());
}
}
当执行addGetField()方法时,发现属性age没有与之匹配的get方法(之前添加的是getAgg(),键为“agg”),在这Mybatis会为该属性新建get方法,存入getMethods中!这也就是为什么即便修改了User中的get方法名或属性名,只要保证其中有一项与xml中的参数名相同,就可获取User对象中的相应属性值,完成对预编译sql语句的拼接。
对于下面这条xml中的语句,只要User类中存在与参数名对应的属性名或get方法名,Mybatis即可完成对sql语句的拼接。
insert into user values(null,#{name},#{addr},#{age})
对于参数 #{name} 来说:
在Mybatis将从数据库中查询的结果封装到VO对象时,也遵循类似规则。即,数据库表字段名和VO对象的属性名、set方法其中一者存在对应关系便可完成该属性的赋值,否则对象的该属性值为null。
但是在以后的开发中,VO对象的编写还是要遵循规范。因为不仅是Mybatis在使用,Spring的依赖注入对VO的set方法名有严格要求。
参考:
MyBatis主流程分析之(三)-准备SQL语句和参数替换、执行
《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
Mybatis参数变量替换流程