参数解析
简介
一 、参数解析规则
1.1 单个参数
@Select("select * from tb_test where id=#{id}")
Employee findOne(Long id);
1.2 单个引用类型参数
/**
* @param employee
* @return
*/
@Select("select * from tb_test where name=#{name} and age=#{age}")
List<Employee> selectByEntity(Employee employee);
1.3 多个简单参数
/**
* 多个参数mybatis会做特殊处理 不能直接使用#{name} #{age}
* @param name
* @param age
* @return
*/
@Select("select * from tb_test where name=#{name} and age=#{age}")
List<Employee> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
1.4 多个引用类型参数
/**
* 复杂参数封装
* @param employee
* @return
*/
@Select("select * from tb_test where name=#{name} and age=#{emp.age}")
List<Employee> selectByExmaple(@Param("name")String name,@Param("emp") Employee employee);
1.5 map类型参数
/**
* 用map封装参数
* @param employee
* @return
*/
@Select("select * from tb_test where name=#{name} and age=#{age}")
List<Employee> selectByMap(Map<String,Object> employee);
1.6 Collection类型参数
/**
* 集合参数
* Collection的key是collection
* List的key是list
* 数组的key是array
* @param list
* @return
*/
@Select("select * from tb_test where name=#{list[0]} and age=#{list[1]}")
List<Employee> selectByList(List<Object> l);
二、 参数解析原理
我们来debug进行分析mybatis的参数解析过程:
dubug这段代码:
/**
* SqlSession非线程安全 测试可以这样使用 切记开发环境不可以这样
* 开发环境必须从sqlSessionFactory获取
*/
SqlSession sqlSession;
@Before
public void sqlSession() throws IOException {
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
//sqlSession = sqlSessionFactory.openSession(true); //自动提交
}
@After
public void close() {
sqlSession.close();
}
@Test
public void selectByExmaple(){
EmploeeMapper mapper = sqlSession.getMapper(EmploeeMapper.class);
List<Employee> axiba = mapper.selectByNameAndAge("axiba", 20);
System.out.println(axiba);
}
public interface EmploeeMapper {
/**
* 多个参数mybatis会做特殊处理 不能直接使用#{name} #{age}
* @param name
* @param age
* @return
*/
@Select("select * from tb_test where name=#{name} and age=#{age}")
List<Employee> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
}
断点打在 List<Employee> axiba = mapper.selectByNameAndAge("axiba", 20);
1.先判断当前的方法是不是在object中声明的方法,是的话直接放行,不是的话就将Method 包装成一个MapperMethodInvoker 在cachedInvoker中返回一个PlainMethodInvoker对象,最后在invoke方法中调用PlainMethodInvoker对象的invoke方法。invoke中去调用了this.mapperMethod.execute(sqlSession, args);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass())
? method.invoke(this, args)
: this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperProxy.MapperMethodInvoker invoker
= (MapperProxy.MapperMethodInvoker)this.methodCache.get(method);
return invoker != null ? invoker :
(MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(
method, (m) -> {
if (m.isDefault()) {
try {
return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
throw new RuntimeException(var4);
}
} else {
return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.mapperMethod.execute(sqlSession, args);
}
}
2.根据sql类型进行分类处理,以本次debug来说,在select中先判断返回值类型,我们的返回值类型为返回对象所以走了method.returnsMany()。参数解析是param = this.method.convertArgsToSqlCommandParam(args);
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
3.参数解析
核心代码
3.1 .获取每个标了@param注解的值,name、age
3.2.每次解析一个参数就给 SortedMap<Integer, String> map中保存消息,(key:参数索引,value:name值)如果标注了param注解value就是注解的值,如果没有标注注解先判断是不是使用了全局参数名配置如果使用就会name=参数名,如果没使用name=map.size()。
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
Class<?>[] paramTypes = method.getParameterTypes();
Annotation[][] paramAnnotations = method.getParameterAnnotations();
SortedMap<Integer, String> map = new TreeMap();
int paramCount = paramAnnotations.length;
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
Annotation[] var9 = paramAnnotations[paramIndex];
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
Annotation annotation = var9[var11];
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
if (this.useActualParamName) {
name = this.getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
}
this.names = Collections.unmodifiableSortedMap(map);
}
3.3.进入getNamedParams方法会先判断是否有参数,如果没有就返回null,如果有就再判断是单参数还是多参数。单参数如果配置useActualParamName 就返回names的value值。多参数就循环遍历names将names的key作为新的map的value,将names的value作为新的map的key。并添加param+元素索引
作为key names的key作为value
private final SortedMap<Integer, String> names;
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
if (!this.hasParamAnnotation && paramCount == 1) {
Object value = args[(Integer)this.names.firstKey()];
return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
} else {
Map<String, Object> param = new ParamMap();
int i = 0;
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)var5.next();
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
String genericParamName = "param" + (i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
三 、总结
mybatis在参数解析上会把所有参数都封装在一个map中,可以使用@param标记的名字获取也可以使用param+参数索引获取。